view_model 0.9.1
view_model: ^0.9.1 copied to clipboard
Everything is ViewModel. Enjoy automatic lifecycle management, prevent memory leaks, and share state effortlessly. Simple, lightweight, and powerful.
0.9.1 #
- update dependencies
0.9.0 #
π Major Update: Introducing ViewModelProvider & Code Generator
π ViewModelProvider: Simpler, Cleaner, Better Replaces the verbose Factory pattern with a declarative, type-safe provider system.
Before (Factory pattern):
class CounterViewModelFactory extends ViewModelFactory<CounterViewModel> {
@override
CounterViewModel build() => CounterViewModel();
}
final vm = watchViewModel(factory: CounterViewModelFactory());
After (Provider pattern):
/// auto generated provider for CounterViewModel
final counterProvider = ViewModelProvider<CounterViewModel>(
builder: () => CounterViewModel(),
);
final vm = vef.watch(counterProvider);
With Arguments:
/// auto generated provider for UserViewModel
final userProvider = ViewModelProvider.arg<UserViewModel, String>(
builder: (userId) => UserViewModel(userId),
key: (userId) => 'user-$userId',
);
final vm = vef.watch(userProvider('user-123'));
π€ Code Generator: Zero Boilerplate #
Introducing view_model_generator - automatically generate ViewModelProvider definitions from annotations.
Installation:
dependencies:
view_model: ^latest
dev_dependencies:
build_runner: ^latest
view_model_generator: ^latest
Usage:
import 'package:view_model_generator/view_model_generator.dart';
part 'counter_view_model.vm.dart';
@genProvider
class CounterViewModel extends ViewModel {
int count = 0;
void increment() => update(() => count++);
}
Run generator:
dart run build_runner build
Generated code:
// counter_view_model.vm.dart
final counterProvider = ViewModelProvider<CounterViewModel>(
builder: () => CounterViewModel(),
);
The generator supports ViewModels with up to 4 constructor parameters and automatically generates the appropriate ViewModelProvider.argX variant.
Package: https://pub.dev/packages/view_model_generator
π New Unified API: vef.watch & vef.read #
Everything is Vef - A unified, consistent API for accessing ViewModels.
New Recommended API:
// Watch (reactive)
final vm = vef.watch(counterProvider);
// Read (non-reactive)
final vm = vef.read(counterProvider);
// Watch cached by key/tag
final vm = vef.watchCached<UserViewModel>(key: 'user-123');
final vm = vef.readCached<UserViewModel>(tag: 'current-user');
Legacy API (still supported):
// Old API still works for backward compatibility
final vm = vef.watchViewModel(factory: CounterViewModelFactory());
final vm = vef.readViewModel(factory: CounterViewModelFactory());
Note: While
watchViewModelandreadViewModelare still supported, we recommend migrating to the newvef.watchandvef.readAPI withViewModelProviderfor better type safety and less boilerplate.
π Everything is Vef #
Vef is the core abstraction of the view_model library, responsible for managing ViewModel lifecycle and dependency injection. WidgetMixin is essentially just a wrapper around WidgetVef.
This means you can use ViewModel in any Dart class, independent of Widgets.
Custom Vef Example:
class StartTaskVef with Vef {
Future<void> runStartupTasks() async {
final authVM = vef.watch(authProvider);
await authVM.checkLoginStatus();
final configVM = vef.watch(configProvider);
await configVM.loadRemoteConfig();
}
@override
void dispose() {
super.dispose();
// Clean up all watched ViewModels
}
}
Use cases:
- Pure Dart Tests: Test ViewModel interactions without
testWidgets - Startup Tasks: Execute initialization logic before any Widget is rendered
See Custom Vef Documentation for details.
π§ Other Changes #
- Renamed
ViewModelPauseProvidertoVefPauseProviderfor consistency - Improved type inference for
ViewModelProvider.argXvariants
0.8.4 #
- Update docs about design philosophy
Everything is ViewModel #
We redefine the "ViewModel" not as a specific MVVM component, but as a Specialized Manager Container equipped with lifecycle awareness.
1. Widget-Centric Architecture In a Flutter App, every action revolves around Pages and Widgets. No matter how complex the logic is, the ultimate consumer is always a Widget. Therefore, binding the Manager's lifecycle directly to the Widget tree is the most logical and natural approach.
2. One Concept, Flexible Scopes You don't need to distinguish between "Services", "Controllers", or "Stores". It's all just a ViewModel. The difference is only where you attach it:
- Global: Attach to the top-level
AppMain. It lives as long as the App (Singleton). - Local: Attach to a Page. It follows the page's lifecycle automatically.
- Shared: Use a unique
key(e.g., ProductID) to share the exact same instance across different Widgets.
3. Seamless Composition & Decoupling
ViewModels can directly depend on and read other ViewModels internally (e.g., a UserVM reading a NetworkVM). However, the ViewModel itself remains Widget-Agnosticβit holds state and logic but does not know about the Widget's existence or hold a BuildContext.
4. Out-of-the-Box Simplicity Compared to GetIt (which requires manual binding glue code) or Riverpod (which involves complex graph concepts), this approach is strictly pragmatic. It provides automated lifecycle management and dependency injection immediately, with zero boilerplate.
0.8.3 #
- Fix docs
0.8.2 #
- Update docs
0.8.1 #
- Fix: Custom
VefPauseProviderwas not working properly when added late, causing pause to fail.
0.8.0 #
- BREAKING CHANGE: Reworked the
Vefpause/resume lifecycle to a more robust and extensible provider-based architecture.- Default providers
PageRoutePauseProvider,TickerModePauseProviderandAppPauseProviderhandle automatic pausing for route and app app lifecycle events and tickMode. - Added
ManualVefPauseProviderfor easy manual control in custom UI scenarios (e.g.,TabBarView). - For details on the new API and migration, see the Pause/Resume Lifecycle Documentation.
- Default providers
// register [ViewModel.routeObserver] to navigatorObservers.
class App {
Widget build(context) {
return MaterialApp(
navigatorObservers: [ViewModel.routeObserver],
// ... other properties
);
}
}
- Fix Devtool
- Added support for
ViewModelStatelessMixinonStatelessWidget. but prefer usingViewModelStateMixinonStatefulWidget.
class MyWidget extends StatelessWidget with ViewModelStatelessMixin {
late final viewModel = watchViewModel<MyViewModel>(
factory: MyViewModelFactory(),
);
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return Text('Hello World ${viewModel}');
}
}
- Add
StateViewModelValueWatcherto watch value changes onStateViewModel.
class MyWidget extends State with ViewModelStateMixin {
const MyWidget({super.key});
late final MyViewModel stateViewModel;
@override
void initState() {
super.initState();
stateViewModel = readViewModel<MyViewModel>(
factory: MyViewModelFactory(),
);
}
@override
Widget build(BuildContext context) {
// Watch value changes on `stateViewModel` and rebuild only when `name` or `age` changes.
return StateViewModelValueWatcher<MyViewModel>(
stateViewModel: stateViewModel,
selectors: [(state) => state.name, (state) => state.age],
builder: (state) {
return Text('Name: \${state.name}, Age: \${state.age}');
},
);
}
}
0.7.0 #
RouteAware Auto Pause (delay rebuilds when page is paused) #
- can manually control pause/resume via
viewModelVisibleListenersexposed byViewModelStateMixin. CallviewModelVisibleListeners.onPause()when the page is covered, andviewModelVisibleListeners.onResume()when it becomes visible again. Wire these methods to your ownRouteObserveror any visibility mechanism.
Example:
class _MyPageState extends State<MyPage> with ViewModelStateMixin<MyPage>, RouteAware {
void didPushNext() {
viewModelVisibleListeners.onPause();
}
void didPopNext() {
viewModelVisibleListeners.onResume(); // triggers one refresh
}
}
-
BreakingChange: Rename
ViewModelWatchertoViewModelBuilder -
Add
ViewModel#update, we often forget callingnotifylistenr()await update(() async { await repository.save(data); _counter++; }); -
Add
StateViewModel#listenStateSelectto listen value diff. -
Add
ObserverBuilderfamily of widgets for fine-grained, reactive UI updates. doc
// shareKey for share value cross any widget
final observable = ObservableValue<int>(0, shareKey: share);
observable.value = 20;
ObserverBuilder<int>(observable: observable,
builder: (v) {
return Text(v.toString());
},
)
// observe 2 value
ObserverBuilder2<int>(
observable1: observable1,
observable2: observable2,
builder: (v1,v2) {
//
},
)
// observe 3 value
ObserverBuilder3<int>(
observable1: observable1,
observable2: observable2,
observable3: observable3,
builder: (v1,v2,v3) {
},
)
0.6.0 #
- Add
ViewModelBuilderandCachedViewModelBuilderwidgets for binding and listening without mixing inViewModelStateMixin; Naming: useshareKeyinCachedViewModelBuilderto avoid confusion with widgetKey.
// Example: Using ViewModelBuilder without mixing ViewModelStateMixin
ViewModelBuilder<MySimpleViewModel>
(
factory: MySimpleViewModelFactory(),
builder: (vm) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(vm.message),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => vm.updateMessage("Message Updated!"),
child: const Text('Update Message'),
),
],
);
},
)
// Example: Using CachedViewModelBuilder to bind to an existing instance
CachedViewModelBuilder<MySimpleViewModel>
(
shareKey: "shared-key", // or: tag: "shared-tag"
builder: (vm) {
return Row(
children: [
Expanded(child: Text(vm.message)),
IconButton(
onPressed: () => vm.incrementCounter(),
icon: const Icon(Icons.add),
),
],
);
},
)
0.5.0 #
- Breaking Change & API Refinement: Major overhaul of ViewModel access
methods to clarify responsibilities and improve predictability.
watchViewModel/readViewModel:- Now primarily responsible for creating new ViewModel instances.
- The
factoryparameter is now mandatory. - Behavior depends on the provided
factory:- If the
factoryincludes akey, the instance is created and cached (or retrieved if already cached). - If the
factoryhas nokey, a new, non-shared instance is created every time.
- If the
- New Methods for Cached Access:
- Introduced
watchCachedViewModelandreadCachedViewModelto explicitly find existing, cached ViewModel instances bykeyortag. - Introduced
maybeWatchCachedViewModelandmaybeReadCachedViewModelfor safely accessing cached instances without throwing errors if not found.
- Introduced
- Migration Guide:
- To create/watch a new instance: Continue using
watchViewModelbut you must provide afactory. - To find an existing instance: Replace
watchViewModel(key: ...)withwatchCachedViewModel(key: ...)orreadCachedViewModel(key: ...).
- To create/watch a new instance: Continue using
- support ViewModel-to-ViewModel Access
- Breaking change: The
keyparameter inwatchViewModel,readViewModel, andViewModel.readhas been changed fromString?toObject?. This allows for the use of custom objects as keys, but requires proper implementation of==andhashCodefor custom key objects.
ViewModels can access other ViewModels using readViewModel and watchViewModel:
readViewModel: Access another ViewModel without reactive connectionwatchViewModel: Create reactive dependency - automatically notifies when the watched ViewModel changes
When a ViewModel (the HostVM ) accesses another ViewModel (the SubVM ) via watchViewModel , the framework automatically binds the SubVM 's lifecycle to the HostVM 's UI observer (i.e., the State object of the StatefulWidget ).
This means both the SubVM and the HostVM are directly managed by the lifecycle of the same State object. When this State object is disposed, if neither the SubVM nor the HostVM has other observers, they will be disposed of together automatically.
This mechanism ensures clear dependency relationships between ViewModels and enables efficient, automatic resource management.
class UserProfileViewModel extends ViewModel {
void loadData() {
// One-time access without listening
final authVM = watchCachedViewModel<AuthViewModel>();
if (authVM?.isLoggedIn == true) {
_fetchProfile(authVM!.userId);
}
}
void setupReactiveAuth() {
// Reactive access - auto-updates when auth changes
final authVM = watchCachedViewModel<AuthViewModel>();
// This ViewModel will be notified when authVM changes
}
void manualListening() {
final authVM = watchCachedViewModel<AuthViewModel>();
// You can also manually listen to any ViewModel
authVM?.listen(() {
// Custom listener logic
_handleAuthChange(authVM);
});
}
}
0.4.7 #
- fix
ViewModel.read
0.4.6 #
- The
view_modelpackage includes a powerful DevTools extension that provides real-time monitoring and debugging capabilities for your ViewModels during development. - create
devtools_options.yamlin root directory of project.
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
- view_model: true

