mvvm_framework 1.0.0
mvvm_framework: ^1.0.0 copied to clipboard
A Flutter package for implementing the MVVM architectural pattern with ease, built as an extension of Flutter Bloc.
Change Log #
All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines.
1.0.0 - 2026-04-13 #
Major architectural simplification: introduces the ViewListener pattern, type-based error dispatch via GenericErrorManager, and numerous correctness and DX improvements.
Breaking Changes & Migration #
1. Model<T> — self-referential generic
copyWithViewStatus now returns T directly, eliminating as T casts. Add the type argument to every Model implementation.
// Before
class MyModel implements Model { ... }
// After
class MyModel implements Model<MyModel> { ... }
For Freezed models only the implements clause changes — no re-generation needed:
// Before
@freezed
abstract class CounterModel with _$CounterModel implements Model { ... }
// After
@freezed
abstract class CounterModel with _$CounterModel implements Model<CounterModel> { ... }
2. MVVM widget — builder pattern
view/viewModel params replaced by vmBuilder/viewBuilder; model renamed to externalModel; new optional listenWhen/buildWhen params added.
// Before
CounterPage({required CounterModel model})
: super(model: model, vmBuilder: ...);
// After
CounterPage({required CounterModel model})
: super(externalModel: model, vmBuilder: ...);
3. ViewListener<M, VM> — new side-effect pattern
listener() removed from ViewModel. Side effects now live in a ViewListener subclass passed as a factory to MVVM; the framework calls .execute() automatically. Override typed hooks instead of a manual switch over ViewStatus:
| Hook | Triggered on |
|---|---|
onSuccess() |
ViewStatusSuccess |
onFailure(ViewStatusError) |
ViewStatusFailure (after error dispatch) |
onInitial() |
ViewStatusInitial |
onLoading() |
ViewStatusLoading |
// Before
listener: (ctx, vm, state) {
CounterListener(viewContext: ViewContext(ctx), viewModel: vm, state: state).execute();
},
// After
listener: (ctx, vm, state) => CounterListener(
viewContext: ViewContext(ctx),
viewModel: vm,
state: state,
),
// Before
@override
void execute() {
if (state.viewStatus.isSuccess) {
viewContext.navigator.pop();
}
}
// After
@override
Future<void> onSuccess() async {
await viewContext.navigator.maybePop();
}
4. ViewStatus — no more Freezed
Plain abstract class with four concrete subclasses. ViewStatusError.message replaced by ViewStatusError.cause (type Object). The viewStatusFailure constant is removed.
// Before
expect(failure.error.message, 'something');
// After
expect(failure.error.cause, isA<NetworkException>());
5. ViewContext — simplified construction
ViewContext(context) now takes only a BuildContext; others is optional (defaults to ViewEmptyAdditionalContext()). showDialog and showModalBottomSheet are regular instance methods.
6. ViewModel — API changes
viewContextproperty removed; useViewListenerfor all UI interactions.errorHandlersis now@visibleForTesting; usedispatchErrorHandler(Type)fromViewListenerinstead.didUpdateWidgetreturn type:void→Future<void>.onInitanddidUpdateWidgetare now wrapped insafeExecuteby the framework automatically.safeExecuteonErrorcallback:void Function(ViewStatusError)→Future<void> Function(ViewStatusError).
// Before
onError: (_) { doSomething(); }
// After
onError: (_) async { doSomething(); }
7. ErrorPresenter<T> — added StackTrace? parameter
Add StackTrace? stackTrace as the second parameter to every GenericErrorManager.register<T>() callback, errorFallback, and setFallback lambda.
// New signature
Future<void> Function(T cause, StackTrace? stackTrace, ViewContext viewContext)
8. ConfigMVVM.initialize() — onGenericError is now async
Signature changed from void Function(...) to Future<void> Function(...); the previous signature silently discarded async work.
New Features #
safeExecute(withLoading: true)— auto-emitsviewStatusLoadingbefore the action; eliminates manualemit(state.copyWithViewStatus(viewStatusLoading))calls.
// Before
Future<void> load() async {
emit(state.copyWithViewStatus(viewStatusLoading));
await safeExecute(_fetch);
}
// After
Future<void> load() => safeExecute(_fetch, withLoading: true);
subscribeStream<T>(stream, onData:, onError:)— managed stream subscription; auto-cancelled inclose(), noStreamSubscriptionfield needed.
// Before
late StreamSubscription<Location> _sub;
@override
Future<void> onInit() async {
_sub = locationStream.listen(_onLocation);
}
@override
Future<void> close() async {
await _sub.cancel();
await super.close();
}
// After
@override
Future<void> onInit() async {
subscribeStream(locationStream, onData: _onLocation);
}
-
ViewModel.onDispose()— lifecycle hook called just before close; override to release controllers, timers, or other resources. -
GenericErrorManager— centralized, type-based error dispatch exported frommvvm_framework.dart.register<T>(handler)— per-exception-type handler.setFallback(handler)— catch-all override (default: basicAlertDialog).reset()— test helper; clears all handlers and restores defaults.
Dispatch priority (lowest → highest):
- Default
AlertDialogfallback GenericErrorManager.setFallback(...)/ConfigMVVM.initialize(errorFallback: ...)GenericErrorManager.register<T>(...)ViewModel.registerErrorHandler<T>(...)(highest)
GenericErrorManager.register<NetworkException>(
(e, stackTrace, ctx) => ctx.scaffoldMessenger.showSnackBar(MySnackBar(e.message)).closed,
);
ConfigMVVM.initialize(
errorFallback: (cause, stackTrace, ctx) => MyErrorDialog(ctx.context, cause.toString()).show(),
);
-
ConfigMVVMimprovements:errorFallbackparameter,isInitializedgetter, double-initassertguard, andreset()now also resetsGenericErrorManager. -
ViewStatusErrorvalue equality:==/hashCodeon bothViewStatusErrorandViewStatusFailure;toString()includes the captured stack trace for easier debugging.
Bug Fixes #
MVVM.didUpdateWidgetnow callsViewModel.didUpdateWidgetonly whenexternalModelis non-null and has actually changed (previously fired on every parent rebuild).ViewContextis no longer constructed eagerly on every state change when a customlistenerfactory is provided.- Removed spurious unused
dart:uiimport fromview_model.dart.