Validation
A validation belongs to one field. A field's validations list is ordered, can hold several rules, and each rule carries its own message and severity: warning: true reports a problem without blocking submission.
final batch = TextFormField(
key: 'batch',
label: 'Batch number',
required: true, // becomes validations[0]
validations: const [
LengthValidation(min: 11, max: 11), // [1]
PatternValidation(r'^B-\d{4}-\d{4}$', // [2]
message: 'Use the B-YYYY-NNNN format.'),
UniqueValidation('batches', // [3]
message: 'This batch was logged before.', warning: true),
],
);Indexes matter: an ActivateValidation rule effect names a validation by its index.
Built-in validations
Every validation is a subtype of the sealed FormValidation({String? message, bool warning = false}). The last column shows which ones the engine checks today; the rest are checked from 0.2.0.
| Validation | Constructor | Checked in 0.1 |
|---|---|---|
RequiredValidation | ({message, warning}) | Yes |
LengthValidation | ({int? min, int? max}) | Yes |
FormatValidation | (TextFormat format): email, url, phone, identifier | No |
PatternValidation | (String pattern) | No |
AllowedValuesValidation | (Iterable<String> values) | Yes |
SelectionCountValidation | ({int? min, int? max}) | Yes |
NumericRangeValidation | ({num? min, num? max, includeMin = true, includeMax = true}) | Yes |
PrecisionValidation | ({decimalPlaces, increment}) | No |
AllowedCurrenciesValidation | (Iterable<String> currencies) | No |
AllowedUnitsValidation | (Iterable<String> units, {dimension}) | No |
DurationRangeValidation | ({Duration? min, Duration? max}) | No |
TemporalBoundsValidation | ({DateTime? earliest, DateTime? latest}) | No |
CalendarValidation | ({Iterable<int> allowedWeekdays, businessHoursId}) | No |
RangeIntegrityValidation | ({allowEqual = true, Duration? minSpan, Duration? maxSpan}) | Yes |
ReferenceEligibilityValidation | (String policyId) | No (host) |
AttachmentValidation | ({minCount, maxCount, maxBytes, mediaTypes, minWidth, maxWidth, minHeight, maxHeight}) | No |
UniqueValidation | (String scopeId) | No |
CrossFieldValidation | ({required otherFieldId, required FieldComparison comparison}) | No |
CustomValidation | (String typeId, {int version = 1}) | No |
FieldComparison has equal, notEqual, lessThan, lessOrEqual, greaterThan and greaterOrEqual.
Settings that contradict each other (a negative length, min above max) and two required validations on one field are design errors: the FormDefinition constructor throws.
Current limits (0.1)
- Only six validation types are checked by the engine: required, length, allowed values, selection count, numeric range and range integrity. The others pass locally and are left to the host.
- The engine reads each field's value from the top-level value map, so fields inside a repeating group are not checked per entry.
CustomValidationstores a type id and version only; it has no settings and no check.
The validator registry in 0.2.0 removes all three limits.
Running validations
abstract final class FormValidationEngine {
static List<FormValidationIssue> validate(
FormDefinition definition,
Map<String, Object?> values, {
Map<String, EffectiveFieldState>? fieldStates,
});
}The engine:
- Takes each field's effective state from
FormRuleEngine.evaluate, unless you passfieldStates. - Skips invisible fields.
- Runs the type's own checks first: rating bounds, and range start before end.
- Runs each active validation in list order. A validation is active unless an
ActivateValidationeffect targets it; then it waits for its rule to match. - Returns a flat list of
FormValidationIssue(fieldKey, message, warning). A validation's ownmessagereplaces the default one.
final issues = FormValidationEngine.validate(deviationIntake, values);
final blocking = issues.where((issue) => !issue.warning).toList();In FormView
FormView runs the same engine on every build. It shows a field's first error only for keys in validationKeys, so the host decides when messages appear: typically the fields the user has touched, then every field on submit. Warnings are not shown in 0.1. See the Quick Start.
What 0.2.0 adds (in review)
The validator registry changes the engine and FormView:
| Area | 0.2.0 |
|---|---|
| Coverage | Every built-in validation is checked, through a FormValidatorKind per type. |
| New validation | RelativeDateValidation({direction, amount, unit, includeTime}): a date at most n days or hours in the future or past. |
| Format | FormatValidation gains protocols (default ['https', 'http']) for URLs. |
| Cross-field | CrossFieldValidation gains offset (a Duration added to the other value) and the sameDay and differentDay comparisons. Comparing a date with a date-time compares calendar days. |
| Repeating groups | Each entry is checked with its own values. UniqueValidation inside a group sees the other entries. Issues carry a path of (groupId, index) entries. |
| Engine | validate(..., validators:, host:, now:). issues.blocksSubmission is true when any issue is an error. |
FormView | New validators: and validationHost: parameters. Shows a field's first error, or its first warning, as a warning. |
| Custom validators | CustomValidatorKind<C> with typed settings, a check and a codec. |
| Editor | A Validation tab with an Add validator menu grouped by category, one card per rule, drag to reorder. |
Validations on groups and the form
Not available yet. Only fields have a validations list; FormGroup, RepeatingGroup and FormDefinition carry none. What works today:
CrossFieldValidationcompares one field with another (checked from 0.2.0).RepeatingGroup(minItems:, maxItems:)limits the number of entries.UniqueValidationon a field inside a repeating group rejects duplicates across entries (from 0.2.0).
Container validations (per entry, across entries, and form-level) are planned. See the Roadmap.