在 Flutter 开发领域,状态管理框架层出不穷,Provider、Riverpod、Bloc、Signal 和 Fish Redux 等都是其中的佼佼者。然而,这些框架在实际使用过程中,普遍存在操作复杂的问题,并且过度关注局部状态的更新。
- Provider:该框架仅支持在 Widget 树的上下层级间共享状态。这就导致处于同级树结构,比如不同路由页面之间,无法直接共享状态。开发者往往只能将状态提升到 Application 层的 Widget 树中进行管理。
- Bloc 和 Signal:这两个框架的状态共享机制基于 Provider 构建,因此同样存在上述共享范围受限的问题。
- Riverpod:为解决状态共享问题,Riverpod 舍弃了 Provider 的实现,自行开发了单例管理器,实现了状态的全局创建与销毁管理。但在实际使用中,它仍需依赖一层 Provider 来创建状态,即便官方提供了注解方式简化操作,整体流程依然较为繁琐。
回归初心,一个 ui 的状态管理应该具备哪些功能?
- 存储 view 的状态
- 声明式 ui,必须要有数据的监听
- 可以在 Widget 之间共享
- 可以跟着 Widget 自动销毁
局部的状态更新真的重要吗?
我们都知道 flutter 3 层树结构, Wiget 就是配置而已。 我们只要恰当的重写 State 的 didUpdate(StatefulWiget old)
,利用 Widget 自身的 diff 机制就不会有性能开销。每次 setState 不过是多了几个或者几十个 Widget 对象的内存开销而已,并且很快会被 gc 回收掉,实际的性能损坏对于大部分机器来说忽略不计。
基于此,一个轻量版的 view_model 如下:
view_model
- 简单且轻量级。
- 无复杂机制,基于
StreamController
和setState
实现。 - 自动释放资源,遵循
State
的dispose
方法。 - 可在任意有状态组件(
StatefulWidget
)间共享。
ViewModel
仅绑定到有状态组件(StatefulWidget
)的State
上。我们不建议将状态绑定到无状态组件(StatelessWidget
)上,因为无状态组件不应有状态。
核心概念
ViewModel
:存储状态并在状态改变时发出通知。ViewModelFactory
:指导如何创建你的ViewModel
。getViewModel
:创建或获取已存在的ViewModel
。listenViewModelStateChanged
:监听Widget.State
内的状态变化。
使用方法
添加依赖
view_model:
git:
url: https://github.com/lwj1994/flutter_view_model
ref: 0.0.1
定义ViewModel
import "package:view_model/view_model.dart";
class MyViewModel extends ViewModel<String> {
MyViewModel({
required super.state,
}) {
debugPrint("创建 MyViewModel,状态: $state,哈希码: $hashCode");
}
void setNewState() {
setState((s) {
return "hi";
});
}
@override
void dispose() async {
super.dispose();
debugPrint("释放 MyViewModel,状态: $state,哈希码: $hashCode");
}
}
class MyViewModelFactory with ViewModelFactory<MyViewModel> {
final String arg;
MyViewModelFactory({this.arg = ""});
@override
MyViewModel build() {
return MyViewModel(state: arg);
}
}
在组件中使用ViewModel
import "package:view_model/view_model.dart";
class _State extends State<Page> with ViewModelStateMixin<Page> {
// 建议使用getter来获取ViewModel
MyViewModel get viewModel =>
getViewModel<MyViewModel>(factory: MyViewModelFactory(arg: "初始参数"));
// 获取ViewModel的状态
String get state => viewModel.state;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
viewModel.setNewState();
},
child: Icon(Icons.add),
),
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
appRouter.maybePop();
},
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("mainViewModel.state = ${_mainViewModel.state}"),
Text(
state,
style: const TextStyle(color: Colors.red),
),
FilledButton(
onPressed: () async {
refreshViewModel(_mainViewModel);
},
child: const Text("刷新mainViewModel")),
FilledButton(
onPressed: () {
debugPrint("页面的MyViewModel哈希码 = ${viewModel.hashCode}");
debugPrint("页面的MyViewModel状态 = ${viewModel.state}");
},
child: const Text("打印MyViewModel")),
],
),
);
}
}
共享ViewModel
你可以设置unique() => true
来在任意有状态组件(StateWidget
)间共享同一个ViewModel
实例。
import "package:view_model/view_model.dart";
class MyViewModelFactory with ViewModelFactory<MyViewModel> {
final String arg;
MyViewModelFactory({this.arg = ""});
@override
MyViewModel build() {
return MyViewModel(state: arg);
}
// 如果为true,则会共享同一个viewModel实例。
@override
bool unique() => false;
}
监听状态变化
@override
void initState() {
super.initState();
listenViewModelStateChanged<MainViewModel, String>(
_mainViewModel,
onChange: (String? p, String n) {
print("mainViewModel状态变化: $p -> $n");
},
);
}
刷新ViewModel
这将释放旧的ViewModel
并创建一个新的。不过,建议使用getter来获取ViewModel
,否则你需要手动重置ViewModel
。
// 建议使用getter来获取ViewModel。
MyViewModel get viewModel => getViewModel<MyViewModel>();
void refresh() {
// 刷新
refreshViewModel(viewModel);
}
或者
late MyViewModel viewModel = getViewModel<MyViewModel>(factory: factory);
void refresh() {
// 刷新并重置
refreshViewModel(viewModel);
viewModel = getViewModel<MyViewModel>(factory: factory);
}
最后贴上 github: github.com/lwj1994/flu…