24
#100DaysOfCodeChallenge -Crop Management Information System- Day 3
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()
.
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.
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.
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.
24