Custom validators
Any function of the right shape is a validator — including one whose error is a list, so a field reports several violations at once.
A factory that takes the error makes a rule reusable across forms with different error types, exactly like the
built-ins. Write string rules over String? so they combine with filled and friends:
Validator<String?, E> matches<E extends Object>(RegExp pattern, E error) =>
(value) => value != null && pattern.hasMatch(value) ? null : error;
/// Pragmatic, not RFC 5322: one `@`, no whitespace, a dot in the domain.
final emailPattern = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$');
Validator<String?, E> isEmail<E extends Object>(E error) =>
matches(emailPattern, error);
Validator<DateTime?, E> notInThePast<E extends Object>(E error) =>
(value) => value != null && value.isBefore(DateTime.now()) ? error : null;
validator: filled(MyError.required) & isEmail(MyError.invalidEmail),A validator that needs another field's value reads it directly — password.fieldValue — and is re-run by
subscribeToFields when that field changes.
One field, several errors
E does not have to be a single code. Make it a list and the validator can report every broken rule at once, which is
what a password strength checklist wants:
Source2 files
enum PasswordRule { minLength, digit, upperCase, symbol }
/// A text field whose error is the list of rules the value breaks.
class PasswordFieldController
extends AdvancedTextFieldController<List<PasswordRule>> {
PasswordFieldController() : super(validator: check);
static List<PasswordRule>? check(String value) {
final broken = [
if (value.length < 8) PasswordRule.minLength,
if (!value.contains(RegExp('[0-9]'))) PasswordRule.digit,
if (!value.contains(RegExp('[A-Z]'))) PasswordRule.upperCase,
if (!value.contains(RegExp('[^A-Za-z0-9]'))) PasswordRule.symbol,
];
return broken.isEmpty ? null : broken;
}
}
String describe(PasswordRule rule) => switch (rule) {
PasswordRule.minLength => 'At least 8 characters',
PasswordRule.digit => 'A digit',
PasswordRule.upperCase => 'An upper-case letter',
PasswordRule.symbol => 'A symbol',
};DocsTextField is a shorthand these docs define, not part of the package. See Rendering fields for the widget code an app writes.The checklist reads the value through the same check function rather than state.error, so it is right before the
user has typed anything: an untouched field has no error, and the checklist should still say every rule is unmet.