Bloc状态在Flutter中使用reactive_forms时为什么不更新?

huangapple go评论54阅读模式
英文:

Why is Bloc state not updating when using reactive_forms in flutter?

问题

Here is the translated content:

我正在使用Flutter与reactive_forms包和BLoC。我尝试将FormGroup外包给BLoC状态类,并使用BlocBuilder小部件将其提供给ReactiveFormBuilder

Flutter BLoC - 个人资料状态:

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: [
              /* 名字 */
              profileRowBuilder(
                context,
                const ReactiveTextFieldCustomV1(
                  formControlName: "op_first_name",
                  hintText: "",
                  showLabel: true,


<details>
<summary>英文:</summary>

I am using flutter with `reactive_forms` package and BLoC. I tried to outsource the `FormGroup` to BLoC state class and providing it to the `ReactiveFormBuilder` using `BlocBuilder` widget.

Flutter Bloc - Profile State:
### BLOC STATE
```dart
class ProfileState extends Equatable {
  final initAdditionalDocForm = FormGroup({
    &quot;doc_images&quot;: FormControl(),
    &quot;doc_number&quot;: FormControl(),
    &quot;doc_type_id&quot;: FormControl()
  });
  final FormGroup initPersonalForm = FormGroup({
    &#39;op_first_name&#39;: FormControl&lt;String&gt;(
        validators: [Validators.minLength(4), Validators.required]),
    &#39;op_last_name&#39;: FormControl&lt;String&gt;(
        validators: [Validators.minLength(4), Validators.required]),
    &#39;op_dob&#39;: FormControl&lt;DateTime&gt;(validators: [Validators.required]),
    &#39;op_gender&#39;: FormControl&lt;String&gt;(
        validators: [Validators.required], value: &quot;Not Specified&quot;),
    &#39;op_email&#39;: FormControl&lt;String&gt;(
        validators: [Validators.required, Validators.email]),
    &#39;op_pet_name&#39;: FormControl&lt;String&gt;(),
    &#39;op_mobile_no&#39;: FormControl&lt;int&gt;(validators: [
      Validators.number,
    ]),
    &#39;op_alternate_mobile_no&#39;: FormControl&lt;int&gt;(validators: [
      Validators.number,
    ]),
    &#39;veh_driving_license_no&#39;:
        FormControl&lt;String&gt;(validators: [Validators.required]),
    &#39;veh_license_validity&#39;:
        FormControl&lt;DateTime&gt;(validators: [Validators.required]),
    &#39;op_pan_no&#39;: FormControl&lt;String&gt;(),
    &#39;AdditionalDoc&#39;: FormArray([]),
    &#39;op_address_pin_code&#39;: FormControl&lt;String&gt;(
        validators: [Validators.required, Validators.number]),
    &#39;op_address_state&#39;: FormControl&lt;String&gt;(validators: [Validators.required]),
    &#39;op_address_city&#39;: FormControl&lt;String&gt;(validators: [Validators.required]),
    &#39;op_address_line_1&#39;: FormControl&lt;String&gt;(validators: [Validators.required]),
    &#39;op_address_line_2&#39;: FormControl&lt;String&gt;(),
    &#39;op_landmark&#39;: FormControl&lt;String&gt;(),
    &#39;op_profile&#39;: FormControl&lt;String&gt;() //Base 64 Profile Picture
  });

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

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

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

  ///Bank Details
  final initPaymentInfoForm = FormGroup({
    &#39;op_bank_name&#39;: FormControl&lt;String&gt;(validators: [Validators.required]),
    &#39;op_bank_ifsc&#39;: FormControl&lt;String&gt;(validators: [Validators.required]),
    &#39;op_bank_account_number&#39;:
        FormControl&lt;int&gt;(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&lt;Object&gt; get props =&gt; [
        personalProfileForm,
        personalProfileAdditionalDocGroup,
        businessProfileForm,
        vehicleInfoForm,
        paymentInfoForm,
      ];

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

BLOC Method

Inside the main Bloc Class there is a event method which is fired when the form is filled.

 Future&lt;void&gt; _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 Widget

The reactive form builder is:

BlocBuilder&lt;ProfileBloc, ProfileState&gt;(
      builder: (context, state) {
        return ReactiveFormBuilder(
          form: () =&gt; state.personalProfileForm,
          builder: (context, form, _) =&gt; ListView(
            children: [
              /* First &amp; Last Name */
              profileRowBuilder(
                context,
                const ReactiveTextFieldCustomV1(
                  formControlName: &quot;op_first_name&quot;,
                  hintText: &quot;&quot;,
                  showLabel: true,
                  labelText: &quot;First Name&quot;,
                  shortHeight: true,
                ),
   ........
ElevatedButton(
                onPressed: () {
                  
                  context
                      .read&lt;ProfileBloc&gt;()
                      .add(SaveProfileData(personalProfile: form));
                  // print(
                  //     state.personalProfileForm.control(&#39;op_first_name&#39;).value);
                  // print(_drivingLicenseNo);
                },
                child: Text(&quot;Press&quot;),
              ),
              ElevatedButton(
                onPressed: () {
                  print(
                      state.personalProfileForm.control(&#39;op_first_name&#39;).value);
                  // state.personalProfileForm.controls.forEach((key, value) {
                  //   print(&quot;$key: ${value.value}&quot;);
                  // });
                },
                child: Text(&quot;State&quot;),
              ),
            ],
          ),
        );
      },
    );
  }

The first button is firing the event and I am passing the entire form to the event and as shown in the _onProfileSave() method, it is copied to state. But the form values which are typed in the textfield are null when I click the 2nd button to view the state.

Would be great to receive a solution. Also would be great to receive any suggestions on how to use reactive_forms with BLoC.

I tried to debug it and check via VS Code debugger, but the form inside of ReactiveBuilder there are values but when the event is fired, in the state the form values are still null. They are not copied.

答案1

得分: 0

也许不会发出新状态,因为它们是==。最近我也遇到了同样的问题,我通过以下方法解决了它:

emit(state.copyWith(FormGroup(event.controls)));

英文:

Maybe is not emitting the new state because they are ==. Same thing happened to me recently with FormGroup i solved with this

emit(state.copyWith(FormGroup(event.controls)));

答案2

得分: 0

As bloc requires an event to be fired when updating the state, it had to fire an event when the form changes and use .copyWith (if you have it set up). As this overhead is not necessary in Cubits, it works with cubits. So I generally follow Cubits for such use cases and Blocs for others like auth, etc.

英文:

As bloc requires an event to be fired when updating the state, it had to fire an event when then form changes and use .copyWith (if you have it setup). As this overhead is not necessary in Cubits, it works with cubits. So I generally follow Cubits for such use cases and Blocs for others like auth etc.

huangapple
  • 本文由 发表于 2023年4月19日 19:15:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76053855.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定