Subforms

Attaching subforms

addSubform makes another form controller part of this one — its fields join validate, reset and every other broadcast. A dynamic list is the simplest case.

addSubform attaches another AdvancedFormController as a child. Its fields join the parent's validate, resetAll, markReadOnly, clearErrors, setValidationMode, setValidationEnabled and every other broadcast, and they count towards the parent's wasModified, canSubmit, validating and validationErrors.

class CheckoutFormController extends AdvancedFormController {
  CheckoutFormController() {
    registerFields([email]);
    addSubform(shipping); // its fields are now part of this form
  }

  final email = AdvancedTextFieldController(validator: filled(MyError.required));
  final shipping = ShippingFormController(); // another AdvancedFormController
}

A dynamic list

Every guest below is its own AdvancedFormController, attached when added and detached when removed. The parent's validate() walks every attached one.

guest_list.dartidle
Source2 files
guest_list_controller.dart
final _emailPattern = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$');

class GuestController extends AdvancedFormController {
  GuestController() {
    registerFields([name, email]);
  }

  final name = AdvancedTextFieldController(validator: filled('Name required'));
  final email = AdvancedTextFieldController(
    validator: (value) => _emailPattern.hasMatch(value) ? null : 'Not an email',
  );
}

class GuestListController extends AdvancedFormController {
  GuestListController() {
    addGuest();
  }

  // Navigation state of our own: which subforms are on screen, in order.
  // AdvancedFormState.subforms is a Set and knows nothing about order.
  final guests = <GuestController>[];

  void addGuest() {
    final guest = GuestController();
    addSubform(guest);
    guests.add(guest);
    notifyListeners();
  }

  void removeGuest(GuestController guest) {
    // Detaches only. The parent still owns it and disposes it with itself.
    removeSubform(guest);
    guests.remove(guest);
    notifyListeners();
  }
}
DocsActions, DocsSubmitButton and DocsTextField are shorthands these docs define, not part of the package. See Rendering fields for the widget code an app writes.

Attaching, detaching, owning

  • addSubform(form) attaches. A no-op if already attached. The parent applies its validation mode to the subform at once — unless the subform has a mode of its own.
  • addSubform(form, enabled: () => …) attaches a conditional section: the closure decides, now and after every value change in the form, whether the section is attached. Detached, it keeps its values and its owner, and its errors are cleared. See Patterns.
  • removeSubform(form) detaches: the subform stops validating, notifying and counting towards the parent's state, and can be re-attached later. It does not dispose it.
  • The parent owns every subform it was ever given and disposes them all in its own dispose(), attached or detached — the same way it owns registered fields. Do not dispose subforms yourself.
  • Ownership is single-parent. A subform attached to two parents is not supported.
  • Calling addSubform with — or on — a disposed controller throws a descriptive StateError. A disposed controller cannot be reused; build a fresh one per appearance.

Both membership changes recompute wasModified, so attaching an already-modified subform marks the parent modified at once, and removing the only modified child clears the flag.

Next

On this page