You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中使用Bloc在组件重建时保存输入值的方案

问题分析与解决方案

你的核心问题有两个:一是**TextEditingController在build方法内创建**,每次Bloc状态变化触发组件重建时,控制器会被重新初始化,导致输入值丢失;二是表单输入值没有纳入Bloc的状态管理,切换选项时无法持久化数据。

下面是具体的修改方案,将所有输入状态交由Bloc管理,同时优化控制器的生命周期:


第一步:更新Bloc的状态与事件

修改Bloc代码,把所有输入字段加入状态,并添加对应的输入事件来更新状态:

class FormBloc extends Bloc<FormEvent, MyFormState> {
  FormBloc() : super(MyFormState.initial()) {
    on<RadioButtonFormEvent>(_setRadioButtonState);
    on<NameChangedEvent>(_onNameChanged);
    on<PriceChangedEvent>(_onPriceChanged);
    on<FormOneChangedEvent>(_onFormOneChanged);
    on<FormTwoChangedEvent>(_onFormTwoChanged);
    on<FormThreeChangedEvent>(_onFormThreeChanged);
  }

  void _setRadioButtonState(RadioButtonFormEvent event, Emitter<MyFormState> emit) {
    emit(state.copyWith(
      buttonIndex: event.buttonIndex,
      buttonName: event.buttonName,
    ));
  }

  void _onNameChanged(NameChangedEvent event, Emitter<MyFormState> emit) {
    emit(state.copyWith(name: event.name));
  }

  void _onPriceChanged(PriceChangedEvent event, Emitter<MyFormState> emit) {
    emit(state.copyWith(price: event.price));
  }

  void _onFormOneChanged(FormOneChangedEvent event, Emitter<MyFormState> emit) {
    emit(state.copyWith(formOne: event.value));
  }

  void _onFormTwoChanged(FormTwoChangedEvent event, Emitter<MyFormState> emit) {
    emit(state.copyWith(formTwo: event.value));
  }

  void _onFormThreeChanged(FormThreeChangedEvent event, Emitter<MyFormState> emit) {
    emit(state.copyWith(formThree: event.value));
  }
}

class MyFormState extends Equatable {
  final int buttonIndex;
  final String buttonName;
  final String name;
  final String price;
  final String formOne;
  final String formTwo;
  final String formThree;

  const MyFormState({
    required this.buttonIndex,
    required this.buttonName,
    required this.name,
    required this.price,
    required this.formOne,
    required this.formTwo,
    required this.formThree,
  });

  // 初始状态初始化
  factory MyFormState.initial() {
    return const MyFormState(
      buttonIndex: 0,
      buttonName: "option one",
      name: "",
      price: "",
      formOne: "",
      formTwo: "",
      formThree: "",
    );
  }

  // 状态复制方法,用于局部更新
  MyFormState copyWith({
    int? buttonIndex,
    String? buttonName,
    String? name,
    String? price,
    String? formOne,
    String? formTwo,
    String? formThree,
  }) {
    return MyFormState(
      buttonIndex: buttonIndex ?? this.buttonIndex,
      buttonName: buttonName ?? this.buttonName,
      name: name ?? this.name,
      price: price ?? this.price,
      formOne: formOne ?? this.formOne,
      formTwo: formTwo ?? this.formTwo,
      formThree: formThree ?? this.formThree,
    );
  }

  @override
  List<Object?> get props => [buttonIndex, buttonName, name, price, formOne, formTwo, formThree];
}

abstract class FormEvent extends Equatable {}

class RadioButtonFormEvent extends FormEvent {
  final int buttonIndex;
  final String buttonName;

  const RadioButtonFormEvent({
    required this.buttonIndex,
    required this.buttonName,
  });

  @override
  List<Object?> get props => [buttonIndex, buttonName];
}

// 新增各输入字段的变更事件
class NameChangedEvent extends FormEvent {
  final String name;

  const NameChangedEvent(this.name);

  @override
  List<Object?> get props => [name];
}

class PriceChangedEvent extends FormEvent {
  final String price;

  const PriceChangedEvent(this.price);

  @override
  List<Object?> get props => [price];
}

class FormOneChangedEvent extends FormEvent {
  final String value;

  const FormOneChangedEvent(this.value);

  @override
  List<Object?> get props => [value];
}

class FormTwoChangedEvent extends FormEvent {
  final String value;

  const FormTwoChangedEvent(this.value);

  @override
  List<Object?> get props => [value];
}

class FormThreeChangedEvent extends FormEvent {
  final String value;

  const FormThreeChangedEvent(this.value);

  @override
  List<Object?> get props => [value];
}

第二步:优化表单UI组件

将FormInput改为StatefulWidget,把控制器移到initState中初始化并绑定Bloc状态,避免每次重建丢失数据:

class FormInput extends StatefulWidget {
  const FormInput({super.key});

  @override
  State<FormInput> createState() => _FormInputState();
}

