Very often we need to build forms in our apps. A number of times it can be pretty much just a bunch of TextFormFields
and DropdownFormFields
and it is very easy to implement other fields like radios and checkboxes with just a comparison of values on the call to validate function. But I recently had too much time to think and I tried to build a custom checkbox formfield. I examined the TextFormField
widget and built one. I’ll explain the process in this article.
The article may seem long because I needed to include the code snapshots to help explain just 3 important parameters needed when building your own custom formfield. If you need the complete code, you can find it here.
The TextFormField
is, basically, written in this manner:
class TextFormField extends FormField<String> { ...@override
_TextFormFieldState createState() => _TextFormFieldState();}class _TextFormFieldState extends FormFieldState<String> { ...}
Here, we see that TextFormField
extends a FormField<T>
. The type we need for a TextFormField
is String
. And so, for our custom CheckboxFormField, we will extend the FormField<bool>
since we need the checkbox to be either true
or false
.
class CheckBoxFormField extends FormField<bool> {@override
_CheckBoxFormFieldState createState() => _CheckBoxFormFieldState();}class _CheckBoxFormFieldState extends FormFieldState<bool> {}
Inspecting the _TextFormFieldState
class we find that it doesn’t actually have a build()
method from which to return a widget. But there is this part of code in this class:
@override
TextFormField get widget => super.widget as TextFormField;
This implies that the super class TextFormField
is responsible for building the widget. And it sure is. Hiding all the unnecessary (for this article) statements from the TextFormField
constructor we get:
TextFormField({
...
}).
...
super(
key:
inititalValue:
onSaved:
validator:
enabled:
autoValidateMode:
builder:(FormFieldState<String> field){
...
return TextField(...);
}
);
The most basic parameters we need from this are initialValue
, validator
and the builder
.
So let’s set up our custom CheckBoxFormField
widget.
class CheckBoxFormField extends FormField<bool> {final bool isChecked;
final Widget label;
final void Function(bool) onChanged;CheckBoxFormField({
this.isChecked,
this.label,
this.onChanged,
FormFieldValidator<bool> validator,
}) : super(
initialValue: isChecked,
validator: validator,
builder: (field) {},
);@override
_CheckBoxFormFieldState createState() => _CheckBoxFormFieldState();}class _CheckBoxFormFieldState extends FormFieldState<bool> {@override
CheckBoxFormField get widget => super.widget;@override
void didChange(bool value) {
super.didChange(value);
}}
With the above code, you can use the builder
method to return a widget. This method has the field
and this offers you a bool
value of isValid
to perform the check according to the validator
provided and you can return a widget built with this.
All you need to remember is that you need the below code to be provided into the onChanged
callback of the CheckBox
widget to provide it a rebuild call when the user interacts with it:
void onChangedHandler(bool value) {
field.didChange(value);
if (onChanged != null) {
onChanged(value);
}
}
And TADA! You have built your own custom FormField
which you can readily use inside your Form
widget and need to just call the validate()
on the state of the form.
I realize that this just reduces a few checks we can do with setState
in any form but when it becomes an app of many forms, this will come in very handy.