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- use TextFormField. Else For Simple user input capture TextField 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'
  ),
);

enter image description here

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:

enter image description here

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

Tags:

Flutter