Validation

Validation modes

Three rules and one table decide when a field validates itself — on submit, on every keystroke, or when it loses focus.

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 three 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

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

"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.

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, 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