What is the difference between TextFormField and TextField?
TextFormField
vs. TextField
TextFormField
returns a TextField
, but wraps the TextField
with extra functionality you can use through a Form
and also without (such as reset, validation, save, etc.).
Generally, you want to use TextFormField
unless writing boiler-plate code is your thing.
How does TextFormField
work? What can it do?
TextFormField
extends FormField
class, (a StatefulWidget
).
FormField
objects do a special thing when they are instantiated: they look up the widget tree for a Form
and register themselves with that Form
.
After registration, these FormField
widgets can be changed by that parent Form
.
Form
is also a StatefulWidget
. It has a FormState
object.
FormState
can get & set data on any/allFormFields
registered to it.
For example to clear the entire form we can call reset()
on FormState
and FormState
will iterate through all child FormFields
registered, resetting each FormField
to its initialValue
(null by default).
Validation is another common use case for putting several FormField
like TextFormField
inside Form
/FormState
. This allows multiple fields to be validated with a single validate()
call on the Form
.
How? FormState
has a validate()
method that iterates through each registered FormField
and calls that FormField's
validate()
method. Much more convenient calling validate()
once on Form
than you manually keeping track of all TextField
and validating each one separately with custom code.
Details
How does a TextFormField register itself with a Form?
FormField
(base class of TextFormField
, etc.) make a call within their build()
method:
Form.of(context)?._register(this);
In English this means:
Search up my context hierarchy until we find a Form
widget (if any) and register myself with that form.
The ?
is in case there is no Form
widget parent. The _register
call will only be run if there is a Form
& FormState
somewhere above.
How does Form.of(context)?._register(this)
work?
Form
& FormState
sneakily use InheritedWidget
.
In FormState
.build() you'll see this code:
return WillPopScope(
onWillPop: widget.onWillPop,
child: _FormScope( // ← sneaky
formState: this,
generation: _generation,
child: widget.child,
),
);
Looking at _FormScope
we see:
class _FormScope extends InheritedWidget
When a parent widget is an InheritedWidget
, any child can find that parent using a special "find me a parent of this exact Type" method.
Here's how that "find me" method is used/exposed inside Form
as a static method we can call from anywhere:
static FormState of(BuildContext context) {
final _FormScope scope = context.dependOnInheritedWidgetOfExactType<_FormScope>();
return scope?._formState;
}
The naming of that method dependOnInheritedWidgetOfExactType
is a bit tricky. It'd be more readable as findInheritedWidgetOfExactType
, but it does more than just find. (We can trigger rebuilds of Form
through children that have registered as dependOn
this FormState
).
Summary
TextFormField
is the deluxe package, air-conditioned, Bluetooth-connected 8-speaker stereo version of TextField
. It includes a lot of common functionality you would use when accepting user-entered information.
TextField
is a simple text field. (you don't care about user input)
TextFormField
is a text field to be used in a form (you care about user input).
If you don't need to validate TextField
.
If you need to validate user input, use TextFormField with validator
.
If you making a
Form
where you require save, reset, or validate operations- useTextFormField
. Else For Simple user input captureTextField
is sufficient.
TextFormField
, which integrates with the Form
widget.
This is a convenience widget that wraps a TextField widget in a FormField.
A Form
ancestor is not required. The Form simply makes it easier to save, reset, or validate multiple fields at once.
To use without a Form, pass a GlobalKey to the constructor and use GlobalKey.currentState to save or reset the form field.
sample:
TextFormField(
autovalidateMode: AutovalidateMode.always
decoration: const InputDecoration(
icon: Icon(Icons.person),
hintText: 'What do people call you?',
labelText: 'Name *',
),
onSaved: (String value) {
// This optional block of code can be used to run
// code when the user saves the form.
},
validator: (String value) {
return value.contains('@') ? 'Do not use the @ char.' : null;
},
)
TextField
, which is the underlying text field without the Form
integration.
The text field calls the onChanged
callback whenever the user changes the text in the field. If the user indicates that they are done typing in the field (e.g., by pressing a button on the soft keyboard), the text field calls the onSubmitted
callback.
Short answer
If you don't know what you need, then use a TextField
. This is the most basic Flutter widget for getting text input from a user. It's the one you should master first.
TextField
Using a TextField
is an easy way to allow user input.
TextField(
decoration: InputDecoration(
hintText: 'Name'
),
);
To get the text that the user entered, you can either get notified every time there is a change like this:
TextField(
decoration: InputDecoration(
hintText: 'Name'
),
onChanged: (text) {
// do something with text
},
),
Or you can use a TextEditingController
, as described here. This will give you access to the text state.
TextFormField
If you find yourself needing to validate user text input before you save it, you might consider using a TextFormField
. Imagine something like this:
There are lots of validation checks that you might want to do on a username and password.
Of course, you could still just use a couple TextFields, but TextFormField
has extra builtin functionality that will make your life easier. Generally, you will only use a TextFormField
when you are using it inside of a Form
widget (though that isn't a strict requirement).
Here is a stripped down example from the documentation:
class MyCustomForm extends StatefulWidget {
@override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
class MyCustomFormState extends State<MyCustomForm> {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) {
// validation logic
},
),
RaisedButton(
child: Text('Submit'),
onPressed: () {
if (_formKey.currentState.validate()) {
// text in form is valid
}
},
),
],
),
);
}
}
See also
- Realistic Forms in Flutter — Part 1
- Forms with Flutter