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:
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,
);
}
Inside the main Bloc Class there is a event method which is fired when the form is filled.
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);
}
}
The reactive form builder is:
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));
// print(
// state.personalProfileForm.control('op_first_name').value);
// print(_drivingLicenseNo);
},
child: Text("Press"),
),
ElevatedButton(
onPressed: () {
print(
state.personalProfileForm.control('op_first_name').value);
// state.personalProfileForm.controls.forEach((key, value) {
// print("$key: ${value.value}");
// });
},
child: Text("State"),
),
],
),
);
},
);
}
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.
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.