Field controllers
Text, boolean, single-select and multi-select controllers, plus read-only fields, server errors, and custom controllers.
AdvancedTextFieldController—Stringvalue; owns aTextEditingControllerand aFocusNode,AdvancedBooleanFieldController—boolvalue, for checkboxes and switches,AdvancedSingleSelectFieldController— one choice fromoptions(dropdowns, radio groups),AdvancedMultiSelectFieldController— a set of choices, withtoggleElement/addValue/removeValue.
All support reset(), markReadOnly() / unmarkReadOnly(), and both sync and async validation. The text and boolean controllers default their initialValue; the two select controllers require both initialValue and options. All accept an optional name, which labels the field in diagnostics and gives logging or serialization a stable handle. Working example: ComplexFormScreen in the example app.
Reading the current state
The full state is field.value, an AdvancedFieldState<T, E>. Two shortcuts cover most reads:
field.fieldValue; // the current value — short for field.value.value
field.error; // the current error, or null — short for field.value.errorRead-only fields and server-side errors
field.markReadOnly(); // setValue becomes a no-op unless force: true
field.setError(MyError.emailTaken); // push an error in from outside, e.g. a server response
field.clearErrors(); // clear everything, including the last async answermarkReadOnly() freezes the value; how the widget looks is yours to decide. Widgets with a nullable callback — Switch, Checkbox, DropdownButton — grey out on their own once getValueSetter() returns null. A text field does not: TextField.readOnly blocks typing but keeps the enabled styling, so pass enabled: !state.readOnly too when a frozen field should also look frozen.
setError(null) is what makes the "apply the server's response to every field" pattern work: fields the server accepted end up valid with nothing to show, rather than invalid with nothing to show.
setError writes validationError only. A code an async check recorded survives it, and the field stays invalid showing that code — use clearErrors() when you mean "forget everything, including the async answer". A pushed error is also not protected from the validators: once the field validates itself, anything that re-runs its sync validator — an edit, subscribeToFields, validateAll — overwrites validationError with whatever the validator returns.
reset() restores the initial value, clears both errors, and makes the field count as untouched again. It keeps its validation mode and readOnly — those are configuration, and configuration changes only through its own API.
AdvancedFormController has markReadOnly(), clearErrors(), and setValidationEnabled(bool) for the whole tree.
Working example: QuizFormScreen in the example app applies a server response with setError.
Writing your own
Extend AdvancedFieldController — your value type, your error type, your domain methods, with validation and lifecycle contributed by the base class:
class IntegerFieldController<E extends Object> extends AdvancedFieldController<int, E> {
IntegerFieldController({
super.initialValue = 0,
super.validator,
super.asyncValidation,
super.name,
});
bool get isNegative => fieldValue.isNegative;
void negate() => setValue(-fieldValue);
}This is the intended way to use the library. example/lib/controllers/password_field_controller.dart is a text field whose error type is List<ValidationError>, so one field reports several rule violations at once.