flux_form 0.5.0
flux_form: ^0.5.0 copied to clipboard
A modular, type-safe form management library. Features input aggregation, sanitization pipelines, declarative validation, smart UI error handling, and State-management agnostic.
0.5.0 #
🏗️ The Schema & Composition Update
This is a major release. It completes the input type hierarchy, introduces cross-schema architecture primitives, adds a fluent builder API, and ships a comprehensive async validation pipeline. Every change is a clean break — no deprecation period.
⚠️ Breaking Changes #
Input Hierarchy Renamed
All concrete list and object inputs have been renamed for consistency with the rest of the input family.
| 0.4.x | 0.5.0 |
|---|---|
ListInput (abstract base) |
BaseListInput |
SimpleListInput (concrete) |
ListInput |
StandardInput<T, E extends FormError> |
ObjectInput<T, E> (abstract) + SimpleObjectInput<T, E> (concrete) |
GenericInput<T, E> |
Removed — use SimpleObjectInput or the builder API |
All Primitive Input Bases Are Now Truly Abstract
StringInput, NumberInput, BoolInput, DateTimeInput, and MapInput
were previously marked final. They are now abstract so user-defined
domain inputs can extend them via InputMixin. Each has a corresponding
Simple* concrete class for composition without subclassing.
FormSchema — touchAll() and reset() Are Now Abstract
Both methods must be implemented in every concrete schema. This was previously optional; it is now enforced at compile time.
FormSchema.reset() Must Increment formKey
FormSchema now carries an immutable int formKey field. The reset()
implementation must pass nextFormKey to the constructor. This enables
stateless widget reset (see below).
FormSubmitter Signature Simplified
FormSubmitter.submit([bool isValid]) — the isValid parameter is removed.
Validity checking is the caller's responsibility. onInvalid is also
removed.
Chain API Removed
ValidatorBuilder, SanitizerBuilder, ValidatorChain, and
SanitizerChain are removed. Use the new per-type builder classes instead.
ComparableValidator.oneOf / notOneOf Removed
Use ObjectValidator.oneOf / notOneOf — these work for any T, not just
Comparable<T>.
✨ New Features #
FormSchema — Schema-Level Architecture
formKey(int) — a generation counter, incremented byreset()vianextFormKey. Use as aValueKeyprefix onTextFieldwidgets to force Flutter to recreate them on reset, clearing visible text without aTextEditingControllerorStatefulWidget.nestedSchemas(Map<String, FormSchema>) — embed sub-schemas (e.g.,AddressSchemainsideProfileSchema). Nested schemas participate inisValid,isTouched,isModified,values, andchangedValuesautomatically.schemaValidators(List<SchemaValidator<E>>) — cross-field validation rules that receive the whole schema. Failures are surfaced viaschemaErrorsand included inisValid.populateFrom(Map<String, dynamic>)— pre-fill the form from a server response map (edit flows). Override in concrete schemas.touchAll()— marks every input touched. Now abstract.reset()— resets every input to itsinitialValueandInputStatus.untouched, and incrementsformKey. Now abstract.validate()— callstouchAll()then checksisValid. Returns the Dart record(FormSchema touched, bool isValid).changedValues— likevaluesbut only includes inputs whereisDirty == true. Nested schemas appear under their key only whenisModified. Use for PATCH API calls.isModified—truewhen any input or nested schema has a value different from itsinitialValue.namedErrors—Map<String, dynamic>of{fieldKey: error}for every invalid input. Useful for server-error mapping.errors— flatList<dynamic>of all non-null errors.invalidInputs—List<FormInput>of all failing inputs.firstErrorOf<E>()— typed variant offirstErrorfor schemas where all inputs share error typeE.isSchemaValid—truewhen allschemaValidatorspass (cross-field rules only, does not re-run per-input validators).schemaErrors—List<dynamic>of errors fromschemaValidators.
SchemaValidator<E> — Cross-Field Validation
A new abstract class for rules that read two or more inputs simultaneously:
SchemaValidator.of<BookingSchema, BookingError>((s) {
if (s.checkIn.value == null || s.checkOut.value == null) return null;
return s.checkOut.value!.isAfter(s.checkIn.value!)
? null : BookingError.checkOutBeforeCheckIn;
})
MultiStepSchema — Wizard / Step-Based Forms
A concrete, immutable class wrapping an ordered list of FormSchema steps
with a currentStepIndex cursor. Key API:
currentStep— the activeFormSchemaadvance()/back()/goToStep(int)— navigation, all return new instancesupdateCurrentStep(FormSchema)/updateStep(int, FormSchema)— mutationvalidateCurrentStep()— returns(MultiStepSchema, bool)validateAll()— touches + validates every steptouchAll()/reset()— cascade into all stepsvalues— mergedMap<String, dynamic>from all stepschangedValues— merged changed values from all stepsprogress—doublein[0.0, 1.0]based on current positioncompletedSteps— count of valid stepsisFirstStep/isLastStep— boundary flags
Builder API — Fluent Input Construction
Six per-type builder classes replace the removed chain API. Each builder
produces a fully configured Simple* input directly — no separate
validator/sanitizer lists needed:
StringInputBuilder<E>— covers allStringValidator,FormatValidator,StringSanitizer, andLogicValidatorrules as named methods.NumberInputBuilder<T extends num, E>—NumberValidatorandNumberSanitizermethods, with.adapt<T>()applied automatically.BoolInputBuilder<E>—BoolValidatormethods.DateTimeInputBuilder<E>— nullable-aware date validators (.required(),.after(),.before(),.onOrAfter(),.onOrBefore(),.between()).ListInputBuilder<T, E>— list-level and item-level validators and sanitizers in one builder, withitemValidate()/itemSanitize()for per-item rules.MapInputBuilder<K, V, E>— map-level and value-level rules.
All builders expose a .validate(Validator) / .sanitize(Sanitizer) escape
hatch for rules not covered by named shortcuts, and a .mode(ValidationMode)
call.
MapValidator — New Validator Namespace
Public namespace consistent with StringValidator, NumberValidator, etc.:
notEmpty, minLength, maxLength, containsKey, requiresKeys,
allValues(predicate), allEntries(predicate).
Validator.compose / AsyncValidator.compose / Sanitizer.compose
Static factory methods that bundle a list of validators or sanitizers into a single named, reusable instance:
final passwordRules = Validator.compose([
StringValidator.required(AuthError.required),
StringValidator.minLength(8, AuthError.tooShort),
StringValidator.hasUppercase(AuthError.noUppercase),
]);
Async Validation Pipeline
FormInput.asyncValidators— declare async rules alongside sync rules on the input class.InputMixin.runAsync(task, onValidating)— the canonical async lifecycle: callsonValidatingsynchronously (for spinner), awaits the task, and returns the resolved input.InputMixin.runBuiltInAsyncValidation(onValidating)— delegates to the input's ownasyncValidatorsgetter.ValidatorPipeline.validateAsyncParallel— fires all async validators simultaneously viaFuture.wait, returns results in declaration order.
InputMixin.setValue(T)
Updates the value without marking the input as InputStatus.touched. This
is the correct method for the blur-mode contract: call setValue in
onChanged, markTouched in onEditingComplete.
FormInput.isDirty
Alias for !isPristine. true when the current value differs from
initialValue. Drives FormSchema.changedValues and isModified.
ValidationMode.blur — Documented UI Contract
blur mode is now fully documented. At runtime it is identical to live
(errors appear when touched). The difference is a UI contract:
- In
onChanged→ callsetValue()(no touch, error hidden). - In
onEditingComplete/ blur callback → callmarkTouched()(error revealed). This enables "validate on focus-loss" without any library-side focus tracking.
EditableSchema Mixin
Opt-in mixin for schemas that support populateFrom. Mix into a schema to
signal intent and enforce the override:
class ProfileSchema extends FormSchema with EditableSchema { ... }
New StringSanitizer Rules
collapseWhitespace()— trims and reduces internal whitespace runs to a single space:"John Doe"→"John Doe".replace(Pattern, String)— general-purpose pattern replacement.truncate(int maxLength)— silently caps string length.
New ListSanitizer Rules
removeWhere(bool Function(T))— removes items matching a predicate.unique()— now preserves insertion order (first occurrence kept).
StringValidator.trimmedRequired
Explicit alias for required. Both trim before checking; trimmedRequired
makes the behaviour self-documenting when the input has no trim sanitizer.
Documented StringValidator.required vs notEmpty
| Value | required / trimmedRequired |
notEmpty |
|---|---|---|
"" |
✗ error | ✗ error |
" " |
✗ error (trimmed) | ✓ valid |
"a" |
✓ valid | ✓ valid |
⚡️ Improvements #
FormSubmitter—onInvalidremoved; validity is always the caller's concern.FormSubmitter.delegatedalso simplified.ListSanitizer.unique()— fixed to preserve insertion order using awhere(seen.add)pattern instead oftoSet().toList()which had undefined iteration order.FormMixin— brought to full parity withFormSchema:touchAll(),reset(),invalidInputs,errorsall added.DateTimeInput—isAfter/isBeforesimplified,daysDifferencehelper added.NumberInput—increment/decrementhelpers preserveTcorrectly for bothintanddoublesubtypes.prepareUpdate— nullable limitation documented: passingvalue: nullon a nullable-typed input is indistinguishable from omitting the argument. Usereset()to revert a nullable field to its initial value.
🗑️ Removed #
Debouncer— usedart:async Timeror an external package (rxdart,easy_debounce,stream_transform).runAsyncis scheduling-agnostic.GenericInput<T, E>— useSimpleObjectInput<T, E>or a builder.StandardInput<T, E>— useObjectInput<T, E>/SimpleObjectInput<T, E>.SimpleListInput<T, E>— renamed toListInput<T, E>.- Old
ListInput<T, E>(abstract) — renamed toBaseListInput<T, E>. FormSubmitter.submit(bool isValid)— validity parameter removed.FormSubmitter.onInvalid— removed.ValidatorBuilder,SanitizerBuilder,ValidatorChain,SanitizerChain— replaced by the builder API.ComparableValidator.oneOf/notOneOf— useObjectValidator.oneOf/notOneOf.