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 |
AdvancedMultiSelectFieldController<V, E> | Set<V> | {required Set<V> initialValue, required List<V> options, validator, asyncValidation, focusNode, name} | toggleElement, addValue, removeValue |
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".
Multi select
Holds a Set<V>. toggleElement(v) adds or removes; addValue asserts membership in options, removeValue stays
silent for an off-list value. The controller copies the initialValue set and the options list, so mutating what you
passed in 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