Validation
The form system supports four categories of validation: synchronous, asynchronous, soft (warnings), and context-aware (cross-field). All validations extend ValidationConfiguration and are bridged to reactive_forms validators at form construction time.
Validation Pipeline
Synchronous Validation
Sync validators run immediately when a field value changes. They return an error message string or null.
ValidationConfiguration
abstract class ValidationConfiguration implements SchemaItem {
final String schemaType;
final String title;
final String? errorMessage;
bool get isSoft => false; // Override for soft validations
String? validate<T>(T? value);
}Built-in Sync Validators
| Validator | Schema Name | Properties |
|---|---|---|
RequiredValidation | formfield.validation.required | errorMessage |
EmailValidation | formfield.validation.text.email | errorMessage |
UrlValidation | formfield.validation.text.url | protocols, hostBlacklist, hostWhitelist |
PatternValidation | formfield.validation.pattern | pattern, allowEmpty |
TextLengthValidation | formfield.validation.text.length | operator (min/max/equal), length |
MinLengthValidation | formfield.validation.minLength | minLength |
MaxLengthValidation | formfield.validation.maxLength | maxLength |
NumberRangeValidation | formfield.validation.numberRange | min, max |
DateRangeValidation | formfield.validation.dateRange | startDate, endDate |
CreditCardValidation | formfield.validation.text.creditCard | Luhn algorithm |
IpAddressValidation | formfield.validation.text.ipAddress | IPv4 and IPv6 |
FutureDateValidation | formfield.validation.futureDate | maxFutureDays, maxFutureHours, includeTime |
PastDateValidation | formfield.validation.pastDate | maxPastDays, maxPastHours, includeTime |
BusinessHoursValidation | formfield.validation.businessHours | allowedWeekdays, timeRanges, excludedDates, dateOnly |
Example: Enrollment Validation
TextField(
name: 'email',
title: 'Email',
validations: [
RequiredValidation(errorMessage: 'Email is required'),
EmailValidation(errorMessage: 'Enter a valid email'),
],
)
DateTimeField(
name: 'start_date',
title: 'Start Date',
selectionType: SelectionType.date,
validations: [
RequiredValidation(errorMessage: 'Start date is required'),
FutureDateValidation(
maxFutureDays: 90,
errorMessage: 'Cannot schedule more than 90 days ahead',
),
BusinessHoursValidation(
allowedWeekdays: [1, 2, 3, 4, 5], // Mon-Fri
timeRanges: [TimeRange(startTime: '09:00', endTime: '17:00')],
dateOnly: true,
errorMessage: 'Must be a business day',
),
],
)Asynchronous Validation
Async validators run after sync validators pass, with configurable debounce. They are bridged to reactive_forms AsyncValidator instances.
abstract class AsyncValidationConfiguration implements SchemaItem {
final Duration timeout;
final Duration debounceDelay;
final String? errorMessage;
Future<String?> validateAsync(dynamic value);
}Async validations are useful for server-side checks like duplicate email detection or username availability.
TextField(
name: 'email',
title: 'Email',
validations: [RequiredValidation(errorMessage: 'Required')],
asyncValidations: [
CustomAsyncValidation(
// Server-side uniqueness check
errorMessage: 'This email is already registered',
),
],
)Form-Level Async Validation
For cross-field async checks (e.g., "this combination of course + date is already booked"), use formAsyncValidations on the Form:
Form(
title: 'Enrollment',
items: [...],
formAsyncValidations: [
BookingConflictValidation(),
],
)Form-level async validations run on submit via form.validateAllAsync() and can set errors on specific fields.
Soft Validation (Warnings)
Soft validations warn the user but do not block form submission. They show amber/yellow indicators instead of red errors.
abstract class SoftValidation extends ValidationConfiguration {
@override
bool get isSoft => true;
SoftBreachDetails? getBreachDetails<T>(T? value, String fieldName);
}SoftRangeValidation
NumberField(
name: 'temperature',
title: 'Temperature',
validations: [
// Hard validation: block submission
NumberRangeValidation(min: -50, max: 200, errorMessage: 'Out of range'),
// Soft validation: warn but allow
SoftRangeValidation(
softMin: 15.0,
softMax: 35.0,
warningMessage: 'Temperature outside normal range',
triggersException: true,
exceptionSeverity: ExceptionSeverity.medium,
),
],
)Collecting Soft Breaches
final breaches = form.collectSoftBreaches();
for (final breach in breaches) {
print('${breach.fieldName}: ${breach.warningMessage}');
print(' Value: ${breach.value}');
print(' Expected: ${breach.softMin} - ${breach.softMax}');
print(' Severity: ${breach.exceptionSeverity}');
}Context-Aware Validation (Cross-Field)
Context-aware validations depend on other fields' values. They implement the ContextAwareValidation mixin:
mixin ContextAwareValidation {
String? validateWithContext<T>(T? value, Map<String, dynamic> formValues);
Set<String> getDependentFields();
}The form automatically subscribes to value changes on dependent fields and re-validates the target field when they change.
DateDependencyValidation
The built-in cross-field validator for date comparisons:
DateTimeField(
name: 'end_date',
title: 'End Date',
selectionType: SelectionType.date,
validations: [
DateDependencyValidation(
dependentFieldName: 'start_date',
operator: DateComparisonOperator.after,
offsetDays: 1,
errorMessage: 'End date must be at least 1 day after start date',
),
],
)DateComparisonOperator values:
| Operator | Meaning |
|---|---|
before | Must be before the dependent field |
after | Must be after the dependent field |
beforeOrEqual | Must be before or same day |
afterOrEqual | Must be after or same day |
sameDay | Must be the same day |
differentDay | Must be a different day |
How Cross-Field Validation Works
- During
Form._buildFormGroup(), fields withContextAwareValidationare detected - A
_ContextAwareBridgedValidatoris added to the FormControl - Subscriptions are created: when any dependent field changes, the target field re-validates
- At validation time, the bridged validator reads the full
formGroup.rawValueand delegates tovalidateWithContext()
DynamicValidator
DynamicValidator is used by the rules system to add or remove validations at runtime. It is attached to fields that have ValidationAction rules.
class DynamicValidator extends Validator<dynamic> {
void activate(String key, ValidationConfiguration config);
void deactivate(String key);
}When a ValidationAction rule fires, it calls activate() or deactivate() on the field's DynamicValidator, which adds or removes the validation from the active set.
DSL Validation Methods
The FieldBuilder base class provides shared validation methods:
.required([String? message])
.email([String? message])
.pattern(String regex, {String? message})
.minLength(int min, {String? message})
.maxLength(int max, {String? message})
.identifier([String? message])
.asyncValidation(AsyncValidationConfiguration validation)Field-specific builders add type-specific methods:
// NumberFieldBuilder
.range(min: 1, max: 100)
.min(1)
.max(100)Next Steps
- Rules System -- Dynamic behavior with conditions and actions
- Validation Patterns -- Real-world validation examples
- Cross-Field Validation -- Advanced cross-field patterns