advanced_forms 0.2.0+3
advanced_forms: ^0.2.0+3 copied to clipboard
Flutter form validation and state management — typed controllers for text, checkbox, dropdown and multi-select fields, async and cross-field validators, and subforms.
0.2.0+3 #
- Fix static analysis for latest Flutter versions
0.2.0+2 #
- Updated docs
0.2.0 #
Upgrading from 0.1.x? See MIGRATION.md for a step-by-step guide.
Breaking changes #
- The package is renamed from
leancode_formstoadvanced_forms. Update the dependency name and change everypackage:leancode_forms/...import topackage:advanced_forms/.... - Minimum Flutter is now 3.19.0 (was 3.10.0), i.e. Dart 3.3.
- Rebuilt on
ChangeNotifier/ValueListenable, soflutter_blocandrxdartare no longer dependencies. - Renamed the core classes:
FieldCubit→AdvancedFieldControllerTextFieldCubit→AdvancedTextFieldControllerBooleanFieldCubit→AdvancedBooleanFieldControllerSingleSelectFieldCubit→AdvancedSingleSelectFieldControllerMultiSelectFieldCubit→AdvancedMultiSelectFieldControllerFormGroupCubit→AdvancedFormControllerFieldState→AdvancedFieldStateFormGroupState→AdvancedFormStateFieldBuilder→AdvancedFieldBuilder
- Renamed
close()todispose()on both controllers, and removed theDisposablemixin. AdvancedFieldBuilderwrapsValueListenableBuilderinstead ofBlocBuilder, sobuilderis aValueWidgetBuilderand takes a thirdchildparameter.ValueListenableBuilderworks directly too.- Replaced
asyncValidatorandasyncValidationDebouncewithasyncValidation: AsyncValidation(validator:, debounce:, timeout:, onFailure:, failureToError:). validate()returnsFuture<bool>on both controllers and now runs the async validators. Await it.autovalidateis replaced by a named validation mode.ValidationModehas three members —manual(the default),onUserInteraction(whatautovalidate: truedid) and the newonUnfocus. Set it onAdvancedFormControllerthrough the constructor orsetValidationMode, and it reaches every field and subform in the tree, including ones registered or attached later.AdvancedFieldState.autovalidatebecomesAdvancedFieldState.validationMode, andsetAutovalidate(bool)becomessetValidationMode(ValidationMode)on both controllers.validate()no longer turns validation on and lost itsenableAutovalidateparameter on both controllers. The mode you set is the mode the form keeps for its whole life, which is what makes a subform attached after the first submit behave exactly like one attached at build time.- In every mode, a field the user has never edited validates nothing on its own — not on its own change, not when a field it depends on changes, and not on losing focus.
await form.validate()still checks those, so a bad prefilled value cannot get through. Use the newfield.prefill(value)for a value the user did not type: it stores the value and clears the errors without making the field count as edited. form.validateWithAutovalidate()is renamed toform.revalidateSync(), matching the field method it broadcasts.setValidationEnabled(false)now reaches subforms, and a switched-off subtree stops counting entirely: its fields validate nothing, and they are excluded fromcanSubmit,validating,hasFailedValidationandvalidationErrorsas well as fromvalidate(). It composes with a parent's switch by AND, so a section that opted out stays out.reset()keeps the validation mode andreadOnly, and makes the field count as untouched again. Previouslyform.resetAll()unlocked fields your code had locked, and undid the autovalidate thatform.validate()turned on.setError(null)clearsvalidationErrorand the status follows, instead of leaving the fieldinvalidwith nothing to show. It leavesasyncErroralone — useclearErrors()for both.- An error never outlives the value it described: changing the value clears both errors, and so does starting a check.
removeSubformreturnsvoid— drop theawait.removeSubformonly detaches: it no longer disposes the subform, and thecloseflag is gone. The parent owns every subform it was given and disposes them all indispose(), skipping any the caller disposed itself.- A disposed controller throws a
StateErrorfromregisterFields,setValidationEnabled,addSubform,removeSubform,subscribeToFieldsandsetValue. AdvancedMultiSelectFieldControllercopies theinitialValueset and theoptionslist, so mutating what you passed in never reaches the field.FieldStatushas a newfailedValidationvalue, for a check that could not run. Exhaustiveswitches need an arm for it.AdvancedFormState.validationErrorsreports each field'serror(sync or async) instead ofvalidationError.selectandaddValueassert that the value is one ofoptions, and so doestoggleElementwhen it adds.onValuesChangedStream/onStatusChangedStreamare now theListenablesonValuesChanged/onStatusChanged.onStatusChangedcarries no payload.- Removed
clear()from the text, single-select, and multi-select controllers — callreset().
Added #
AsyncValidation.timeoutbounds how long a check may run (default: no bound), andAsyncValidation.failureToErrorturns a failed check into a displayable error code.AdvancedFieldController.lastFailurecarries the exception, its stack trace, and whether the check timed out.AdvancedFormState.canSubmitandAdvancedFormState.hasFailedValidationreport submit readiness and failed checks across the whole form.- The single-select and multi-select controllers accept
asyncValidation, which they could not before. AdvancedTextFieldControllerowns aTextEditingController(field.textController) kept in two-way sync with the value. EveryAdvancedFieldControllerhas aFocusNode(field.focusNode) with afocus()shortcut, so a dropdown or a date picker gets one too — not just text fields. PassfocusNode:to the constructor to bind a node you already own; the field then never disposes it.AdvancedFieldControllergained an optionalString? namefor debugging, logging, and serialization.AdvancedFieldController.revalidateSync()re-runs the sync validator when the field validates itself, for custom cross-field wiring.ValidationMode.onUnfocusvalidates a field when focus moves away from it. Leaving flushes a debounce in any mode; a value unchanged since its last check reuses that answer, so repeated focus cycles cost no requests, and a check that failed is retried. Bind the field'sfocusNodefor it to fire, or callfield.handleUnfocus()from a widget that manages focus itself, such as a picker.field.handleUnfocus()returnsFuture<void>, so a caller that wants to can await the validation it starts. Nothing has to: it reports its own failures.- A field may opt out of its form's mode with
field.setValidationMode(...), and a subform with its own mode — from its constructor or its setter — keeps it. A later change of the parent's mode reaches every other child.validationEnabledstill outranks every mode.
Changed #
- Cross-field checks re-run the sync validator only, both through
subscribeToFieldsand throughvalidateAll: true. - A settled async answer is reused while it still describes the value, so a second submit press on an unchanged form runs no async validators.
Fixed #
- An async result arriving late no longer overwrites a newer value, nor undoes
reset(),clearErrors(),setError()orsetValidationEnabled(false). - A validator that throws before its first
awaitno longer leaves the field stuck onvalidatingforever. markReadOnly()stops a running check, and removing a subform mid-check no longer leaves the form stuck reportingvalidating.addSubformandremoveSubformrecomputewasModified, so a removed subform no longer latches the parent as modified.- Text typed into a read-only text field is reverted immediately.
Deprecated #
AdvancedFieldController.streamreplaces thestreamthatFieldCubitinherited fromCubit. It ships deprecated and is removed in 0.3.0 — useaddListener,subscribeToFields, or the builder widgets.
0.1.2 #
- Bumped
blocto^9.0.0.
0.1.1 #
- Bumped
rxdartto^0.28.0.
0.1.0 #
- Documented the public API in README.md.
0.0.1 #
- Initial version of the library.