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),
  ],
);

Each validation has a stable id, which an ApplyValidation rule effect names. The designer gives each validation it adds a new one (pattern, then pattern-2); a validation declared in code without one takes its type id, and two on one field must not share one: a field with two pattern validations gives the second an id, such as PatternValidation('[0-9]', id: 'pattern-2'), or the form throws and says so. The codec writes the id and requires it.

Built-in validations ​

Every validation is a subtype of the sealed FormValidation({String? id, 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
SignatureValidation(): the field must carry a signatureNo
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 ​

text
abstract final class FormValidationEngine {
  static List<FormValidationIssue> validate(
    FormDefinition definition,
    Map<String, Object?> values, {
    RuleOutcome? outcome,             // what the rules decided; evaluated here when omitted
    FormKit? kit,                     // the validator and rule kinds; the standard kit by default
    FormValidationHost? host,
    DateTime? now,
  });
}

The engine:

  1. Takes what the rules decided from FormRuleEngine.evaluate, unless you pass outcome, and checks the values rules compute.
  2. Skips fields a rule hides, or whose container it hides.
  3. Runs the type's own checks first: rating bounds, and range start before end.
  4. Checks a field a rule requires as required, then runs each active validation in list order. A validation is active unless an ApplyValidation effect names it; then it runs only while one of those rules holds.
  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 ​

A FormKitForm runs the same engine as the form is filled, for the fields each edit can affect. A field's message shows once it is dirty or touched — its first error, else its first warning — and submit() shows them all. See Filling forms.

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(..., kit:, host:, now:). issues.blocksSubmission is true when any issue is an error.
FormViewNew kit: 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.