Migrating from 0.1.x
leancode_forms 0.1.x to advanced_forms 0.2 — the renames, the behaviour changes that compile but behave differently, and the widget clean-up, in the order to do them.
0.2 keeps the form-building model of 0.1.x — fields, form groups, validators, subforms — and replaces the state-management machinery underneath it. This page is the map; the full migration guide in the repository has every detail and every before/after snippet.
What changed
flutter_blocandrxdartare gone. Fields and forms mix inChangeNotifierand implementValueListenable, both from the Flutter SDK, so the library no longer constrains the app's state-management stack.- Every
*Cubitis anAdvanced*Controller. Most members carry over unchanged. - Lifecycle is synchronous.
void dispose()replacesFuture<void> close(); theDisposablemixin is gone. AdvancedTextFieldControllerowns itsTextEditingControllerandFocusNode, so widgets stop allocating their own.- Validation was rebuilt.
validate()isFuture<bool>and reaches async validators;bool autovalidatebecame a three-memberValidationMode; fourteen defects in the async pipeline were fixed, each with a regression test. - The package was renamed from
leancode_formstoadvanced_forms, and the floor is Flutter 3.19 / Dart 3.3.
Checklist
Update pubspec.yaml. Replace leancode_forms with advanced_forms; remove flutter_bloc, rxdart, and
bloc_test, bloc_presentation or flutter_hooks if forms were the only reason for them. Add provider if you
used BlocProvider.
Rename the classes with the table below, and change every package:leancode_forms/ import.
Replace .state reads with .value, or the fieldValue and error shortcuts.
Move asyncValidator: and asyncValidationDebounce: into asyncValidation: AsyncValidation(validator:, debounce:).
Replace clear() with reset(), and setAutovalidate(bool) with setValidationMode(ValidationMode).
Migrate the widgets: a third child parameter on builders, and delete the TextEditingController plumbing in
favour of controller: field.textController.
Convert close() overrides to dispose(), and addDisposable(...) registrations to explicit cleanup.
Replace stream subscriptions — onValuesChangedStream.listen becomes onValuesChanged.addListener.
Read the behaviour changes below against your code. They compile, and behave differently.
Rename reference
| 0.1.x | 0.2 |
|---|---|
FieldCubit<T, E> | AdvancedFieldController<T, E> |
TextFieldCubit<E> | AdvancedTextFieldController<E> |
BooleanFieldCubit<E> | AdvancedBooleanFieldController<E> |
SingleSelectFieldCubit<V, E> | AdvancedSingleSelectFieldController<V, E> |
MultiSelectFieldCubit<V, E> | AdvancedMultiSelectFieldController<V, E> |
FormGroupCubit | AdvancedFormController |
FieldState<T, E> / FormGroupState | AdvancedFieldState<T, E> / AdvancedFormState |
FieldBuilder<T, E> | AdvancedFieldBuilder<T, E> — builder gains a third child parameter |
cubit.state | controller.value, plus fieldValue and error |
cubit.isClosed | controller.isDisposed |
field.clear() | removed — field.reset() |
asyncValidator:, asyncValidationDebounce: | asyncValidation: AsyncValidation(validator:, debounce:, timeout:, onFailure:, failureToError:) |
bool validate() | Future<bool> validate() — await it |
setAutovalidate(bool), state.autovalidate | setValidationMode(ValidationMode), state.validationMode |
validate(enableAutovalidate: …) | removed — validate() never changes the mode |
form.validateWithAutovalidate() | form.revalidateSync() |
onValuesChangedStream / onStatusChangedStream | onValuesChanged / onStatusChanged — Listenables, no payload |
removeSubform(form, close: …) | removeSubform(form) — detaches only, returns void |
Future<void> close(), addDisposable, Disposable | void dispose() |
BlocBuilder<FormGroupCubit, FormGroupState> | ValueListenableBuilder<AdvancedFormState> |
FieldCubit.stream | AdvancedFieldController.stream — deprecated bridge, removed in 0.3.0 |
Behaviour changes that are not renames
These compile after the renames and behave differently, so nothing points you at them.
validate()is asynchronous and runs the async validators. Every call site needsawait; a barevalidate();statement still compiles and silently ignores the result.autovalidate: trueisValidationMode.onUserInteraction;falseismanual, the default.onUnfocusis new.validate()no longer turns validation on: if you relied on "quiet until the first submit, live afterwards", setonUserInteractionup front.- Only a field the user has edited validates itself. In every mode. Write programmatic values with the new
prefill(value), notsetValue, so a profile fetch does not arm the field. - A throwing async validator no longer hangs the field. It lands on the new
FieldStatus.failedValidation; exhaustiveswitches overFieldStatusneed a new arm. setError(null)clears instead of marking the field invalid, andsetErrorleavesasyncErroralone.reset()keeps the validation mode andreadOnly.form.resetAll()no longer unlocks fields you locked.subscribeToFieldsre-runs the sync validator only, fires on the first value change to any watched field, and never on a status-only change.removeSubformonly detaches. The parent owns every subform it was given and disposes them all.- A disposed controller throws a
StateErrorfromregisterFields,addSubform,removeSubform,setValidationEnabled,subscribeToFieldsandsetValue. - Form state settles synchronously, in the same call stack as the field change — no microtask hop.
setValueclears both errors inmanualmode, and the error goes blank while an async check runs.- The select controllers assert that
selectandaddValueare given one ofoptions, in debug builds. validationErrorsreportserror, sync or async, instead ofvalidationError.
The full guide
explains each with a before/after, and covers migrating custom fields, bloc_test suites and bloc_presentation
events.
After the port
Everything in these docs applies. Start with Validation modes — it is where the mental
model moved the most — and Lifecycle and ownership for the single-owner rule that close()-era
code tends to violate.