Field controllers

Custom controllers

Use the concrete base class for any value, extend it for domain methods, and override setValue to transform what the user types.

The concrete base class

AdvancedFieldController<T, E> is not abstract. Construct it directly for any value with no dedicated controller — a slider, a stepper, a date, a rating, a derived total — and bind it like any other field. Scalars has a running slider.

final rating = AdvancedFieldController<int, MyError>(
  initialValue: 0,
  validator: (stars) => stars == 0 ? MyError.required : null,
  name: 'rating',
);

Ordinary user writes need no force: — that is only for writing to a field you have marked read-only.

Domain methods

Extend the base class — or AdvancedTextFieldController when the widget binds a text controller — to add methods on top of setValue:

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 increment() => setValue(fieldValue + 1);
}

Transforming what the user types

To normalise, mask or upper-case, override setValue on a text controller. Every write to textController — keystroke, paste or programmatic — goes through the public setValue, so the override always runs, and the transformed value is written back into the text controller in the same turn:

class PhoneFieldController extends AdvancedTextFieldController<MyError> {
  PhoneFieldController({String initialValue = '', super.validator})
    : super(initialValue: _digits(initialValue));

  static String _digits(String value) => value.replaceAll(RegExp(r'\D'), '');

  @override
  void setValue(String newValue, {bool force = false}) =>
      super.setValue(_digits(newValue), force: force);
}

Normalise initialValue before it reaches super, as above: reset() returns to that value without passing through setValue.

A list as the error type

The example app's PasswordFieldController is a text field whose error type is a List, so one field reports several rule violations at once — Custom validators has a running version.

Spell out E when nothing infers it

With no validator, E infers to its bound Object. AdvancedBooleanFieldController<MyError>() keeps every later switch on your enum compiling. The same goes for a map of mixed fields: Map<String, AdvancedFieldController<dynamic, MyError>>.

On this page