Recap
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 architecture
Overview
In this post we will receive user input data via our form and save the form data in cloud firestore.
Going Deeper
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.
AddFarmerScreenController
class AddFarmerScreen extends StatefulWidget {
static String routeName = 'AddFarmerScreen';
const AddFarmerScreen({
Key? key,
}) : super(key: key);
@override
_AddFarmerScreenController createState() => _AddFarmerScreenController();
}
class _AddFarmerScreenController extends State<AddFarmerScreen> {
@override
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 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.
FocusNode
class _AddFarmerScreenController extends State<AddFarmerScreen> {
@override
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
.
initState
class _AddFarmerScreenController extends State<AddFarmerScreen> {
@override
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
@override
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.
Dispose
class _AddFarmerScreenController extends State<AddFarmerScreen> {
@override
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
@override
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.
Handle Register Farmer
class _AddFarmerScreenController extends State<AddFarmerScreen> {
@override
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.
The screenshots below show the User input data being submitted via the form followed by the User Input data being stored in cloud firestore.
Handle on Will Pop
class _AddFarmerScreenController extends State<AddFarmerScreen> {
@override
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,
Handle dropdown on changed
class _AddFarmerScreenController extends State<AddFarmerScreen> {
@override
Widget build(BuildContext context) => _AddFarmerScreenView(this);
...
void _handleDropdownOnChanged(FocusNode focusNode) {
focusNode.requestFocus();
}
This method assign focus or highlight the Dropdown Form Field the user is currently interacting with. A screenshot of the Marital Status Dropdown field is show below.
ShowDatePicker
class _AddFarmerScreenController extends State<AddFarmerScreen> {
@override
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;
});
}
},
);
}
This methods displays a date picker when the user taps on the dateOfBirthDropdownFormField()
. Screenshot of the Date picker shown below.
HandleIsHeadOfHousehold
class _AddFarmerScreenController extends State<AddFarmerScreen> {
@override
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.
Wrap Up
We used FocusNode
, TextEditingController
, TextFormField
, showDatePicker
, Checkbox
and Form
to accept user input and send the Form Date to cloud firestore.
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)