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.
NB: 436 lines of code
class AddFarmerPage extends StatefulWidget {
static String routeName = 'AddFarmerPage';
const AddFarmerPage({
Key? key,
}) : super(key: key);
@override
_AddFarmerPageController createState() => _AddFarmerPageController();
}
class _AddFarmerPageController extends State<AddFarmerPage> {
@override
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;
@override
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();
}
@override
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);
@override
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.
Top comments (0)