背景

 

每种语言都有自己数据共享的方式,全局变量、单例、数据持久化等。我们在进行数据的共享的时候,不仅要考虑到数据的共享性,还需考虑到数据变化后,共享的各个组件如何进行数据更新。本篇针对这个问题,介绍一下flutter中数据共享的方式,以及如何进行数据变更之后通知各个组件进行刷新的。

 

实现

 

概念

 

Flutter中已经为我们提供了一个widget来进行数据共享,它就是InheritedWidget,看下官方对它的介绍:

 

InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种数据在widget树中从上到下传递、共享的方式,比如我们在应用的根widget中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget中来获取该共享的数据!这个特性在一些需要在widget树中共享数据的场景中非常方便!如Flutter SDK中正是通过InheritedWidget来共享应用主题(Theme)和Locale (当前语言环境)信息的

 

从官方解释来看,InheritedWidget是一个从上往子树和节点上传递数据,当数据改变的时候,通知子widgets实现数据变化后更新。

 

使用

 

我们通过官方栗子来看下它具体的工作模式:

1.首先我们定义一个用来数据共享的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.inheritFromWidgetOfExactType(ShareDataWidget);
  }

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

2.然后我们实现一个用来显示从ShareDataWidget中共享的数据:

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

3.定义一个按钮,点击自增,来看当InheritedWidget的数据发生变化时,子widget中进行数据当更新:

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

运行后,我们会发现,每点击一下自增按钮,_TestWidget中会更新count的显示。

 

原理

 

我们介绍完了InheritedWidget的用法,接下来我们看一下,它是如何实现数据共享以及数据变化之后,它是如何通知子widget进行数据更新的呢?

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => new InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

这里涉及到widget的didChangeDependencies概念:

StatefulWidget里,State对象有一个didChangeDependencies回调,它会在“依赖”发生变化时被Flutter Framework调用。而这个“依赖”指的就是子widget是否使用了父widget中InheritedWidget的数据!如果使用了,则代表子widget依赖有依赖InheritedWidget;如果没有使用则代表没有依赖。这种机制可以使子组件在所依赖的InheritedWidget变化时来更新自身!比如当主题、locale(语言)等发生变化时,依赖其的子widget的didChangeDependencies方法将会被调用。

 

当updateShouldNotify返回true时,即子widget的“依赖”发生了改变,于是回调了子widget的didChangeDependencies方法。一般来说,子widget很少会重写此方法,因为在依赖改变后framework也都会调用build()方法。但是,如果你需要在依赖改变后执行一些昂贵的操作,比如网络请求,这时最好的方式就是在此方法中执行,这样可以避免每次build()都执行这些昂贵操作。

从源码中看,InheritedWidget的实现很简单:

  • createElement:创建对应widget的Element,所有widget共有的一个方法。
  • updateShouldNotify:从名字看就可以知道,该方法来决定是否通知子widget进行更新,返回true则通知子widget进行更新

 

似乎没有看到跟数据共享的身影,既然widget中没有,那一定是在它对应的Element中了。

class InheritedElement extends ProxyElement {
  
  InheritedElement(InheritedWidget widget) : super(widget);

  @override
  InheritedWidget get widget => super.widget;

  final Map<Element, Object> _dependents = HashMap<Element, Object>();

  @override
  void _updateInheritance() {
    assert(_active);
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
  }

  ...

  @protected
  void updateDependencies(Element dependent, Object aspect) {
    setDependencies(dependent, null);
  }

  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }

  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (Element dependent in _dependents.keys) {
      assert(() {
        // check that it really is our descendant
        Element ancestor = dependent._parent;
        while (ancestor != this && ancestor != null)
          ancestor = ancestor._parent;
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies.contains(this));
      notifyDependent(oldWidget, dependent);
    }
  }
}

从InheritedElement源码中,我们可以看到,InheritedElement维护了一个依赖列表。它记录了所有依赖它的Element,然后通过notifyClients方法对依赖列表中的每一个Element进行更新,即调用子widget的didChangeDependencies()方法,notifyClients在其父类中当widget改变的时候调用。这就解释了,当InheritedWidget状态改变更新时,如何通知依赖其的子widget。还有一个问题,子widget是什么时候被加入到这个依赖列表的呢?

还记得我们刚刚举的栗子吗,在创建共享widget的时候我们写了一个通用约定的静态方法:

//定义一个便捷方法,方便子树中的widget获取共享数据  
  static ShareDataWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(ShareDataWidget);
  }

会不会跟它有关呢,我们来看下该方法的实现:

@override
  InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

  @override
  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

_inheritedWidgets:在InheritedElement源码中,可以看到通过_updateInheritance方法, 该map缓存了该节点的父节点中所有相关的InheritedElement。

可以看到,我们是通过inheritFromWidgetOfExactType()静态方法,将子widget注册给InheritedWidget,这就是子widget“依赖”的由来。需注意的是:inheritFromWidgetOfExactType是获取最近的给定类型的Widget,该widget必须是InheritedWidget的子类,并向该widget注册传入的context,当该widget改变时,这个context会重新构建以便从该widget获得新的值。

至此,我们分析完了子widget是如何从注册“依赖”,到当InheritedWidget数据发生变化时,如何通知子widget进行数据更新的过程。