30
#100DaysOfCodeChallenge - Crop Management Information System - Day 6
On Day 5 we refactored the Farmer Registration form to improve the readability of our code. On Day 2 we created the
FarmerService()
class and the AddFarmerCommand()
class in keeping with gskinner MVC+S architectureIn this post we will receive user input data via our form and save the form data in cloud firestore.
We'll look at how all the various pieces fit together. For brevity, we'll select representative widgets and methods to show how the pieces fit together. NB: These code snippets were reused with appropriate variations to build out the form logics.
class AddFarmerScreen extends StatefulWidget {
static String routeName = 'AddFarmerScreen';
const AddFarmerScreen({
Key? key,
}) : super(key: key);
_AddFarmerScreenController createState() => _AddFarmerScreenController();
}
class _AddFarmerScreenController extends State<AddFarmerScreen> {
Widget build(BuildContext context) => _AddFarmerScreenView(this);
FarmerServiceModel farmer = FarmerServiceModel.form();
final GlobalKey<FormState> _formkey = GlobalKey<FormState>();
bool _formChanged = false;
...
}
This controller is responsible for all the AddFarmerScreen logic.
It contain an instance of the
It contain an instance of the
FarmerServiceModel()
called farmer
. This class will be sent to cloud Firestore via the FarmerService
class._formKey
tracks the state of the form. It allows us to validate and save the form data._formChanged
this variable becomes true when the user first interacts with the form.class _AddFarmerScreenController extends State<AddFarmerScreen> {
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
late TextEditingController dateOfBirthController;
late FocusNode nationalIdFocusNode;
late FocusNode lastNameFocusNode;
late FocusNode isHeadOfHouseholdFocusNode;
// FocusNode was created for all form fields. Not shown for
// brevity.
...
}
By creating FocusNode for each form fields, I can programmatically control which form field should should he highlighted as the user interacts with the form. Similarly, I can programmatically control the text of the
DateOfBirthTextField
by the DateOfBirthController
.class _AddFarmerScreenController extends State<AddFarmerScreen> {
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
void initState() {
super.initState();
// Initialize the focusNode
nationalIdFocusNode = FocusNode();
lastNameFocusNode = FocusNode();
dateOfBirthFocusNode = FocusNode();
dateOfBirthController = TextEditingController();
isHeadOfHouseholdFocusNode = FocusNode();
}
}
...
The focusNodes and controllers are initialized when the AddFarmerScreen is added to the widget tree.
class _AddFarmerScreenController extends State<AddFarmerScreen> {
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
void dispose() {
super.dispose();
nationalIdFocusNode.dispose();
lastNameFocusNode.dispose();
dateOfBirthFocusNode.dispose();
isHeadOfHouseholdFocusNode.dispose();
dateOfBirthController.dispose();
}
...
}
FocusNodes and controller are destroyed when the AddFarmerScreen is removed from the widget tree.
class _AddFarmerScreenController extends State<AddFarmerScreen> {
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
void _handleRegisterFarmer() async {
if (_formkey.currentState!.validate()) {
_formkey.currentState!.save();
AddFarmerCommand(context)
.run(farmerServiceModel: farmer, context: context);
}
...
}
This method is called when the user press the RegisterFarmer button. The form is first validated, if validation is successful the form data is added to the
FarmerServiceModel
. The FarmerServiceModel
is then passed to the FarmerService().addFarmer()
method via the AddFarmerCommand().run()
method. These two methods were discussed in details on Day 2.class _AddFarmerScreenController extends State<AddFarmerScreen> {
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
Future<bool> _handleOnWillPop() async {
if (_formChanged) {
return await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Warning'),
content: Text(
'Are you sure you want to abandon the form? Any changes will be lost.'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: Text('Cancel')),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: Text(
'Abandon',
style: TextStyle(color: Colors.red),
)),
],
);
}).then((value) => value!);
} else {
return Future<bool>.value(true);
}
}
To ensure good user experience, the

_handleOnWillPop()
method will be called if the user partially fill out the form but decided to leave the page before registering the Farmer. When this method is called an alertDialog()
will be displayed asking to the confirm there intention to leave the page. Screenshot of this dialog is shown below,class _AddFarmerScreenController extends State<AddFarmerScreen> {
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
void _handleDropdownOnChanged(FocusNode focusNode) {
focusNode.requestFocus();
}
class _AddFarmerScreenController extends State<AddFarmerScreen> {
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
late DateTime _date;
void _showDatePicker() {
showDatePicker(
context: context,
initialDate: DateTime(1960),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
).then(
(value) {
if (value != null) {
setState(() {
dateOfBirthController.text = DateFormat.yMMMd().format(value);
_date = value;
});
}
},
);
}
class _AddFarmerScreenController extends State<AddFarmerScreen> {
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
bool _isHeadOfHousehold = false;
bool? _handleIsHeadOfHouseholdOnChange(bool? value) {
operationScaleFocusNode.unfocus();
if (_isHeadOfHousehold == true) {
setState(() {
_isHeadOfHousehold = false;
});
} else {
setState(() {
setState(() {
_isHeadOfHousehold = true;
});
});
}
}
This method allows the user to toggle between the isHeadOfHousehold checkbox.
We used
FocusNode
, TextEditingController
, TextFormField
, showDatePicker
, Checkbox
and Form
to accept user input and send the Form Date to cloud firestore.30