view_model Skill
Use this skill when tasks involve Flutter view_model architecture, migration, bug fixing, performance tuning, or feature implementation.
Source of truth
- Full reference (embedded in this skill):
references/README_FULL_EN.md
references/README_FULL_ZH.md
- Upstream source in repo:
packages/view_model/README.md
packages/view_model/README_ZH.md
- Skill-local examples:
examples/counter_example.dart, examples/state_view_model_example.dart, examples/sharing_example.dart
If examples conflict with README, follow README.
Full-reference loading policy
- For implementation/refactor/debug tasks, read
references/README_FULL_EN.md first.
- For Chinese responses or terminology checks, also read
references/README_FULL_ZH.md.
- For trivial requests (single API clarification), you may use this SKILL summary first, then open full reference only if uncertain.
- If embedded reference and upstream README diverge, treat upstream as latest truth and sync the embedded reference.
- Keep
references/README_FULL_*.md as real files inside the skill package; do not replace them with symlinks to outside paths.
Trigger phrases
Use this skill for requests like:
- "用 view_model 写/改状态管理"
- "watch/read 有什么区别"
- "ViewModelSpec 怎么做共享/单例"
- "StateViewModel / listenStateSelect / ValueWatcher"
- "生命周期、自动销毁、pause/resume"
- "
@GenSpec 或 view_model_generator"
Core model (must stay accurate)
- Architecture is type-keyed instance registry + binding-based reference counting.
- Two base mixins:
with ViewModel: managed instance (lifecycle + notify + DI access).
with ViewModelBinding: binding host (watch/read/listen/recycle APIs).
- Widget mixins are wrappers over
ViewModelBinding:
ViewModelStateMixin (recommended default for widgets).
ViewModelStatelessMixin (lightweight, but has multi-mount caveat).
Implementation workflow
- Choose ViewModel style
with ViewModel: mutable fields + update/notifyListeners.
StateViewModel<T>: immutable state + setState, supports state diff/selective listeners.
ChangeNotifierViewModel: only when extending ChangeNotifier behavior is required.
- Define
ViewModelSpec
ViewModelSpec<T>(builder: ...) for no args.
ViewModelSpec.arg/arg2/arg3/arg4 for parameterized construction.
- Use
key for shared instance identity.
- Use
tag for grouped lookup.
- Use
aliveForever: true only for real process-lifetime singletons.
- Integrate with host
- Widget page:
State<T> with ViewModelStateMixin.
- Simple widget case:
StatelessWidget with ViewModelStatelessMixin.
- No-custom-state option:
ViewModelBuilder<T>(spec, builder: ...).
- Cached-only builder option:
CachedViewModelBuilder<T>(shareKey: ... | tag: ..., builder: ...).
- Non-widget classes (bootstrap/service/test):
with ViewModelBinding and call dispose() manually when done.
- Choose access API correctly
watch(spec): create/get + bind + listen (reactive rebuild/onUpdate).
read(spec): create/get + bind, no listener.
watchCached/readCached: lookup existing instance only (no creation).
maybeWatchCached/maybeReadCached: null-safe cached lookup.
watchCachesByTag/readCachesByTag: batch tag lookup.
listen/listenState/listenStateSelect: side-effect listeners, auto-cleaned on binding dispose.
recycle(vm): force unbind all and dispose; next watch/read gets fresh instance.
- Handle dependencies and sharing
- In a ViewModel,
viewModelBinding is available via Zone from parent binding.
- ViewModel-to-ViewModel calls (
read/watch/listen) are part of the same binding lifecycle chain.
- Without
key: per-binding isolated instance.
- With same
key: cross-binding shared instance.
- Static lookup (
ViewModel.readCached, ViewModel.maybeReadCached) is lookup-only (no bind, no create).
- Lifecycle and cleanup
- Lifecycle hooks:
onCreate, onBind, onUnbind, onDispose.
- Prefer
addDispose(() { ... }) for subscriptions/controllers/stream cleanup.
- Auto-dispose occurs when handle
bindingIds becomes empty and aliveForever is false.
- Performance and visibility
- For route-based pause/resume, register:
MaterialApp(navigatorObservers: [ViewModel.routeObserver])
- Built-in pause providers: route cover, ticker mode, app lifecycle.
- Use
StateViewModelValueWatcher for selector-level rebuilds (usually pair with read, not watch).
ObservableValue + ObserverBuilder(1/2/3) for lightweight reactive values; same shareKey means shared underlying state.
- App-level setup
- Call
ViewModel.initialize(...) once at app startup (subsequent calls are ignored).
- Configure
ViewModelConfig when needed:
isLoggingEnabled
equals (state equality strategy)
onListenerError
onDisposeError
- If using
equals: (a, b) => a == b, ensure state classes implement == and hashCode.
- Testing and mocking
- Prefer pure Dart unit tests with
ViewModelBinding() (no testWidgets required for many cases).
- Always
binding.dispose() in teardown.
- For spec override:
spec.setProxy(...) and spec.clearProxy().
- Code generation (optional)
- Annotate with
@GenSpec, add part '*.vm.dart', then run dart run build_runner build.
- Generator creates
xxxViewModelSpec and supports up to 4 constructor args.
Do/Don't checklist
Do:
- Keep
watch for reactive UI, read for imperative actions.
- Set explicit
key whenever instance sharing is a requirement.
- Dispose non-widget bindings explicitly.
- Use
listenStateSelect for side effects on selected state fields.
Don't:
- Claim
read is "non-binding" (it still binds and affects lifecycle).
- Use cached APIs expecting auto-create behavior.
- Overuse
aliveForever for page-scoped state.
- Forget
ViewModel.routeObserver when relying on route pause behavior.
Response pattern for implementation requests
When generating code for users:
- Prefer complete, runnable snippets with:
- imports
- ViewModel class
- Spec declaration
- widget/binding usage
- disposal/setup notes
- State why
watch or read was chosen.
- If introducing sharing, show explicit
key and lifecycle implications.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: view-model3description: Build or refactor Flutter state management with the view_model package, including ViewModel/ViewModelBinding mixins, ViewModelSpec sharing, watch/read semantics, lifecycle, pause-resume, testing, and code generation. Use when this capability is needed.4---56# view_model Skill78Use this skill when tasks involve Flutter `view_model` architecture, migration, bug fixing, performance tuning, or feature implementation.910## Source of truth1112- Full reference (embedded in this skill):13 - `references/README_FULL_EN.md`14 - `references/README_FULL_ZH.md`15- Upstream source in repo:16 - `packages/view_model/README.md`17 - `packages/view_model/README_ZH.md`18- Skill-local examples: `examples/counter_example.dart`, `examples/state_view_model_example.dart`, `examples/sharing_example.dart`1920If examples conflict with README, follow README.2122## Full-reference loading policy2324- For implementation/refactor/debug tasks, read `references/README_FULL_EN.md` first.25- For Chinese responses or terminology checks, also read `references/README_FULL_ZH.md`.26- For trivial requests (single API clarification), you may use this SKILL summary first, then open full reference only if uncertain.27- If embedded reference and upstream README diverge, treat upstream as latest truth and sync the embedded reference.28- Keep `references/README_FULL_*.md` as real files inside the skill package; do not replace them with symlinks to outside paths.2930## Trigger phrases3132Use this skill for requests like:33- "用 view_model 写/改状态管理"34- "watch/read 有什么区别"35- "ViewModelSpec 怎么做共享/单例"36- "StateViewModel / listenStateSelect / ValueWatcher"37- "生命周期、自动销毁、pause/resume"38- "`@GenSpec` 或 view_model_generator"3940## Core model (must stay accurate)4142- Architecture is type-keyed instance registry + binding-based reference counting.43- Two base mixins:44 - `with ViewModel`: managed instance (lifecycle + notify + DI access).45 - `with ViewModelBinding`: binding host (watch/read/listen/recycle APIs).46- Widget mixins are wrappers over `ViewModelBinding`:47 - `ViewModelStateMixin` (recommended default for widgets).48 - `ViewModelStatelessMixin` (lightweight, but has multi-mount caveat).4950## Implementation workflow51521. Choose ViewModel style53- `with ViewModel`: mutable fields + `update`/`notifyListeners`.54- `StateViewModel<T>`: immutable state + `setState`, supports state diff/selective listeners.55- `ChangeNotifierViewModel`: only when extending `ChangeNotifier` behavior is required.56572. Define `ViewModelSpec`58- `ViewModelSpec<T>(builder: ...)` for no args.59- `ViewModelSpec.arg/arg2/arg3/arg4` for parameterized construction.60- Use `key` for shared instance identity.61- Use `tag` for grouped lookup.62- Use `aliveForever: true` only for real process-lifetime singletons.63643. Integrate with host65- Widget page: `State<T> with ViewModelStateMixin`.66- Simple widget case: `StatelessWidget with ViewModelStatelessMixin`.67- No-custom-state option: `ViewModelBuilder<T>(spec, builder: ...)`.68- Cached-only builder option: `CachedViewModelBuilder<T>(shareKey: ... | tag: ..., builder: ...)`.69- Non-widget classes (bootstrap/service/test): `with ViewModelBinding` and call `dispose()` manually when done.70714. Choose access API correctly72- `watch(spec)`: create/get + bind + listen (reactive rebuild/`onUpdate`).73- `read(spec)`: create/get + bind, no listener.74- `watchCached/readCached`: lookup existing instance only (no creation).75- `maybeWatchCached/maybeReadCached`: null-safe cached lookup.76- `watchCachesByTag/readCachesByTag`: batch tag lookup.77- `listen/listenState/listenStateSelect`: side-effect listeners, auto-cleaned on binding dispose.78- `recycle(vm)`: force unbind all and dispose; next `watch/read` gets fresh instance.79805. Handle dependencies and sharing81- In a ViewModel, `viewModelBinding` is available via Zone from parent binding.82- ViewModel-to-ViewModel calls (`read/watch/listen`) are part of the same binding lifecycle chain.83- Without `key`: per-binding isolated instance.84- With same `key`: cross-binding shared instance.85- Static lookup (`ViewModel.readCached`, `ViewModel.maybeReadCached`) is lookup-only (no bind, no create).86876. Lifecycle and cleanup88- Lifecycle hooks: `onCreate`, `onBind`, `onUnbind`, `onDispose`.89- Prefer `addDispose(() { ... })` for subscriptions/controllers/stream cleanup.90- Auto-dispose occurs when handle `bindingIds` becomes empty and `aliveForever` is false.91927. Performance and visibility93- For route-based pause/resume, register:94 - `MaterialApp(navigatorObservers: [ViewModel.routeObserver])`95- Built-in pause providers: route cover, ticker mode, app lifecycle.96- Use `StateViewModelValueWatcher` for selector-level rebuilds (usually pair with `read`, not `watch`).97- `ObservableValue` + `ObserverBuilder(1/2/3)` for lightweight reactive values; same `shareKey` means shared underlying state.98998. App-level setup100- Call `ViewModel.initialize(...)` once at app startup (subsequent calls are ignored).101- Configure `ViewModelConfig` when needed:102 - `isLoggingEnabled`103 - `equals` (state equality strategy)104 - `onListenerError`105 - `onDisposeError`106- If using `equals: (a, b) => a == b`, ensure state classes implement `==` and `hashCode`.1071089. Testing and mocking109- Prefer pure Dart unit tests with `ViewModelBinding()` (no `testWidgets` required for many cases).110- Always `binding.dispose()` in teardown.111- For spec override: `spec.setProxy(...)` and `spec.clearProxy()`.11211310. Code generation (optional)114- Annotate with `@GenSpec`, add `part '*.vm.dart'`, then run `dart run build_runner build`.115- Generator creates `xxxViewModelSpec` and supports up to 4 constructor args.116117## Do/Don't checklist118119Do:120- Keep `watch` for reactive UI, `read` for imperative actions.121- Set explicit `key` whenever instance sharing is a requirement.122- Dispose non-widget bindings explicitly.123- Use `listenStateSelect` for side effects on selected state fields.124125Don't:126- Claim `read` is "non-binding" (it still binds and affects lifecycle).127- Use cached APIs expecting auto-create behavior.128- Overuse `aliveForever` for page-scoped state.129- Forget `ViewModel.routeObserver` when relying on route pause behavior.130131## Response pattern for implementation requests132133When generating code for users:134- Prefer complete, runnable snippets with:135 - imports136 - ViewModel class137 - Spec declaration138 - widget/binding usage139 - disposal/setup notes140- State why `watch` or `read` was chosen.141- If introducing sharing, show explicit `key` and lifecycle implications.142143---144> Converted and distributed by [TomeVault](https://tomevault.io/claim/lwj1994) — claim your Tome and manage your conversions.145<!-- tomevault:4.0:skill_md:2026-04-11 -->