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

Flutter中reactive_forms结合Bloc时状态未更新的问题

问题:reactive_forms与BLoC结合时表单状态无法更新

我在Flutter中使用reactive_forms包结合BLoC架构开发,尝试将FormGroup托管到BLoC的State类中,通过BlocBuilder提供给ReactiveFormBuilder。但点击保存按钮后,状态中的表单值始终没有更新,调试发现ReactiveBuilder内的form有值,但状态里的表单还是初始值。


Flutter Bloc - 个人信息状态

class ProfileState extends Equatable {
  final initAdditionalDocForm = FormGroup({
    "doc_images": FormControl(),
    "doc_number": FormControl(),
    "doc_type_id": FormControl()
  });
  final FormGroup initPersonalForm = FormGroup({
    'op_first_name': FormControl<String>(
        validators: [Validators.minLength(4), Validators.required]),
    'op_last_name': FormControl<String>(
        validators: [Validators.minLength(4), Validators.required]),
    'op_dob': FormControl<DateTime>(validators: [Validators.required]),
    'op_gender': FormControl<String>(
        validators: [Validators.required], value: "Not Specified"),
    'op_email': FormControl<String>(
        validators: [Validators.required, Validators.email]),
    'op_pet_name': FormControl<String>(),
    'op_mobile_no': FormControl<int>(validators: [
      Validators.number,
    ]),
    'op_alternate_mobile_no': FormControl<int>(validators: [
      Validators.number,
    ]),
    'veh_driving_license_no':
        FormControl<String>(validators: [Validators.required]),
    'veh_license_validity':
        FormControl<DateTime>(validators: [Validators.required]),
    'op_pan_no': FormControl<String>(),
    'AdditionalDoc': FormArray([]),
    'op_address_pin_code': FormControl<String>(
        validators: [Validators.required, Validators.number]),
    'op_address_state': FormControl<String>(validators: [Validators.required]),
    'op_address_city': FormControl<String>(validators: [Validators.required]),
    'op_address_line_1': FormControl<String>(validators: [Validators.required]),
    'op_address_line_2': FormControl<String>(),
    'op_landmark': FormControl<String>(),
    'op_profile': FormControl<String>() //Base 64 Profile Picture
  });

  static const _vehNoRegex =
      r"^[A-Z]{2}[ -][0-9]{1,2}(?: [A-Z])?(?: [A-Z]*)? [0-9]{4}$";

  final initVehicleInfoForm = FormGroup({
    //vehicle images maintained in the screen it self due to naming convention of API
    'veh_no_person': FormControl<int>(validators: [Validators.number]),
    'veh_charge_per_person':
        FormControl<int>(validators: [Validators.number, Validators.min(20)]),
    'veh_registration_no': FormControl<String>(
        validators: [Validators.pattern(_vehNoRegex), Validators.required]),
    'veh_city': FormControl<String>(validators: [Validators.required]),
    'veh_wheel_type': FormControl<int>(value: 4),
    'veh_capacity': FormControl<int>(),
    'veh_dimension': FormControl<String>(),
    'veh_color': FormControl<String>(validators: [Validators.required]),
    'veh_type':
        FormControl<int>(validators: [Validators.required], value: 1), //1 or 2
    'veh_fuel_type': FormControl<String>(validators: [Validators.required]),
    'veh_3km_15km': FormControl<int>(validators: [Validators.number]),
    'veh_above_15km': FormControl<int>(validators: [Validators.number]),
    'AdditionalDoc': FormArray([]),
  });

  final initBusinessProfileForm = FormGroup({
    'doc_pan':
        FormControl<String>(validators: [Validators.required]), //Base64 Image
    'op_bu_address_city':
        FormControl<String>(validators: [Validators.required]),
    'op_bu_address_line_1':
        FormControl<String>(validators: [Validators.required]),
    'op_bu_address_line_2':
        FormControl<String>(validators: [Validators.required]),
    'op_bu_address_pin_code':
        FormControl<int>(validators: [Validators.required]),
    'op_bu_address_state':
        FormControl<String>(validators: [Validators.required]),
    'op_payment_mode': FormControl<String>(validators: [Validators.required]),
    'op_bu_email': FormControl<String>(
        validators: [Validators.required, Validators.email]),
    'op_bu_gstn_available':
        FormControl<bool>(validators: [Validators.required]),
    'op_bu_gstn_no': FormControl<String>(validators: [Validators.required]),
    'op_bu_landmark': FormControl<String>(validators: [Validators.required]),
    'op_bu_name': FormControl<String>(validators: [Validators.required]),
    'op_bu_pan_no': FormControl<String>(validators: [Validators.required]),
    'AdditionalDoc': FormArray([])
  });

  ///Bank Details
  final initPaymentInfoForm = FormGroup({
    'op_bank_name': FormControl<String>(validators: [Validators.required]),
    'op_bank_ifsc': FormControl<String>(validators: [Validators.required]),
    'op_bank_account_number':
        FormControl<int>(validators: [Validators.required]),
  });

  late final FormGroup personalProfileForm;
  late final FormGroup personalProfileAdditionalDocGroup;
  late final FormGroup vehicleInfoForm;
  late final FormGroup businessProfileForm;
  late final FormGroup paymentInfoForm;

  ProfileState({
    FormGroup? personalForm,
    FormGroup? additionalDoc,
    FormGroup? vehicleForm,
    FormGroup? businessForm,
    FormGroup? paymentForm,
  }) : super() {
    personalProfileAdditionalDocGroup = additionalDoc ?? initAdditionalDocForm;
    personalProfileForm = personalForm ?? initPersonalForm;
    vehicleInfoForm = vehicleForm ?? initVehicleInfoForm;
    businessProfileForm = businessForm ?? initBusinessProfileForm;
    paymentInfoForm = paymentForm ?? initPaymentInfoForm;
  }