class _FormInputState extends State<FormInput> {
  late final TextEditingController nameController;
  late final TextEditingController priceController;
  late final TextEditingController formOneController;
  late final TextEditingController formTwoController;
  late final TextEditingController formThreeController;
  late final FormBloc formBloc;

  @override
  void initState() {
    super.initState();
    formBloc = BlocProvider.of<FormBloc>(context);
    final initialState = formBloc.state;
    
    // 从Bloc初始状态初始化控制器
    nameController = TextEditingController(text: initialState.name)
      ..addListener(_onNameChanged);
    priceController = TextEditingController(text: initialState.price)
      ..addListener(_onPriceChanged);
    formOneController = TextEditingController(text: initialState.formOne)
      ..addListener(_onFormOneChanged);
    formTwoController = TextEditingController(text: initialState.formTwo)
      ..addListener(_onFormTwoChanged);
    formThreeController = TextEditingController(text: initialState.formThree)
      ..addListener(_onFormThreeChanged);
  }

  @override
  void dispose() {
    // 销毁控制器避免内存泄漏
    nameController.dispose();
    priceController.dispose();
    formOneController.dispose();
    formTwoController.dispose();
    formThreeController.dispose();
    super.dispose();
  }

  // 输入变化时触发Bloc事件更新状态
  void _onNameChanged() {
    formBloc.add(NameChangedEvent(nameController.text));
  }

  void _onPriceChanged() {
    formBloc.add(PriceChangedEvent(priceController.text));
  }

  void _onFormOneChanged() {
    formBloc.add(FormOneChangedEvent(formOneController.text));
  }

  void _onFormTwoChanged() {
    formBloc.add(FormTwoChangedEvent(formTwoController.text));
  }

  void _onFormThreeChanged() {
    formBloc.add(FormThreeChangedEvent(formThreeController.text));
  }

  @override
  Widget build(BuildContext context) {
    final formState = context.watch<FormBloc>().state;
    final optionOneIsActive = formState.buttonIndex == 0;
    final optionTwoIsActive = formState.buttonIndex == 1;

    return Container(
      padding: EdgeInsets.only(
          bottom: MediaQuery.of(context).viewInsets.bottom,
          top: 15,
          left: 15,
          right: 15),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          TextField(
            controller: nameController,
            decoration: const InputDecoration(hintText: 'Name'),
          ),
          const SizedBox(height: 10),
          TextField(
            controller: priceController,
            keyboardType: TextInputType.number,
            decoration: const InputDecoration(hintText: 'Price'),
          ),
          const SizedBox(height: 15),
          const OptionsWidget(),
          Visibility(
              visible: optionOneIsActive,
              child: TextField(
                controller: formOneController,
                keyboardType: TextInputType.number,
                decoration: const InputDecoration(hintText: 'Form one'),
              )),
          Visibility(
              visible: optionTwoIsActive,
              child: Column(
                children: [
                  TextField(
                    controller: formTwoController,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(hintText: 'Form two'),
                  ),
                  TextField(
                    controller: formThreeController,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(hintText: 'Form three'),
                  ),
                ],
              )),
          const SizedBox(height: 10),
          ElevatedButton(
            onPressed: () {
              BlocProvider.of<ProductsListBloc>(context).add(
                AddProductEvent(
                  productName: formState.name,
                  productPrice: int.parse(formState.price),
                  productDescOne: formState.formOne,
                  productDescTwo: formState.formTwo,
                  productDescThree: formState.formThree,
                  formOption: formState.buttonName,
                ),
              );
            },
            child: const Text('Create New'),
          ),
        ],
      ),
    );
  }
}

class OptionsWidget extends StatelessWidget {
  const OptionsWidget({super.key});

  Widget CustomRadioButton(String text, int index, BuildContext context) {
    final formBloc = BlocProvider.of<FormBloc>(context);
    final formState = context.watch<FormBloc>().state;
    final isSelected = formState.buttonIndex == index;

    return OutlinedButton(
        onPressed: () {
          formBloc.add(RadioButtonFormEvent(
            buttonIndex: index,
            buttonName: text,
          ));
        },
        style: OutlinedButton.styleFrom(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
          side: BorderSide(color: isSelected ? Colors.blue : Colors.grey),
        ),
        child: Text(
          text,
          style: TextStyle(
            color: isSelected ? Colors.blue : Colors.grey,
          ),
        ));
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        CustomRadioButton("option one", 0, context),
        const SizedBox(width: 15),
        CustomRadioButton("option two", 1, context),
      ],
    );
  }
}

关键改动说明

  1. 状态持久化:所有输入字段都存入Bloc的MyFormState,切换选项时状态不会丢失,提交时直接从Bloc状态取值,保证数据一致性。
  2. 控制器生命周期优化:控制器在initState中初始化,绑定Bloc初始值并添加监听,只在组件销毁时释放,避免重建时重置输入。
  3. UI逻辑简化:直接通过Bloc状态判断选项是否激活,无需在build中重复计算状态。

内容的提问来源于stack exchange,提问作者Vitali Danichkin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 20:00:58