Field controllers

The four controllers

Text, boolean, single-select and multi-select — constructors, setters, and the methods they all share.

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

pizza_order.dartidle
Source2 files
pizza_order_controller.dart
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 TextEditingControllerfield.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

Next

On this page