view_model
SKILL.md
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.mdreferences/README_FULL_ZH.md
- Upstream source in repo:
packages/view_model/README.mdpackages/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.mdfirst. - 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_*.mdas 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 extendingChangeNotifierbehavior is required.
- Define
ViewModelSpec
ViewModelSpec<T>(builder: ...)for no args.ViewModelSpec.arg/arg2/arg3/arg4for parameterized construction.- Use
keyfor shared instance identity. - Use
tagfor grouped lookup. - Use
aliveForever: trueonly 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 ViewModelBindingand calldispose()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; nextwatch/readgets fresh instance.
- Handle dependencies and sharing
- In a ViewModel,
viewModelBindingis 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
bindingIdsbecomes empty andaliveForeveris 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
StateViewModelValueWatcherfor selector-level rebuilds (usually pair withread, notwatch). ObservableValue+ObserverBuilder(1/2/3)for lightweight reactive values; sameshareKeymeans shared underlying state.
- App-level setup
- Call
ViewModel.initialize(...)once at app startup (subsequent calls are ignored). - Configure
ViewModelConfigwhen needed:isLoggingEnabledequals(state equality strategy)onListenerErroronDisposeError
- If using
equals: (a, b) => a == b, ensure state classes implement==andhashCode.
- Testing and mocking
- Prefer pure Dart unit tests with
ViewModelBinding()(notestWidgetsrequired for many cases). - Always
binding.dispose()in teardown. - For spec override:
spec.setProxy(...)andspec.clearProxy().
- Code generation (optional)
- Annotate with
@GenSpec, addpart '*.vm.dart', then rundart run build_runner build. - Generator creates
xxxViewModelSpecand supports up to 4 constructor args.
Do/Don't checklist
Do:
- Keep
watchfor reactive UI,readfor imperative actions. - Set explicit
keywhenever instance sharing is a requirement. - Dispose non-widget bindings explicitly.
- Use
listenStateSelectfor side effects on selected state fields.
Don't:
- Claim
readis "non-binding" (it still binds and affects lifecycle). - Use cached APIs expecting auto-create behavior.
- Overuse
aliveForeverfor page-scoped state. - Forget
ViewModel.routeObserverwhen 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
watchorreadwas chosen. - If introducing sharing, show explicit
keyand lifecycle implications.
Weekly Installs
0
Repository
lwj1994/flutter…ew_modelGitHub Stars
21
First Seen
Jan 1, 1970
Security Audits