In Flutter, how does each AutovalidateMode value change when Form and TextFormField validators run, and which suits a registration form?
answer
- default is disabled
- always: every build, even before typing
- form-level vs field-level scope
- onUnfocus: when focus leaves
- onUserInteractionIfError: only existing errors
basics
~20 sAutovalidateMode decides when validators run without an explicit validate(): disabled (the default) waits for it; always runs on every build; onUserInteraction after an edit; onUnfocus when a field loses focus; onUserInteractionIfError re-checks only fields already showing an error.
solid answer
~40 sBoth `Form` and `FormField` take an `autovalidateMode`, and both default to `AutovalidateMode.disabled`. `always` validates on every build, so an empty form shows errors on its first frame. `onUserInteraction` validates after the user edits, but on a `Form` it is form-wide: once any field is touched, every field validates, untouched ones included. On a single `TextFormField` it validates only that field. `onUnfocus` validates a field when focus leaves it. `onUserInteractionIfError` (Flutter 3.41+) re-checks a field on edits only while it already has an error. For a registration form I validate on submit and use `onUserInteractionIfError`, or `onUnfocus` per field, so errors never appear while someone is still typing.
code
dart · 25 linesimport 'package:flutter/material.dart';
Widget buildRegistrationFields({
required GlobalKey<FormState> formKey,
required FormFieldValidator<String> validatePlate,
required FormFieldValidator<String> validateOwner,
}) {
return Form(
key: formKey, // the Form itself stays AutovalidateMode.disabled
child: Column(
children: [
TextFormField(
autovalidateMode: AutovalidateMode.onUnfocus,
decoration: const InputDecoration(labelText: 'Plate number'),
validator: validatePlate,
),
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteractionIfError,
decoration: const InputDecoration(labelText: 'Owner name'),
validator: validateOwner,
),
],
),
);
}go deeper
Know that validation is off until you call validate(), and name the modes that turn it on automatically: always, onUserInteraction and onUnfocus.
Explain the form-wide versus field-level scope of onUserInteraction, that validate() marks the form as interacted, and what onUserInteractionIfError adds.
Pick a timing per field for a real form, justify it against user frustration and noisy errors, and keep validators pure because some modes run them on every build.
Set a team convention for validation timing across many forms so each screen does not invent its own, and decide where it lives in shared form widgets.
## Where the setting lives `AutovalidateMode` is an enum accepted by both **`Form`** and every **`FormField`** (so by `TextFormField` too). Both default to **`AutovalidateMode.disabled`**. The two settings combine: a field validates when *its own* mode says so, and also when the *enclosing form's* mode says so. An explicit `FormState.validate()` call always validates every registered field, whatever the modes are. "Validate" here means: run the field's `validator` (or take its `forceErrorText`) and store the result as the field's error text, which `TextFormField` shows under the input. ## The five values | Value | When validators run | Scope when set on `Form` | |---|---|---| | `disabled` | Only on an explicit `validate()` | none | | `always` | On every build, from the first frame | every field | | `onUserInteraction` | After the user has changed a value | every field, once **any** field was changed | | `onUnfocus` | When a field loses focus | each field as it loses focus | | `onUserInteractionIfError` | After a change, but only while an error exists | every field, once any field was changed and one has an error | A few mechanics behind the table: - **"Interaction" means a value change.** `FormFieldState.didChange` sets the field's has-interacted flag; typing, pasting and even a programmatic change to a `TextFormField`'s controller all reach it. Merely focusing a field does not. - **`validate()` counts as interaction for the form.** After a submit attempt, a form in `onUserInteraction` mode treats itself as interacted with and revalidates every field on each rebuild, so errors update live as the user fixes them. - **`always` validates inside `build`.** Validators therefore run on every rebuild of the form, which is one reason they must be pure and cheap. - **`onUnfocus` wraps each field in a `Focus` listener** and validates that one field when its focus is lost. An explicit `validate()` still checks every field, including the focused one. - **`reset()` clears the interaction flags**, so modes that wait for interaction go quiet again. ## Form-level onUserInteraction surprises people The most common complaint about Flutter forms is "I typed in the first field and every other field turned red". That is `Form(autovalidateMode: AutovalidateMode.onUserInteraction)` behaving as documented: the form's flag becomes true as soon as *any* field has been changed, and from then on the form validates *all* of its fields on each rebuild. If you want per-field behaviour, put the mode on each `TextFormField` instead of on the `Form`. ## Choosing a timing for a registration form Validation timing is a user-experience decision, but Flutter's modes map onto the usual options: 1. **Submit-only** (`disabled` everywhere): the quietest option; errors appear only after the button is pressed and stay until the next `validate()`. 2. **Submit, then live correction**: leave the fields in `onUserInteractionIfError` (Flutter 3.41+). A field is not judged while the user fills it in; after a failed submit, each field that has an error is re-checked as the user edits it, so the message disappears the moment the value becomes valid. 3. **On leaving the field** (`onUnfocus` on each field): the user finishes a plate number, moves on, and learns immediately if it is malformed. 4. **As they type** (`onUserInteraction` on a field): good for a constraint the user is actively aiming at, such as a password rule, and noisy elsewhere. For a vehicle-registration form with a plate number, an owner name and an address, option 2 or 3 avoids shouting at people mid-keystroke while still correcting them quickly. Reserve `always` for a form that is pre-filled from saved data and must show what is already wrong. ## Combining form and field settings The form's mode and each field's mode are both honoured, so they can be mixed deliberately: - `Form` left at `disabled` with individual fields set to `onUnfocus` gives per-field timing without the form-wide scope. - `Form(autovalidateMode: AutovalidateMode.onUnfocus)` applies focus-loss validation to every field, except a field whose own mode is `always`, which keeps validating on every build. - A field set to `always` inside a quiet form shows its error from the first frame, which is occasionally what a pre-filled, known-bad value needs. Every change also calls `Form.onChanged`, which is the place to recompute something like "enable the Register button" without waiting for a submit. ## Things that do not change with the mode - The validator's signature and its synchronous nature. - `save()`: no mode ever calls `onSaved`. - The need to call `validate()` on submit, because a field that was never touched is never validated by the interaction-based modes.
- With Form(autovalidateMode: AutovalidateMode.onUserInteraction), why do untouched fields show errors after the user types in the first one?On a `Form`, the interaction flag is form-wide: it becomes true once any registered field has changed, and from then on the form validates every field on each rebuild. Empty fields that fail their validators therefore show errors immediately. Setting `onUserInteraction` on each `TextFormField` instead limits validation to the field that was edited.
- Why is AutovalidateMode.always a poor default for an empty sign-up form?`always` validates during every build, starting with the first frame, so a blank form opens covered in "required" errors before the user has done anything. It also runs every validator on every rebuild of the form, which punishes any validator that is slow or has side effects. It suits a pre-filled form that must show what is already wrong.
- Under AutovalidateMode.onUnfocus, does FormState.validate() still check the field that currently has focus?Yes. In Flutter 3.47 an explicit `validate()` runs every registered field's validator regardless of focus or mode; `onUnfocus` only adds automatic validation when focus leaves a field. Flutter 3.29 fixed a bug in which a submit under `onUnfocus` could report a false positive.
saying these in an interview costs you the question
- The default AutovalidateMode for Form and TextFormField is onUserInteraction.
- Form-level onUserInteraction validates only the field the user edited.
- AutovalidateMode.always waits until the user has typed something.
- Focusing a field counts as user interaction for autovalidation.
- With an autovalidate mode set, calling validate() on submit is unnecessary.