Making a Custom FormField in Flutter

Andi
3 min readMay 15, 2021

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.

https://github.com/uncoded-decimal/custom_flutter_widgets

--

--