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),
],
);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.
| 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 |
SignatureValidation | (): the field must carry a signature | 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, {
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:
- Takes what the rules decided from
FormRuleEngine.evaluate, unless you passoutcome, and checks the values rules compute. - Skips fields a rule hides, or whose container it hides.
- Runs the type's own checks first: rating bounds, and range start before end.
- Checks a field a rule requires as required, then runs each active validation in list order. A validation is active unless an
ApplyValidationeffect names it; then it runs only while one of those rules holds. - 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
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:
| 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(..., kit:, host:, now:). issues.blocksSubmission is true when any issue is an error. |
FormView | New kit: 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.