Flutter 跨组件状态共享 Provider 的实现
我们都知道 Provider 是 Flutter 官方推荐的状态管理方式之一,实现也是依托于 InheritedWidget 这个组件,但是我们这里先不看 Provider 源码,先来看看自己能否使用之前对 InheritedWidget 的了解来实现一个简单的 Provider。
创建一个保存共享的数据 InheritedWidget,定义一个通用的 InheritedProvider 类,它继承自 InheritedWidget
// 一个通用的InheritedWidget,保存任需要跨组件共享的状态
class InheritedProvider<T> extends InheritedWidget {
InheritedProvider({@required this.data, Widget child}) : super(child: child);
//共享状态使用泛型
final T data;
@override
bool updateShouldNotify(InheritedProvider<T> old) {
//返回true,每次更新都会调用依赖其的子孙节点的`didChangeDependencies`。
return true;
}
}
通知数据的变化我们用的是 ChangeNotifier,这是 Flutter 提供的一个类,它继承自Listenable,实现了 发布者-订阅者模式,ChangeNotifier定义大致如下
class ChangeNotifier implements Listenable {
///存储监听
ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();
bool _debugAssertNotDisposed() {
assert(() {
if (_listeners == null) {
throw FlutterError(
'A $runtimeType was used after being disposed.\n'
'Once you have called dispose() on a $runtimeType, it can no longer be used.'
);
}
return true;
}());
return true;
}
///是否存在监听
@protected
bool get hasListeners {
assert(_debugAssertNotDisposed());
return _listeners.isNotEmpty;
}
///添加监听器
@override
void addListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
_listeners.add(listener);
}
///移除监听器
@override
void removeListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
_listeners.remove(listener);
}
///销毁
@mustCallSuper
void dispose() {
assert(_debugAssertNotDisposed());
_listeners = null;
}
///通知监听器
@protected
@visibleForTesting
void notifyListeners() {
assert(_debugAssertNotDisposed())
if (_listeners != null) {
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
for (final VoidCallback listener in localListeners) {
try {
//通知监听器,触发监听器回调
if (_listeners.contains(listener))
listener();
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'foundation library',
context: ErrorDescription('while dispatching notifications for $runtimeType'),
informationCollector: () sync* {
yield DiagnosticsProperty<ChangeNotifier>(
'The $runtimeType sending notification was',
this,
style: DiagnosticsTreeStyle.errorProperty,
);
},
));
}
}
}
}
}
如果我们把共享的状态放到一个 Model 类中,然后让它继承自 ChangeNotifier,这样当共享的状态改变时,我们只需要调用 notifyListeners() 来通知订阅者,然后由订阅者来重新构建 InheritedProvider,不就可以了嘛 下面这个类继承自 StatefulWidget,然后定义了一个 of() 静态方法供子类方便获取 Widget 树中 InheritedProvider 保存的共享状态(model)
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
ChangeNotifierProvider({
Key key,
this.data,
this.child,
});
final Widget child;
final T data;
//定义一个便捷方法,方便子树中的widget获取共享数据
static T of<T>(BuildContext context, {bool listen = true}) {
final provider = listen
? context.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>()
: context.getElementForInheritedWidgetOfExactType<InheritedProvider<T>>()?.widget
as InheritedProvider<T>;
return provider.data;
}
@override
_ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>();
}
class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider<T>> {
void update() {
//如果数据发生变化(model类调用了notifyListeners),重新构建InheritedProvider
setState(() => {});
}
@override
void didUpdateWidget(ChangeNotifierProvider<T> oldWidget) {
//当Provider更新时,如果新旧数据不"==",则解绑旧数据监听,同时添加新数据监听
if (widget.data != oldWidget.data) {
oldWidget.data.removeListener(update);
widget.data.addListener(update);
}
super.didUpdateWidget(oldWidget);
}
@override
void initState() {
// 给model添加监听器
widget.data.addListener(update);
super.initState();
}
@override
void dispose() {
// 移除model的监听器
widget.data.removeListener(update);
super.dispose();
}
@override
Widget build(BuildContext context) {
return InheritedProvider<T>(
data: widget.data,
child: widget.child,
);
}
}
可以看到 _ChangeNotifierProviderState 类的主要作用就是监听到共享状态(model)改变时重新构建Widget树。在 _ChangeNotifierProviderState 类中调用 setState() 方法,widget.child始终是同一个,所以执行build时,InheritedProvider 的 child 引用的始终是同一个子 widget,所以 widget.child 并不会重新 build,这也就相当于对 child 进行了缓存!当然如果 ChangeNotifierProvider 父级 Widget 重新 build 时,则其传入的 child 便有可能会发生变化。
使用案例
class Demo {
Item(this.count);
int count;
}
class DemoModel extends ChangeNotifier {
// 用于保存列表
final List<Demo> _items = [];
// 禁止改变列表里的信息
UnmodifiableListView<Demo> get items => UnmodifiableListView(_items);
// 这是唯一一种能从外部列表的方法
void add(Demo item) {
_items.add(item);
// 通知监听器(订阅者),重新构建 InheritedProvider, 更新状态。
notifyListeners();
}
}
class ProviderRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: ChangeNotifierProvider<DemoModel>(
data: DemoModel(),
child: Builder(builder: (context) {
return Column(
children: <Widget>[
Builder(builder: (context){
var count = ChangeNotifierProvider.of<DemoModel>(context).items.length;
return Text("总数量: $count");
}),
Consumer<DemoModel>(
builder: (context, demo)=> Text("总数量: ${demo.items.length}");
)
Builder(builder: (context){
print("RaisedButton build"); //在后面优化部分会用到
return RaisedButton(
child: Text("添加"),
onPressed: () {
//这里添加后数量会更新
ChangeNotifierProvider.of<DemoModel>(context).add(Demo(20));
},
);
}),
],
);
}),
),
);
}
}