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_bloc and rxdart are gone. Fields and forms mix in ChangeNotifier and implement ValueListenable, both from the Flutter SDK, so the library no longer constrains the app's state-management stack.
  • Every *Cubit is an Advanced*Controller. Most members carry over unchanged.
  • Lifecycle is synchronous. void dispose() replaces Future<void> close(); the Disposable mixin is gone.
  • AdvancedTextFieldController owns its TextEditingController and FocusNode, so widgets stop allocating their own.
  • Validation was rebuilt. validate() is Future<bool> and reaches async validators; bool autovalidate became a three-member ValidationMode; fourteen defects in the async pipeline were fixed, each with a regression test.
  • The package was renamed from leancode_forms to advanced_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 subscriptionsonValuesChangedStream.listen becomes onValuesChanged.addListener.

Read the behaviour changes below against your code. They compile, and behave differently.

Rename reference

0.1.x0.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>
FormGroupCubitAdvancedFormController
FieldState<T, E> / FormGroupStateAdvancedFieldState<T, E> / AdvancedFormState
FieldBuilder<T, E>AdvancedFieldBuilder<T, E>builder gains a third child parameter
cubit.statecontroller.value, plus fieldValue and error
cubit.isClosedcontroller.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.autovalidatesetValidationMode(ValidationMode), state.validationMode
validate(enableAutovalidate: …)removed — validate() never changes the mode
form.validateWithAutovalidate()form.revalidateSync()
onValuesChangedStream / onStatusChangedStreamonValuesChanged / onStatusChangedListenables, no payload
removeSubform(form, close: …)removeSubform(form) — detaches only, returns void
Future<void> close(), addDisposable, Disposablevoid dispose()
BlocBuilder<FormGroupCubit, FormGroupState>ValueListenableBuilder<AdvancedFormState>
FieldCubit.streamAdvancedFieldController.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 needs await; a bare validate(); statement still compiles and silently ignores the result.
  • autovalidate: true is ValidationMode.onUserInteraction; false is manual, the default. onUnfocus is new. validate() no longer turns validation on: if you relied on "quiet until the first submit, live afterwards", set onUserInteraction up front.
  • Only a field the user has edited validates itself. In every mode. Write programmatic values with the new prefill(value), not setValue, 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; exhaustive switches over FieldStatus need a new arm.
  • setError(null) clears instead of marking the field invalid, and setError leaves asyncError alone.
  • reset() keeps the validation mode and readOnly. form.resetAll() no longer unlocks fields you locked.
  • subscribeToFields re-runs the sync validator only, fires on the first value change to any watched field, and never on a status-only change.
  • removeSubform only detaches. The parent owns every subform it was given and disposes them all.
  • A disposed controller throws a StateError from registerFields, addSubform, removeSubform, setValidationEnabled, subscribeToFields and setValue.
  • Form state settles synchronously, in the same call stack as the field change — no microtask hop.
  • setValue clears both errors in manual mode, and the error goes blank while an async check runs.
  • The select controllers assert that select and addValue are given one of options, in debug builds.
  • validationErrors reports error, sync or async, instead of validationError.

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.

On this page