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
相关产品推荐
相关产品推荐

