Core concepts

Forms, subforms and widgets

A form controller is the root of a tree of fields and subforms; a widget subscribes to exactly one field.

A form controller owns fields and subforms

AdvancedFormController is the root of a tree. Its direct leaves are the fields you registerFields, and its subtrees are other form controllers attached with addSubform. Almost everything it does is a broadcast over that tree: validate, resetAll, clearErrors, markReadOnly, setValidationMode, setValidationEnabled.

It is also a ValueListenable<AdvancedFormState>, whose derived members — canSubmit, validating, hasFailedValidation, validationErrors — are computed from the live fields on every read, so they can never report a field that has since been removed. Form-level state covers them.

Ownership follows registration: the form disposes every field it was ever given and every subform it was ever attached, in its own dispose(). You dispose the form, and only the form. Lifecycle and ownership has the full set of rules.

Widgets subscribe, one field each

AdvancedFieldBuilder<T, E> is a thin wrapper over ValueListenableBuilder that hides the state type argument. It rebuilds its subtree when — and only when — its field notifies. A form with twenty fields rebuilds one input per keystroke.

AdvancedFieldBuilder<bool, SignupError>(
  field: form.acceptTerms,
  builder: (context, state, _) => Switch(
    value: state.value,
    onChanged: form.acceptTerms.getValueSetter(), // null while read-only
  ),
)

For anything the form as a whole knows — is it modified, is something validating — subscribe to the form controller the same way with ValueListenableBuilder<AdvancedFormState>.

Next

On this page