Prefilled forms
wasModified is baselined at registerFields time, so a form filled after construction needs its baseline moved — or its data passed as initialValue.
wasModified compares each field's value with its value at registerFields time. A controller that registers fields
in its constructor and then loads server data reports wasModified: true forever, and a "Save" button gated on it is
live from the start. Press both buttons below and watch the wasModified chip.
Source2 files
class EditProfileController extends AdvancedFormController {
EditProfileController() {
registerFields([name, email]); // baseline: both empty
}
final name = AdvancedTextFieldController(validator: filled('Required'));
final email = AdvancedTextFieldController(validator: filled('Required'));
/// Writes the loaded values without arming validation — but they differ
/// from the empty baseline, so wasModified becomes true.
Future<void> loadWithPrefill() async {
final profile = await _fetchProfile();
name.prefill(profile.name);
email.prefill(profile.email);
}
/// The same, then registers the same fields again so the loaded values
/// become the new baseline: wasModified is false until the user edits.
Future<void> loadAndRebaseline() async {
await loadWithPrefill();
registerFields([name, email]);
}
Future<({String name, String email})> _fetchProfile() async {
await Future<void>.delayed(const Duration(milliseconds: 400));
return (name: 'Ada Lovelace', email: 'ada@example.com');
}
}DocsActions, DocsFormStatus and DocsTextField are shorthands these docs define, not part of the package. See Rendering fields for the widget code an app writes.Two ways to get the baseline right
- Build the field controllers with the loaded data as
initialValue:. The form is constructed after the data arrives,wasModifiedstartsfalse, andreset()returns to the loaded values — which is what a Discard button should do. Preferred whenever the form has one. - Re-call
registerFields(sameList)once the data has arrived, as above. Re-baselining does not movereset(): that always returns to the constructor'sinitialValue, so after a re-baselineresetAll()lands off the baseline and flipswasModifiedback totrue.
prefill versus setValue
Both write the value and clear both errors. setValue also marks the field as edited by the user, which is what lets a
validation mode fire; prefill does not, so a bad value
from the server shows no error until submit — where validate() still catches it. Both count towards wasModified,
because both change the value.
Who owns what
The form owns what it was given, and you own the form. What registerFields replaces, and which calls throw on a disposed controller.
Testing forms
Controllers are plain ChangeNotifiers with synchronous setters, so a form test is a plain test — with a pump or a fake clock only where async validation is involved.