The four controllers
Text, boolean, single-select and multi-select — constructors, setters, and the methods they all share.
| Controller | Value | Constructor | Setters |
|---|---|---|---|
AdvancedTextFieldController<E> | String | {initialValue = '', validator, asyncValidation, focusNode, name} | typing into textController, setValue |
AdvancedBooleanFieldController<E> | bool | {initialValue = false, validator, asyncValidation, focusNode, name} | setValue |
AdvancedSingleSelectFieldController<V, E> | V? | {required V? initialValue, required List<V> options, validator, asyncValidation, focusNode, name} | select(V? option); null clears; setOptions(List<V>) |
AdvancedMultiSelectFieldController<V, E> | Set<V> | {required Set<V> initialValue, required List<V> options, validator, asyncValidation, focusNode, name} | toggleElement, addValue, removeValue, setOptions(List<V>) |
All of them support reset(), markReadOnly() / unmarkReadOnly(), setError(), clearErrors(),
setValidationMode(), subscribeToFields(), validate(), getValueSetter(), focus(), and both sync and async
validation. name labels the field in reported errors and as its FocusNode's debug label; it is not used for
identity — fields are identified by reference.
Source2 files
enum Size { small, medium, large }
enum Topping { mozzarella, basil, olives, mushrooms, pepperoni }
class PizzaOrderController extends AdvancedFormController {
PizzaOrderController() {
registerFields([size, toppings, extraCrispy, notes]);
}
// A required dropdown: null to start, notNull to enforce a choice.
final size = AdvancedSingleSelectFieldController<Size, String>(
initialValue: null,
options: Size.values,
validator: notNull('Pick a size'),
);
// A Set value; there is no built-in Set validator, so a closure.
final toppings = AdvancedMultiSelectFieldController<Topping, String>(
initialValue: const {Topping.mozzarella},
options: Topping.values,
validator: (chosen) => chosen.length > 3 ? 'At most 3 toppings' : null,
);
final extraCrispy = AdvancedBooleanFieldController<String>();
final notes = AdvancedTextFieldController<String>(
validator: notLongerThan(60, 'Keep it short'),
);
String get summary =>
'${size.fieldValue?.name} with ${toppings.fieldValue.map((t) => t.name).join(', ')}'
'${extraCrispy.fieldValue ? ', extra crispy' : ''}';
}DocsActions, DocsChipsField, DocsDropdownField, DocsSubmitButton, DocsSwitchField and DocsTextField are shorthands these docs define, not part of the package. See Rendering fields for the widget code an app writes.Text
Owns a TextEditingController — field.textController — kept in two-way sync with the value: user input flows into
the field, and programmatic changes (setValue, reset, prefill, a relation) flow back into the visible text, with
the caret kept on the same characters where possible. Text typed into a read-only field is reverted in the same turn.
Boolean
Defaults to false. The one rule a boolean usually needs is built in: validator: mustBeTrue(MyError.mustAccept).
Single select
Holds a V? and a non-nullable List<V> options. A required dropdown is initialValue: null plus
validator: notNull(MyError.required). select(option) asserts in debug builds that the option is one of options;
select(null) clears and is always allowed. initialValue is never checked, so an off-list initial value is the way
to represent "unknown".
The list can change while the form is open — instructors filtered by the chosen aircraft, cities loaded once the
country is known. setOptions(newList) replaces it and clears a selected value that is not on the new list. The
clearing is the program's doing, like prefill: an untouched field stays untouched and shows no error until
validate(), while a field the user had picked from shows its notNull error at once. A read-only field is cleared
too, since a value that is not on the list cannot be shown. Widgets rebuild even when the value stayed the same.
aircraft.addListener(() {
instructor.setOptions(instructorsFor(aircraft.fieldValue));
});Membership is ==
select, addValue and setOptions compare with ==. A Pilot deserialized from the server is a different object
from the Pilot on the list, so with the default identity == every refresh clears the selection. Make V a value
type (Equatable, a record, an enum) or select by id and resolve to the instance that is on the list.
Multi select
Holds a Set<V>. toggleElement(v) adds or removes; addValue asserts membership in options, removeValue stays
silent for an off-list value. setOptions(newList) replaces the list and drops the selected values that are not on it,
with the same rules as on the single select. Both controllers copy the initialValue and the options you pass, so
mutating them later never reaches the field.
Reading a field
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.error