#100DaysOfCodeChallenge -Crop Management Information System- Day 3

Recap

On Day 2 I created the FarmerServce() class that is responsible for making network calls to Firebase Auth Farmer collection and documents. I also created the AddFarmerCommand() class that is responsible for passing an instance of the FarmerServiceModel() to FarmerService().

Overview

Today I created the Farmer registration form. This form will accept user input than will create an instance of the FarmerServiceModel() that will be stored in cloud firestore.

Building this form exposed weaknesses in my understanding for Flutter layout and using dart late variable.

Add Farmer Form

Below is a screenshot of the form design followed by the code.
Farmer Form

NB: 436 lines of code

class AddFarmerPage extends StatefulWidget {
  static String routeName = 'AddFarmerPage';
  const AddFarmerPage({
    Key? key,
  }) : super(key: key);

  
  _AddFarmerPageController createState() => _AddFarmerPageController();
}

class _AddFarmerPageController extends State<AddFarmerPage> {
  
  Widget build(BuildContext context) => _AddFarmerPageView(this);
  FarmerServiceModel farmer = FarmerServiceModel.form();
  final GlobalKey<FormState> _formkey = GlobalKey<FormState>();
  bool _formChanged = false;

  late FocusNode registrationNumberFocusNode;
  late FocusNode nationalIdFocusNode;
  late FocusNode profilePictureFocusNode;
  late FocusNode firstNameFocusNode;
  late FocusNode lastNameFocusNode;
  late FocusNode nicknameFocusNode;
  late FocusNode dateOfBirthFocusNode;
  late FocusNode genderFocusNode;
  late FocusNode ethnicityFocusNode;
  late FocusNode maritalStatusFocusNode;
  late FocusNode addressFocusNode;
  late FocusNode isHeadOfHouseholdFocusNode;
  late FocusNode isFarmingPrimaryIncomeSourceFocusNode;
  late FocusNode isActiveFarmerFocusNode;
  late FocusNode farmerCategoryFocusNode;
  late FocusNode subsectorFocusNode;
  late FocusNode operationScaleFocusNode;

  
  void initState() {
    super.initState();
    registrationNumberFocusNode = FocusNode();
    nationalIdFocusNode = FocusNode();
    profilePictureFocusNode = FocusNode();
    firstNameFocusNode = FocusNode();
    lastNameFocusNode = FocusNode();
    nicknameFocusNode = FocusNode();
    dateOfBirthFocusNode = FocusNode();
    genderFocusNode = FocusNode();
    ethnicityFocusNode = FocusNode();
    maritalStatusFocusNode = FocusNode();
    addressFocusNode = FocusNode();
    isHeadOfHouseholdFocusNode = FocusNode();
    isFarmingPrimaryIncomeSourceFocusNode = FocusNode();
    isActiveFarmerFocusNode = FocusNode();
    farmerCategoryFocusNode = FocusNode();
    subsectorFocusNode = FocusNode();
    operationScaleFocusNode = FocusNode();
  }

  
  void dispose() {
    super.dispose();
    registrationNumberFocusNode.dispose();
    nationalIdFocusNode.dispose();
    profilePictureFocusNode.dispose();
    firstNameFocusNode.dispose();
    lastNameFocusNode.dispose();
    nicknameFocusNode.dispose();
    dateOfBirthFocusNode.dispose();
    genderFocusNode.dispose();
    ethnicityFocusNode.dispose();
    maritalStatusFocusNode.dispose();
    addressFocusNode.dispose();
    isHeadOfHouseholdFocusNode.dispose();
    isFarmingPrimaryIncomeSourceFocusNode.dispose();
    isActiveFarmerFocusNode.dispose();
    farmerCategoryFocusNode.dispose();
    subsectorFocusNode.dispose();
    operationScaleFocusNode.dispose();
  }

  //FORM LEVEL METHODS
  void _hanldeOnFormChanged() {
    if (_formChanged) return;
    setState(() {
      _formChanged = true;
    });
  }

  void _handleRegisterFarmer() async {
    if (_formkey.currentState!.validate()) {
      _formkey.currentState!.save();
      // todo: create register farmer command
    }
  }

  void _handleDropdownOnChanged(String? value) {}

  String _date = '';

  void _showDatePicker() {
    showDatePicker(
      context: context,
      initialDate: DateTime.now(),
      firstDate: DateTime(1900),
      lastDate: DateTime.now(),
    ).then((value) {
      if (value != null) {
        setState(() {
          // farmer.saveDateOfBirth(value);
          // print(farmer.dateOfBirth);
          print(value.toString());
          _date = value.toString();
          // print(_date);
        });
      }
    });
  }
}

