所有程序里界面和数据的交互都至关重要,直接决定了整个程序的结构,选好状态管理方案的重要性就不言而喻了
目前为止,Flutter 里的状态管理有很多的实现方法,官方也给出了很多的案例
官方给出的参考举例
- setState
- ChangeNotifier
- Delegate
- scoped_model -InheritedWidget
- Sigslot
- provide
- flutter-provide
- rx dart, fish redux, bloc
- EventBus
setState
最原始最基本 最重要的方式 setState,支持规模较小的程序足够了,其它方式最终都需要调用 setState
Function callback
Flutter 内置 ChangeNotifier, ValueNotifier 都可以认为是类似方案
Delegate
可以认为是多个回调函数,其他语言里都有类似模式,名称似乎来源于 Objective-C。
Sigslot
源自 Qt 里的经典编程模式,Dart 可以轻易实现。这种方式在 Flutter 里可能根本不会有太多应用
pkg:scoped_model
个人以为是最佳方案,源自 Fuchsia 代码,在其中广泛使用,设置程序几乎都是这个模式,后来独立为 package,包括注释也只有 287 行代码。由于 Fuchsia App 结构都是后台Service+前台UI,这个方案绝对是最合适的方案。使用 InheritedWidget 实现,性能不可能更好。
pkg:provide
出自Flutter dev team,绝对的官方了,总共代码 675行。实现方式和 scoped_model 类似,增加 Providers,Provider 支持 Stream。
flutter-provide
可以认为这个比 provide 更早,功能更丰富,实现依然是 InheritedWidget。可能不会有太广泛使用,但是在时间上有历史意义,故列出。
rxdart, fish-redux,bloc
略……^ _ ^
EventBus
分享主体:
InheritedWidget
可能对InheritedWidget 比较陌生,但是在Flutter开发当中,我们几乎每个Page里都能看到的身影,并且用过它,,它可以高效的将数据在Widget树中向下传递、共享,这在一些需要在Widget树中共享数据的场景中非常方便,如Flutter中,正是通过InheritedWidget来共享应用主题(Theme)和Locale(当前语言环境)信息的。它是自上而下的。
如:
MediaQuery.of(context).size.width
Theme.of(context).snackBarTheme.actionTextColor
使用姿势
final InheritedWidgetModel model;
//点击+号的方法
final Function() increment;
//点击-号的方法
final Function() reduce;
const MainInheritedWidget({Key key, Widget child, this.model, this.increment, this.reduce})
: super(key: key, child: child);
static MainInheritedWidget of(BuildContext context) {
context.dependOnInheritedWidgetOfExactType();
// context.inheritFromWidgetOfExactType(MainInheritedWidget);
}
@override
bool updateShouldNotify(MainInheritedWidget oldWidget) {
return oldWidget.model != model;
}
触发事件
//触发,完全是自定义
MainInheritedWidget.of(context).increment();
//取数据
Text( "${MainInheritedWidget.of(context).model.count}",style: TextStyle(fontSize: 50 ,fontWeight: FontWeight.w900),),
scoped_model
短小精干的scoped_model,是以InheritedWidget为基础的开发,所以直接展示,它的读取在一个函数体里去操作
class Counter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScopedModelDescendant(
builder: (context, _, model) => ActionChip(
label: Text('${model.count}'),//可以直接取
onPressed: model.increaseCount,//可以直接改变
),
);
}
}
ChangeNotifier
通知Notification的发送是通过disPatch进行分发的,就好像Android里面的事件分发,当NotificationListener监听到了通知事件,这时候会走到其onNotification回调中,根据回调中的返回值类型(true还是false)来决定是否还继续向父亲节点发送通知。它是自下而上。
接受数据
NotificationListener(
onNotification: (notification) {
setState(() {
count = notification.count;
});
return true;
},
发送数据
TestNotification(count: count).dispatch(context);
我们需要注意的地方:
Builder(
builder: (context) {
return RaisedButton(
color: Colors.blue,
child: Text('+'),
onPressed: () {
count++;
TestNotification(count: count).dispatch(context);
},
);
},
)
为什么我们一定要用Builder 取构建一下?
原因是通知在分发的时候,需要一个context参数,这个参数指的是Notification监听的子widget的context,如果直接的话,context是根widget的,这样会导致监听不到子widget了。
所以需要我们通过Builder构建出我们子widget的context,
Provide
和scoped_model一样,Provide也是借助了InheritWidget,将共享状态放到顶层MaterialApp之上。底层部件通过Provier获取该状态,并通过混合ChangeNotifier通知依赖于该状态的组件刷新。Provide还提供了Provide.stream,让我们能够以处理流的方式处理数据
直接上才艺:
void main() {
var counter = ProvideCounterModel();
var providers = Providers();
providers..provide(Provider.value(counter));
runApp(ProviderNode(
child: ProvideApp(),
providers: providers,
));
接受数据:
child: Provide(
builder: (context, child, counter) {
return Text(
"${counter.count}",
style: TextStyle(fontSize: 50, fontWeight: FontWeight.w900),
);
},
)
发送数据:
RaisedButton(
color: Colors.blue,
child: Text('+'),
onPressed: () {
Provide.value(context).increment();
},
)
EventBus
在APP中,我们经常会需要一个广播机制,用以跨页面事件通知,Flutter中我们可以使用event_bus提供的事件总线功能来实现一些状态的更新,其核心是基于Dart Streams(流);事件总线通常实现了订阅者模式,订阅者模式包含发布者和订阅者两种角色,可以通过事件总线来触发事件和监听事件
EventBus才艺展示
import 'package:event_bus/event_bus.dart';
EventBus eventBus = new EventBus();
class CounterEvent {
int count;
CounterEvent({this.count});
}
接受数据
@override
void initState() {
super.initState();
print("initState");
subscription = eventBus.on().listen((event) {
setState(() {
count = event.count;
});
});
}
@override
void dispose() {
print("dispose");
if (subscription != null) {
subscription.cancel();
}
super.dispose();
}
发送数据
RaisedButton(
color: Colors.blue,
child: Text('+'),
onPressed: () {
count ++;
eventBus.fire(CounterEvent(count: count));
},
)