mk_forms 1.0.1
mk_forms: ^1.0.1 copied to clipboard
A flexible, reactive, and type-safe Flutter form management library powered by flutter_bloc. Handles minimal to complex forms with ease.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.0.1 - 2026-09-20 #
Added #
MkFormBuilder— Rebuilds fromMkFormState(with optionalformBlocandbuildWhen), mirroringMkFormListener.- All field widgets now expose the parameters of the Flutter widget they wrap, so they can be customized without rebuilding them:
MkTextField,MkEmailField,MkPasswordField: everyTextFieldparameter (inputFormatters,cursor*,autofillHints,contextMenuBuilder,onTap,onEditingComplete,focusNode,buildCounter,undoController, …) exceptcontroller.focusNodemay be supplied (it is never disposed by the widget);enabledcombines with the bloc's state.MkPasswordFieldgainsinitiallyObscured.MkCheckboxField: everyCheckboxListTileparameter (tristate,fillColor,shape,side,controlAffinity, …), plusonChanged,enabled,errorStyle,errorPadding.isErrordefaults to "an error is shown".MkDropdownField: everyDropdownButtonFormFieldparameter that is not aFormFieldmember (selectedItemBuilder,isExpanded,icon,dropdownColor,menuMaxHeight, …), plusenabled.MkRadioGroup: theRadioListTile/Radioparameters at group level,MkRadioOption.secondary,onChanged,enabled,highlightSelected,errorStyle.MkMultiSelectSearchDropdown:InkWell,InputChip, bottom sheet and dialog parameters (chip*,sheet*,dialog*), plus localizable labels (doneLabel,selectAllLabel,clearAllLabel,moreLabelBuilder),showSelectAll,icon,hintStyle,onChanged,enabled.
- Only parameters available in Flutter 3.41 are exposed (the version this package's own code already requires).
Fixed #
MkMultiSelectSearchDropdownonly rendered the first picked item: the modal handed the field its own mutable list, so later picks looked like no-ops to the bloc. It now reports a copy.MkFormBloc/MkStepFormBlocleaked a second, never-closedMkGroupBloc(and one extra listener per control) while building their initial state.MkFormBloc.submit()could runonSubmittwice when called again while validation was still in progress.- Editing a field while async validation was in flight made
submit()silently do nothing. A supersededMkFieldBloc.validate()now resolves to the outcome of the newer validation. - Mapping server errors onto fields inside
onSubmitFailure(or touching a field) no longer wipes the failure state; only real value edits clear it. MkValidators.emailrejected valid addresses with hyphenated domains and accepted trailing garbage, commas anda@b.c@@@. It is now anchored and follows the HTML5 rule.dependsOnno longer runs async validators at construction, no longer validates twice per dependency change, and debounces the async part likechangeValue. Dependents also detach from their dependency onclose(), and group/array dependencies only trigger when their contents actually change.- A throwing async validator no longer shows the raw exception text (hostnames, stack details). The field gets a generic localized message (
MkErrorCodes.asyncValidationFailed, English and Spanish included) and the error is reported throughonError/BlocObserver. isDirtycompares collection values (lists, sets, maps) by contents instead of identity.- Async work finishing after
close()(submit(),validate(),nextStep(),goToStep(),validateStep(),loadInitialData(),reset()) no longer throws "Cannot emit new states after calling close". MkRadioGroup(vertical layout) ignoredMkRadioOption.enabledand the bloc's enabled state visually; disabled options now render as disabled.- The multi-select modal title could overflow next to the Done button; it now ellipsizes.
Performance #
- Bulk operations (
touch,reset,markFormSubmitted,validate) on groups, arrays and forms recompute the aggregate once instead of once per child.submit()on a 1000-field form went from ~315 ms to ~16 ms andreset()from ~105 ms to ~1 ms. - Aggregate flags and nested values are read from cached state instead of re-walking the subtree.
- State listeners are only notified when a control's state actually changed.
MkTextFieldno longer rebuilds on every keystroke when nothing visible changes.- Added
MkFieldBloc.changeValue(value, markTouched: true); checkbox, radio and dropdown widgets use it to emit (and rebuild) once per interaction instead of twice.
Changed #
- Minimum versions corrected to Flutter
>=3.41.0/ Dart>=3.9.0. The code already required them (AutovalidateMode.onUserInteractionIfErrorships in 3.41,RadioGroupand the dropdown'sinitialValuein 3.35); the previous>=3.10.0made older Flutter fail at compile time instead of at version resolution. MkFormScopeis now aStatelessWidgetbuilt onBlocProviderinstead of a customInheritedWidget, with the same two constructors:MkFormScope(create:, lazy:)creates and closes the bloc,MkFormScope.value(value:)exposes an existing one without closing it. Breaking:MkFormScope(formBloc: x)is nowMkFormScope.value(value: x).MkFormScope.ofno longer registers a rebuild dependency (useMkFormBuilder).MkFieldBloc.validate({cascadeDependents}): the parameter is deprecated and ignored; dependents are revalidated when the dependency's value changes.- Upgraded
equatableto^3.0.0. - Dependencies bumped:
bloc ^9.2.1,flutter_bloc ^9.1.1,meta ^1.19.0(dev:bloc_test ^10,mocktail ^1.0.5,flutter_lints ^6). Removed the unusedrxdartdependency. - Tree shaking verified. Unused widgets, validators, the step-form bloc and the Spanish messages are removed from release builds; the package also builds with
flutter build web --wasmand keeps Flutter's icon tree shaking intact (see README, Tree Shaking & Bundle Size).
1.0.0 - 2026-09-19 #
🎉 Initial stable release #
Core Architecture
MkFieldBloc<T>— Reactive field BLoC managing value, sync/async validation, dirty/touched/pristine state, debouncing, and cross-field dependencies.MkFormBloc— Abstract coordinator BLoC managing aggregated validation, submission lifecycle, and automatic submission-error clearing on edit.MkSimpleFormBloc— LightweightMkFormBlocwith an inlineonSubmitclosure — no subclass required for simple forms.MkStepFormBloc— Multi-step wizard BLoC with granular per-step validation gating, multi-route state preservation, and API draft hydration.MkSimpleStepFormBloc— Inline-closure variant ofMkStepFormBloc.MkGroupBloc— Groups named controls into an independently validatable subform.MkArrayBloc<C>— Dynamic array of form controls with add, insert, remove, and move operations.
Validation
- Built-in
MkValidators:required,email,minLength,maxLength,min,max,pattern,match,matchControl,compose,composeAsync. - Async validators with configurable debounce time and automatic stale-request cancellation.
- Cross-field
dependsOn— automatic revalidation when a dependency field changes, without recursion loops. - Server error mapping via
setFieldErrors(Map<String, String>). - Partial-form validation:
validateGroup(name),validateControls([names]),validateStep([step]).
AutovalidateMode Support
AutovalidateMode.always— errors shown immediately, even on pristine fields.AutovalidateMode.onUserInteraction(default) — errors shown after touch or submit attempt.AutovalidateMode.onUnfocus— errors shown after field blur or submit attempt.AutovalidateMode.onUserInteractionIfError— same asonUserInteraction.AutovalidateMode.disabled— errors shown only after submit attempt.- Configurable at
MkFormScopelevel (scope-wide default) or per-field (override). MkFieldState.shouldShowError([AutovalidateMode])for custom widget integration.
Form State & Lifecycle
MkFieldState<T>:value,initialValue,error,isDirty,isTouched,isValidating,isValid,isInvalid,isPure,wasFormSubmitted,showError,shouldShowError([mode]).MkFormState:isValid,isValidating,isDirty,isTouched,isSubmitting,isSuccess,isFailure,submissionError,values.MkStepFormState: extendsMkFormStatewithcurrentStep,totalSteps,completedSteps,isLoadingInitialData,redirectedFromStep,invalidStepIndex.populateValues(data, {markPristine: true})— hydrate fields from API response without triggering premature errors.loadInitialData(loader, {resolveInitialStep})— async draft loading with smart redirect to earliest invalid step.reset()— resets form and all controls to their initial values.
UI Widgets
MkFormScope—InheritedWidgetproviding form bloc and defaultAutovalidateModeto descendants. Context extensions:context.mkForm,context.mkField<T>(name),context.mkGroup(name),context.mkAutovalidateMode.MkFormListener— Side-effect listener for submission status withonSubmitting,onSuccess,onFailurecallbacks.MkFieldBuilder<T>— Fine-grained BlocBuilder for a single field with optionalbuildWhen.MkSubformBuilder— Isolated BlocBuilder for a namedMkGroupBloc; changes in one subform never rebuild siblings.MkTextField— Material text field with automatic focus/blur tracking and controller sync.MkEmailField— Pre-configured email keyboard, autofill, and format validation.MkPasswordField— Obscured input with show/hide eye toggle.MkCheckboxField— Checkbox with error label and custom label widget.MkRadioGroup<T>— Vertical/horizontal/wrap layout; supports customoptionBuilder.MkDropdownField<T>— Single-selection dropdown.MkMultiSelectSearchDropdown<T>— Multi-selection with live search, chips, select-all/clear, bottom-sheet or dialog.
Internationalization (i18n)
MkFormLocalizations.delegate— standardflutter_localizationsintegration.- Built-in English (
DefaultEnglishMessages) and Spanish (SpanishMessages) message sets. - Global override:
MkFormLocalizations.current = MyMessages(). - Scoped override:
MkLocalizationScope(messages: ..., child: ...). - Dynamic runtime locale switching — validation errors update instantly without UI jank.
MkErrorCodes— error code constants for i18n message resolution.
Testing
- 68 unit and widget tests covering field blocs, form lifecycle, step forms, subform validation, array blocs, validators, localization, all field widgets, and all
AutovalidateModevariants.