Validation

Available modes

The four modes that ship with the package, and the three rules and one table they come from — validate on submit, on every keystroke, when a field is left, or live once it was first left.

A validator is a function: value in, error out, null means valid. When it runs is decided by the form's ValidationMode, and the whole trigger behaviour fits in three rules.

  1. The mode decides which events make a field validate itself. Set it once on the form; it reaches every field and subform, including ones registered or attached later.
  2. A field the user has never edited validates nothing on its own, in every mode. validate() is what checks those, so a prefilled form does not greet the user with errors.
  3. A round runs the sync validator first, and the async validator only if sync passed.

validate() obeys none of them: it ignores the mode, never changes it, and runs every field — touched or not.

The four modes

ValidationModeWhat the user sees
manual (default)Nothing validates until validate() is called — usually on submit. Editing a field still clears the error that described its old value
onUserInteractionEvery edit validates the field being edited; the async check waits out its debounce
onUnfocusLeaving a field the user edited validates it. Tabbing through an untouched field costs nothing; a debounced async check is flushed on the way out
afterFirstUnfocusQuiet while the user types, until the field is first left after an edit. From then on every edit validates it, so an error disappears the moment the value is fixed

The gate, in full — the row is the mode, the column is the event:

modevalue changedfocus lefta dependency changed
manual———
onUserInteractionvalidate— (the edit already did)sync only
onUnfocus— (still typing)validatesync only
afterFirstUnfocus, not yet left— (still typing)validatesync only
afterFirstUnfocus, left oncevalidate— (the edit already did)sync only

afterFirstUnfocus is the onUnfocus row until the user first leaves the field after editing it, and the onUserInteraction row from then on. Only that unfocus flips it: a validate() from a submit button does not, and reset() flips it back. A field the user tabbed through without editing has not been "left" in this sense, so it still waits. Like onUnfocus, it needs the widget to bind field.focusNode (see Configuring the mode).

"A dependency changed" is what subscribeToFields and validateAll produce; it re-runs the sync validator only, because this field's own value did not change and its async verdict still stands.

A mode of your own

The four modes are constants of ValidationMode. A rule none of them states is a class that extends CustomValidationMode and overrides one method: it sees which event is happening and what happened to the field before, and answers how much to run. Everything else on this page still holds for it, the untouched-field rule included. Your own validation mode has the whole story.

Try it

Switch the mode while you type. The form is the same; only the trigger changes.

validation_modes.dartidle
Source2 files
profile_form_controller.dart
class ProfileFormController extends AdvancedFormController {
  ProfileFormController() {
    registerFields([username, website]);
  }

  final username = AdvancedTextFieldController(
    validator:
        filled('Username is required') &
        atLeastLength(3, 'At least 3 characters'),
  );

  final website = AdvancedTextFieldController(
    validator: (value) => value.isEmpty || value.startsWith('https://')
        ? null
        : 'Must start with https://',
  );
}
DocsActions, DocsHint, DocsLinkBanner, DocsModePicker, DocsSubmitButton and DocsTextField are shorthands these docs define, not part of the package. See Rendering fields for the widget code an app writes.

Next

On this page