Flutter 跨组件状态共享 Provider 的实现

标签: 编程学习 Flutter学习

我们都知道 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));
                  },
                );
              }),
            ],
          );
        }),
      ),
    );
  }
}