- Breaking change: rename
initConfigtoinitialize
0.4.5 #
- Add
ViewModelProviderfor convenient and generic ViewModel factory creation.
0.4.4 #
- Add
ViewModel.maybeRead
0.4.3 #
- Add
maybeWatchViewModelandmaybeReadViewModel - update
watchViewModelfind logic
VM watchViewModel<VM extends ViewModel>({
ViewModelFactory<VM>? factory,
Object? key,
Object? tag,
});
| Parameter Name | Type | Optional | Description |
|---|---|---|---|
factory |
ViewModelFactory<VM>? |
β | Provides the construction method for the ViewModel. Optional; if an |
| existing instance is not found in the cache, it will be used to create a new | |||
| one. | |||
key |
String? |
β | Specifies a unique key to support sharing the same ViewModel instance. |
| First, it tries to find an instance with the same key in the cache. | |||
tag |
Object? |
β | Add a tag for ViewModel instance. get tag by viewModel.tag. and it's |
used by find ViewModel by watchViewModel(tag:tag). |
π Lookup Logic Priority (Important)
The internal lookup and creation logic of watchViewModel is as follows
(executed in priority order):
- If a key is passed in:
- First, attempt to find an instance with the same key in the cache.
- If a factory exists, use the factory to get a new instance.
- If no factory is found and no instance is found, an error will be thrown.
- If a tag is passed in, attempt to find the latest created instance which has the same tag in the cache.
- If nothing passed in, attempt to find the latest created instance of this type in the cache.
β οΈ If no ViewModel instance of the specified type is found, an error will be thrown. Ensure that the ViewModel has been correctly created and registered before use.
0.4.2 #
- Support find existing ViewModel by tag
set tag in ViewModelFactory.tag():
class MyViewModelFactory extends ViewModelFactory<MyViewModel> {
@override
Object? tag() {
return 'tag';
}
}
find existing ViewModel by tag:
late final MyViewModel viewModel;
@override
void initState() {
super.initState();
viewModel = watchViewModel<MyViewModel>(tag: 'tag');
}
0.4.1 #
Breaking change:
- Use
recycleViewModelinstead ofrefreshViewModel.
0.4.0 #
Breaking change:
-
Use
ViewModelinstead ofStatelessViewModel. -
Use
StateViewModelinstead ofViewModel. -
Use either
watchViewModelorreadViewModelinstead ofgetViewModel/requireExistingViewModel. -
Use
StateViewModel.listenStateinstead ofViewModel.listen. -
Use
ViewModel.listeninstead ofViewModel.addListener. -
Support
ViewModel.read<T>to read existing view model globally.
0.3.0 #
- transfer to https://github.com/lwj1994/flutter_view_model. thank to Miolin