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