【Flutter原理】深入理解跨组件共享Provider

背景

通过前面的文章深入理解InheritedWidget和文章深入理解Notification,我们知道Flutter原生提供了两种组件之间数据共享的组件自上而下的共享数据方式采用InheritedWIgdt,自下而上的数据共享方式采用Notification,如果是跨页面共享数据呢?

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

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

本文这里介绍的是Flutter官方提倡的一个插件Provider。它的原理就是绑定InheritedWidget与依赖它的子孙组件的依赖关系,并且当InheritedWidget数据发生变化时,可以自动更新依赖的子孙组件。不了解InheritedWidget的强烈建议先看一遍深入理解InheritedWidget,下面我们来详细介绍下Provider的用法以及原理。

手动编写简单的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;
      }
    }
    
  2. 利用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
    }
    
  2. 使用

    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"));
                  }),
                ],
              ),
            ),
          )),
        );
      }
    }
    

效果:

image-20211112163132366

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

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

原理

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

ChangeNotifierProvider流程

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

总结

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

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>