Skip to content

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.

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

ValidationConstructorChecked in 0.1
RequiredValidation({message, warning})Yes
LengthValidation({int? min, int? max})Yes
FormatValidation(TextFormat format): email, url, phone, identifierNo
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.
  • CustomValidation stores 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 ​

dart
abstract final class FormValidationEngine {
  static List<FormValidationIssue> validate(
    FormDefinition definition,
    Map<String, Object?> values, {
    Map<String, EffectiveFieldState>? fieldStates,
  });
}

The engine:

  1. Takes each field's effective state from FormRuleEngine.evaluate, unless you pass fieldStates.
  2. Skips invisible fields.
  3. Runs the type's own checks first: rating bounds, and range start before end.
  4. Runs each active validation in list order. A validation is active unless an ActivateValidation effect targets it; then it waits for its rule to match.
  5. Returns a flat list of FormValidationIssue(fieldKey, message, warning). A validation's own message replaces the default one.
dart
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:

Area0.2.0
CoverageEvery built-in validation is checked, through a FormValidatorKind per type.
New validationRelativeDateValidation({direction, amount, unit, includeTime}): a date at most n days or hours in the future or past.
FormatFormatValidation gains protocols (default ['https', 'http']) for URLs.
Cross-fieldCrossFieldValidation 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 groupsEach entry is checked with its own values. UniqueValidation inside a group sees the other entries. Issues carry a path of (groupId, index) entries.
Enginevalidate(..., validators:, host:, now:). issues.blocksSubmission is true when any issue is an error.
FormViewNew validators: and validationHost: parameters. Shows a field's first error, or its first warning, as a warning.
Custom validatorsCustomValidatorKind<C> with typed settings, a check and a codec.
EditorA 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:

  • CrossFieldValidation compares one field with another (checked from 0.2.0).
  • RepeatingGroup(minItems:, maxItems:) limits the number of entries.
  • UniqueValidation on 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.