背景

 跨页面/组件共享状态的管理方式比较多,比如全局时间总线EventBus,它是一个第三方的插件,使用观察者模式实现,通过它可以实现跨组件状态同步。但是它有两个缺点:

  • 必须显示定义各种事件,不好管理
  • 需要手动注册和取消注册,避免内存泄漏

手动编写简单的Provider

由于官方提供的provider功能比较全面,很多冗余的逻辑影响我们的理解。这里我们先按照Provider的逻辑实现,先手动吧编写一个简单的Provider。

分为以下几步进行:

  1. 自定义InheritedWidget
    需要一个保存共享数据的InheritedWidget,由于具体业务数据类型不可预期,为了通用性,我们使用泛型,定义一个通用的InheritedProvider类,它继承自InheritedWidget
// 一个通用的InheritedWidget,保存需要跨组件共享的状态
class InheritedProvider<T> extends InheritedWidget {
  InheritedProvider({
    required this.data,
    required Widget child,
  }) : super(child: child);

  final T data;

  @override
  bool updateShouldNotify(InheritedProvider<T> old) {
    //在此简单返回true,则每次更新都会调用依赖其的子孙节点的`didChangeDependencies`。
    return true;
  }
}
  1. 利用Flutter提供的ChangeNotifier类,实现订阅者类
//将要共享的状态放到一个Model类中,然后让它继承自ChangeNotifier,这样当共享的状态改变时,我们只需要调用notifyListeners() 来通知订阅者,然后由订阅者来重新构建InheritedProvider
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
  ChangeNotifierProvider({
    Key? key,
    required this.data,
    required this.child,
  });

  final Widget child;
  final T data;

  //定义一个便捷方法,方便子树中的widget获取共享数据
  static T? of<T>(BuildContext context) {
    // final type = _typeOf<InheritedProvider<T>>();
    final provider =
        context.dependOnInheritedWidgetOfExactType<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,
    );
  }
}

这里我们需要注意的是data类型T是继承自ChangeNotifier,widget.data的观察者其实就是update方法,当我们的data数据变化的时候就会通知观察者,调用到了update方法,update方法内部调用是setState()方法,从而触发了刷新。所以最终_ChangeNotifierProviderState类通过监听到共享状态(model)改变时重新构建Widget树的目的就达到了。

举个例子

就是用最开始新建Flutter Demo的时候 ,那个计数器的例子

  1. 定义Counter类,用用于表示数据信息
class Counter with ChangeNotifier {//1
  int _count;
  Counter(this._count);

  void add() {
    _count++;
    notifyListeners();//2
  }
  get count => _count;//3
}
  1. 使用
class _MyHomePageState extends State<MyHomePageWidget> {
  late PointerEvent _event;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Center(
          child: Container(
        width: 200,
        height: 200,
        color: Colors.yellow,
        //ChangeNotifierProvider<Counter>
        child: ChangeNotifierProvider<Counter>(
          data: Counter(1),
          child: Column(
            children: [
              // 这里一定要用Builder(builder: (context) {}包裹,不然:ChangeNotifierProvider.of<Counter>(context)为null
              Builder(builder: (context) {
                return Text(
                    "count:${ChangeNotifierProvider.of<Counter>(context)?.count}");
              }),
              Builder(builder: (context) {
                var counter = ChangeNotifierProvider.of<Counter>(context);
                return ElevatedButton(
                    onPressed: () {
                      counter?.add();
                    },
                    child: Text("点我+1"));
              }),
            ],
          ),
        ),
      )),
    );
  }
}

效果:

flutter Android开如何接收第三方app分享文件_共享数据

每次点击’点我+1’,count就会自增1。

其实这个只是个简单的demo,在真正的大型应用当中会有跨页面的应用场景,就能体现出它的优势了。我们只需要将ChangeNotifierProvider应用到根Widget树上,那么整个APP就可以共享数据了。

原理

来来来重点,根据上面的简单例子,我们梳理下Provider的原理:

flutter Android开如何接收第三方app分享文件_ide_02

Module变化后会自动触发notifyListeners(),调用到订阅者ChangeNotifierProvider的update方法,ChangeNotifierProvider内部会重新构建InheritedWidget,而依赖该InheritedWidget的子孙Widget就会更新。

总结

通过上面简单的例子我们基本上了解了Provider的整体工作流程,其实官方提供的Provider还有很多其他实用的功能,比如:同时监听多个Module,代码可读性更好,需要详细了解其功能的可以查看:https://github.com/rrousselGit/provider/。provider的用法这里就不做介绍了。