class _AddFarmerPageView
    extends WidgetView<AddFarmerPage, _AddFarmerPageController> {
  final state;
  const _AddFarmerPageView(this.state) : super(state);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Add Farmer'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Form(
            key: state._formkey,
            onChanged: state._hanldeOnFormChanged,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Padding(
                  padding: const EdgeInsets.only(bottom: 24.0),
                  child: Center(
                    child: Text(
                      'Farmer Registration Form',
                      style: TextStyles.title.bold,
                    ),
                  ),
                ),
                Expanded(
                  child: ListView(children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: [
                        // Registration Number
                        Expanded(
                          child: TextFormField(
                            decoration: FormStyles.textFieldDecoration(
                                labelText: 'Registration Number'),
                            focusNode: state.registrationNumberFocusNode,
                            textInputAction: TextInputAction.next,
                            keyboardType: TextInputType.visiblePassword,
                            autovalidateMode:
                                AutovalidateMode.onUserInteraction,
                            validator: state.farmer.validateRegistrationNumber,
                            onSaved: state.farmer.saveRegistrationNumber,
                          ),
                        ),
                        SizedBox(
                          width: 20.0,
                        ),
                        // National ID Number
                        Expanded(
                          child: TextFormField(
                            decoration: FormStyles.textFieldDecoration(
                                labelText: 'National ID Number'),
                            focusNode: state.nationalIdFocusNode,
                            textInputAction: TextInputAction.next,
                            autovalidateMode:
                                AutovalidateMode.onUserInteraction,
                            validator: state.farmer.validateNationalId,
                            onSaved: state.farmer.saveNationalId,
                          ),
                        )
                      ],
                    ),
                    Row(
                      children: [
                        Expanded(
                          child: Column(
                            // mainAxisSize: MainAxisSize.min,
                            crossAxisAlignment: CrossAxisAlignment.stretch,
                            children: [
                              SizedBox(
                                height: 300,
                                width: 300,
                                child: Container(
                                  color: Colors.blue,
                                ),
                              ),
                              ElevatedButton.icon(
                                  onPressed: () {},
                                  icon: Icon(Icons.camera_alt_outlined),
                                  label: Text('Take Picture')),
                            ],
                          ),
                        ),
                        SizedBox(width: 30.0),
                        Expanded(
                          child: Column(children: [
                            // FIRST NAME
                            TextFormField(
                              decoration: FormStyles.textFieldDecoration(
                                  labelText: 'First Name'),
                              focusNode: state.firstNameFocusNode,
                              textInputAction: TextInputAction.next,
                              autovalidateMode:
                                  AutovalidateMode.onUserInteraction,
                              validator: state.farmer.validateFirstName,
                              onSaved: state.farmer.saveFirstName,
                            ),

                            // NICKNAME
                            TextFormField(
                              decoration: FormStyles.textFieldDecoration(
                                  labelText: 'Nickname'),
                              focusNode: state.nicknameFocusNode,
                              textInputAction: TextInputAction.next,
                              autovalidateMode:
                                  AutovalidateMode.onUserInteraction,
                              validator: state.farmer.validateNickname,
                              onSaved: state.farmer.saveNickname,
                            ),
                            // LAST NAME
                            TextFormField(
                              decoration: FormStyles.textFieldDecoration(
                                  labelText: 'Last Name'),
                              focusNode: state.lastNameFocusNode,
                              textInputAction: TextInputAction.next,
                              autovalidateMode:
                                  AutovalidateMode.onUserInteraction,
                              validator: state.farmer.validateLastName,
                              onSaved: state.farmer.saveLastName,
                            ),
                          ]),
                        )
                      ],
                    ),
                    SizedBox(
                      height: 15,
                    ),
                    Row(
                      children: [
                        Expanded(
                          child: TextFormField(
                            readOnly: true,
                            focusNode: state.dateOfBirthFocusNode,
                            decoration: InputDecoration(
                                border: OutlineInputBorder(),
                                labelText: 'Date of Birth',
                                hintText: state._date,
                                suffixIcon: GestureDetector(
                                  child: Icon(Icons.calendar_today_outlined),
                                  onTap: state._showDatePicker,
                                )),
                          ),
                        ),
                        SizedBox(width: 30.0),
                        Expanded(
                          child: DropdownButtonFormField(
                            focusNode: state.genderFocusNode,
                            decoration: FormStyles.textFieldDecoration(
                                labelText: 'Gender'),
                            value: Gender.all[1],
                            onChanged: state._handleDropdownOnChanged,
                            onSaved: state.farmer.saveGender,
                            items: Gender.all
                                .map((e) => DropdownMenuItem(
                                      child: Text(e),
                                      value: e,
                                    ))
                                .toList(),
                          ),
                        ),
                        SizedBox(width: 30.0),
                        Expanded(
                          child: DropdownButtonFormField(
                            focusNode: state.ethnicityFocusNode,
                            decoration: FormStyles.textFieldDecoration(
                                labelText: 'Ethnicity'),
                            onChanged: state._handleDropdownOnChanged,
                            onSaved: state.farmer.saveGender,
                            items: Ethnicity.all
                                .map((e) => DropdownMenuItem(
                                      child: Text(e),
                                      value: e,
                                    ))
                                .toList(),
                          ),
                        ),
                        SizedBox(width: 30.0),
                        Expanded(
                          child: DropdownButtonFormField(
                            focusNode: state.maritalStatusFocusNode,
                            decoration: FormStyles.textFieldDecoration(
                                labelText: 'Marital Status'),
                            onChanged: state._handleDropdownOnChanged,
                            onSaved: state.farmer.saveGender,
                            items: MaritalStatus.all
                                .map((e) => DropdownMenuItem(
                                      child: Text(e),
                                      value: e,
                                    ))
                                .toList(),
                          ),
                        ),
                      ],
                    ),
                    TextFormField(
                      focusNode: state.addressFocusNode,
                      decoration:
                          FormStyles.textFieldDecoration(labelText: 'Address'),
                      textInputAction: TextInputAction.next,
                      autovalidateMode: AutovalidateMode.onUserInteraction,
                      validator: state.farmer.validateAddress,
                      onSaved: state.farmer.saveAddress,
                      maxLines: 5,
                      minLines: 2,
                    ),
                    Row(
                      children: [
                        // FARMER CATEGORY
                        Expanded(
                          child: DropdownButtonFormField(
                            focusNode: state.farmerCategoryFocusNode,
                            decoration: FormStyles.textFieldDecoration(
                                labelText: 'Farmer Category'),
                            onChanged: state._handleDropdownOnChanged,
                            onSaved: state.farmer.saveFarmerCategory,
                            items: FarmerCategory.all
                                .map((e) => DropdownMenuItem(
                                      child: Text(e),
                                      value: e,
                                    ))
                                .toList(),
                          ),
                        ),
                        SizedBox(width: 30.0),
                        // SUBSECTOR
                        Expanded(
                          child: DropdownButtonFormField(
                            focusNode: state.subsectorFocusNode,
                            decoration: FormStyles.textFieldDecoration(
                                labelText: 'Subsector'),
                            onChanged: state._handleDropdownOnChanged,
                            onSaved: state.farmer.saveSubsector,
                            items: Subsector.all
                                .map((e) => DropdownMenuItem(
                                      child: Text(e),
                                      value: e,
                                    ))
                                .toList(),
                          ),
                        ),
                        SizedBox(width: 30.0),
                        // OPERATION SCALE
                        Expanded(
                          child: DropdownButtonFormField(
                            focusNode: state.operationScaleFocusNode,
                            decoration: FormStyles.textFieldDecoration(
                                labelText: 'Operation Scale'),
                            onChanged: state._handleDropdownOnChanged,
                            onSaved: state.farmer.saveOperationScale,
                            items: OperationScale.all
                                .map((e) => DropdownMenuItem(
                                      child: Text(e),
                                      value: e,
                                    ))
                                .toList(),
                          ),
                        ),
                      ],
                    ),
                    SizedBox(
                      height: 15,
                    ),
                    Row(
                      children: [
                        Expanded(
                          child: FormField(builder: (builder) {
                            return CheckboxListTile(
                                title: Text('Head of Household'),
                                value: false,
                                onChanged: (bool) {});
                          }),
                        ),
                        Expanded(
                          child: FormField(builder: (builder) {
                            return CheckboxListTile(
                                title: Text('Farming Primary Income Source'),
                                value: false,
                                onChanged: (bool) {});
                          }),
                        ),
                        Expanded(
                          child: FormField(builder: (builder) {
                            return CheckboxListTile(
                                title: Text('Active Farmer'),
                                value: true,
                                onChanged: (bool) {});
                          }),
                        ),
                      ],
                    ),
                    SizedBox(
                      height: 15,
                    ),
                    ElevatedButton(
                        onPressed: () {}, child: Text('Register Farmer'))
                  ]),
                )
              ],
            )),
      ),
    );
  }
}

This code need to be refactored to improve readability and maintainability. I will refactor this code in a future post.

Building this form made me realize the need to better understand how Layout and constraints in flutter works. We will build our capacity in this area in a future post as well.

Wrap up

On Day 3, I built the Farmer Form but more importantly I realized the need to improve my knowledge of flutter layout and constraints.

Coming to flutter from a Django background made we realize the need of a dart package that automate the creation of forms from dart Models. This is a beautiful problem that I hope to solve in the future.

Connect with me

Thank you for reading my post. Feel free to subscribe below to join me on the #100DaysOfCodeChallenge or connect with me on LinkedIn and Twitter.

24