flutter provider使用及其原理分析

官方刷新框架provider及其实现原理。

  • Provider:实现数据改变时,对应的局部widget自动刷新。
  • 响应式的实现。解决InheritedWidget由上而下的传递方式,实现model改变,widget自动刷新

provider的使用

  1. 定义对象并继承ChangeNotifier,在变化的时候发送通知方法notifyListeners:
class OrgInfo extends ChangeNotifier {
  List<OrgModel> orgs;
  OrgModel curorg;

  OrgInfo({this.orgs, this.curorg});

  void setOrgInfo(List<OrgModel> orgsParams, OrgModel curorgParams) {
    orgs = orgsParams;
    curorg = curorgParams;
    notifyListeners();
  }

  //当被剔除组织时,移除列表数据
  void removeOrgInfo(String orgId) {
    for (var orgModel in orgs) {
      if (orgModel.orgId == orgId) {
        orgs.remove(orgModel);
        break;
      }
    }
    notifyListeners();
  }

  void addOrgModel(OrgModel orgModel) {
    orgs.add(orgModel);
    notifyListeners();
  }
}
  1. 在app初始化时注册model:
class _MyAppState extends State<MyApp> {
  ...
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        providers: [
          ChangeNotifierProvider.value(value: UserManager.instance.orgInfo),
        ],
        child: MaterialApp(
            ...);
  }
}
  1. 在需要的自动局部的刷新使用Consumer,控件精确的越细越好:
Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
            child: MediaQuery.removePadding(
                removeTop: true,
                context: context,
                child: Consumer<OrgInfo>(
                  builder: (context, OrgInfo orgInfo, _) => ListView.builder(
                    shrinkWrap: true,
                    itemBuilder: (BuildContext context, int index) {
                      if (index == 0) {
                        return orgHeader(
                            orgInfo.orgs[widget.selectIndex].orgName);
                      }
                      return cell(index - 1);
                    },
                    itemCount: orgInfo.orgs.length + 1,
                  ),
                ))),
        SizedBox(
          height: 200,
        )
      ],
    );
  }

原理

其中model对象继承ChangeNotifier,被观察者对象,当model对象改变时,调用notifyListeners通知观察者刷新。
通知如何与widget结合实现自动刷新。

InheritedWidget

参考文章:
数据共享(InheritedWidget)
关于跨组件传递数据,你只需要记住这三招

案例:点击按钮,刷新子widget计数
如果不使用InheritedWidget:正常做法定义一个count变量,然后当参数传递到子widget。
如果使用InheritedWidget,代码如下:

class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    @required this.data,
    Widget child
  }) :super(child: child);
    
  final int data; //需要在子树中共享的数据,保存点击次数
    
  //定义一个便捷方法,方便子树中的widget获取共享数据  
  static ShareDataWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  //该回调决定当data发生变化时,是否通知子树中依赖data的Widget  
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    //如果返回true,则子树中依赖(build函数中有调用)本widget
    //的子widget的`state.didChangeDependencies`会被调用
    return old.data != data;
  }
}
class _TestWidget extends StatefulWidget {
  @override
  __TestWidgetState createState() => new __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
  @override
  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享数据
    return Text(ShareDataWidget
        .of(context)
        .data
        .toString());
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
    //如果build中没有依赖InheritedWidget,则此回调不会被调用。
    print("Dependencies change");
  }
}
class InheritedWidgetTestRoute extends StatefulWidget {
  @override
  _InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
}

class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return  Center(
      child: ShareDataWidget( //使用ShareDataWidget
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 20.0),
              child: _TestWidget(),//子widget中依赖ShareDataWidget
            ),
            RaisedButton(
              child: Text("Increment"),
              //每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新  
              onPressed: () => setState(() => ++count),
            )
          ],
        ),
      ),
    );
  }
}
  • ShareDataWidget继承InheritedWidget的模型对象,_TestWidget是InheritedWidgetTestRoute的子类。点击父类中的按钮,_TestWidget数据自动刷新。

小结:

  • 在父widget中state构造ShareDataWidget,子widget的state可以ShareDataWidget.of(context).data获取数据,避免了数据传递。
  • 数据自上而下传递。
  • setState也能刷新子widget。使用ShareDataWidget继承InheritedWidget的模型对象,子类的didChangeDependencies也会刷新。
  • dependOnInheritedWidgetOfExactType 子state会调用didChangeDependencies;getElementForInheritedWidgetOfExactType 子state不会调用didChangeDependencies。

缺点

  • setState会刷新所有widget
  • InheritedWidget也会全部刷新

Provider

InheritedProvider

// 一个通用的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;
  }
}

InheritedProvider是继承InheritedWidget对象,dependOnInheritedWidgetOfExactType时会刷新子类的didChangeDependencies。

ChangeNotifier实现

class ChangeNotifier implements Listenable {
  List listeners=[];
  @override
  void addListener(VoidCallback listener) {
     //添加监听器
     listeners.add(listener);
  }
  @override
  void removeListener(VoidCallback listener) {
    //移除监听器
    listeners.remove(listener);
  }
  
  void notifyListeners() {
    //通知所有监听器,触发监听器回调 
    listeners.forEach((item)=>item());
  }
   
  ... //省略无关代码
}

ChangeNotifier把方法添加到数组中,notifyListeners遍历调用。观察者实现。

ChangeNotifierProvider

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) {
    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,
    );
  }
}
  • 泛型T表示数据InheritedProvider data,继承自ChangeNotifier。被观察者
  • ChangeNotifierProvider data的观察者,T data监听update,当数据发生变化时,update方法调用setState,创建InheritedProvider,刷新child。
  • 实现InheritedProvider data与widget child的绑定。

data与UI的关系

data是被观察者,ChangeNotifierProvider是data的观察者;data改变时,ChangeNotifierProvider监听到变化,调用setState,构建InheritedProvider刷新UI。实现data与UI的绑定。

总结

  1. setState能刷新widget子树,刷新范围太大,并且需要把数据对象传递到子类。
  2. InheritedWidget不用传递数据对象,通过context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();获取父类的数据。但是刷新范围大,只能由上而下传递。
  3. Provider可以实现局部刷新。只要数据对象改变,UI能自动变化,实现响应式编程。屏蔽刷新逻辑,实现响应式数据与UI的绑定。无论是子类或父类改变数据都能刷新绑定的UI。


    image

参考文章:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容