  @override
  List<Object> get props => [
        personalProfileForm,
        personalProfileAdditionalDocGroup,
        businessProfileForm,
        vehicleInfoForm,
        paymentInfoForm,
      ];

  ProfileState copyWith({
    FormGroup? personalForm,
    FormGroup? additionalDoc,
    FormGroup? vehicleForm,
    FormGroup? businessForm,
    FormGroup? paymentForm,
  }) =>
      ProfileState(
        personalForm: personalForm ?? personalProfileForm,
        additionalDoc: additionalDoc ?? personalProfileAdditionalDocGroup,
        vehicleForm: vehicleInfoForm,
        businessForm: businessProfileForm,
        paymentForm: paymentInfoForm,
      );
}

BLoC方法

主Bloc类中的事件处理方法:

Future<void> _onSaveProfile(ProfileEvent event, Emitter emit) async {
    try {
      emit(ProfileSaving());
      if (event is SaveProfileData) {
        state.copyWith(personalForm: event.personalProfile);
      }

      emit(ProfileSaved());
    } catch (e) {
      print(e);
    }
  }

UI组件

响应式表单构建器代码:

BlocBuilder<ProfileBloc, ProfileState>(
      builder: (context, state) {
        return ReactiveFormBuilder(
          form: () => state.personalProfileForm,
          builder: (context, form, _) => ListView(
            children: [
              /* First & Last Name */
              profileRowBuilder(
                context,
                const ReactiveTextFieldCustomV1(
                  formControlName: "op_first_name",
                  hintText: "",
                  showLabel: true,
                  labelText: "First Name",
                  shortHeight: true,
                ),
              // 省略其他表单组件
              ElevatedButton(
                onPressed: () {
                  
                  context
                      .read<ProfileBloc>()
                      .add(SaveProfileData(personalProfile: form));
                },
                child: Text("Press"),
              ),
              ElevatedButton(
                onPressed: () {
                  print(
                      state.personalProfileForm.control('op_first_name').value);
                },
                child: Text("State"),
              ),
            ],
          ),
        );
      },
    );

问题原因与解决方案

1. 核心问题:未emit新状态

在_onSaveProfile方法中,你调用了state.copyWith()但没有将生成的新状态通过emit发送出去。BLoC的状态更新必须通过emit触发,否则UI和状态都不会更新。

2. copyWith方法参数错误

你的copyWith方法中,vehicleForm、businessForm、paymentForm直接使用了当前state的属性,而不是传入的参数,这会导致这些表单永远无法更新。

3. FormGroup的可变性问题

FormGroup是可变对象,Equatable默认比较对象引用,即使表单内容变化,Equatable会认为状态没有改变,可能导致UI不重建。需要重写Equatable的比较逻辑,或者在保存时创建FormGroup的副本。

修复后的代码

修复BLoC的_onSaveProfile方法

Future<void> _onSaveProfile(ProfileEvent event, Emitter emit) async {
    try {
      emit(ProfileSaving());
      if (event is SaveProfileData) {
        // 创建新状态并emit
        final newState = state.copyWith(personalForm: event.personalProfile);
        emit(newState);
      }
      emit(ProfileSaved());
    } catch (e) {
      print(e);
      // 可以添加错误状态emit
      emit(ProfileSaveFailed(e.toString()));
    }
  }

修复ProfileState的copyWith方法

ProfileState copyWith({
    FormGroup? personalForm,
    FormGroup? additionalDoc,
    FormGroup? vehicleForm,
    FormGroup? businessForm,
    FormGroup? paymentForm,
  }) =>
      ProfileState(
        personalForm: personalForm ?? personalProfileForm,
        additionalDoc: additionalDoc ?? personalProfileAdditionalDocGroup,
        vehicleForm: vehicleForm ?? vehicleInfoForm, // 改为使用参数
        businessForm: businessForm ?? businessProfileForm, // 改为使用参数
        paymentForm: paymentForm ?? paymentInfoForm, // 改为使用参数
      );

处理FormGroup的Equatable比较

为了让Equatable正确识别FormGroup的内容变化,需要重写props中的比较逻辑,或者在保存时创建FormGroup的副本:

// 可选:创建FormGroup的副本方法
FormGroup _cloneFormGroup(FormGroup original) {
  return FormGroup.fromMap(original.value, validators: original.validators);
}

// 在emit新状态时使用副本
final newState = state.copyWith(
  personalForm: _cloneFormGroup(event.personalProfile),
);
emit(newState);

reactive_forms与BLoC结合的最佳实践

  • 单向数据流:表单的输入变化可以直接在reactive_forms中处理,只有需要持久化或跨组件共享的状态才通过BLoC更新。
  • 避免直接传递FormGroup:可以只传递表单的value(Map)到BLoC,在State中根据value重建FormGroup,减少可变对象的传递。
  • 拆分状态:将大型表单拆分为多个小的FormGroup,对应不同的子状态,避免单个State过于臃肿。
  • 状态分层:使用加载、保存、成功、失败等不同状态类(如ProfileSaving、ProfileSaved、ProfileSaveFailed),让UI能清晰处理不同状态。
  • 表单验证:reactive_forms的内置验证可以在UI层处理,BLoC只负责最终提交前的整体验证或API验证。

内容的提问来源于stack exchange,提问作者Arham S C

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 11:07:11