flutter provider使用及其原理分析

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

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

provider的使用

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

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

  void setOrgInfo(List 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 {
  ...
  @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(
                  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();
  }

  //该回调决定当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 {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return  Center(
      child: ShareDataWidget( //使用ShareDataWidget
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            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 extends InheritedWidget {
  InheritedProvider({@required this.data, Widget child}) : super(child: child);
  
  //共享状态使用泛型
  final T data;
  
  @override
  bool updateShouldNotify(InheritedProvider 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 extends StatefulWidget {
  ChangeNotifierProvider({
    Key key,
    this.data,
    this.child,
  });

  final Widget child;
  final T data;

  //定义一个便捷方法,方便子树中的widget获取共享数据
  static T of(BuildContext context) {
    final type = _typeOf>();
    final provider =  context.dependOnInheritedWidgetOfExactType>();
    return provider.data;
  }

  @override
  _ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}

class _ChangeNotifierProviderState extends State> {
  void update() {
    //如果数据发生变化(model类调用了notifyListeners),重新构建InheritedProvider
    setState(() => {});
  }

  @override
  void didUpdateWidget(ChangeNotifierProvider 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(
      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();获取父类的数据。但是刷新范围大,只能由上而下传递。
  3. Provider可以实现局部刷新。只要数据对象改变,UI能自动变化,实现响应式编程。屏蔽刷新逻辑,实现响应式数据与UI的绑定。无论是子类或父类改变数据都能刷新绑定的UI。


    image

参考文章:

  • 数据共享(InheritedWidget)
  • 跨组件状态共享(Provider)
  • 为什么需要做状态管理,怎么做?
  • 关于跨组件传递数据,你只需要记住这三招
  • 善用 Provider 榨干 Flutter 最后一点性能
  • Provide的简单应用及理解

你可能感兴趣的:(flutter provider使用及其原理分析)