数据共享:InheritedWidget
解释:
Flutter中InheritedWidget可以实现不同子Widget(可以是不同的页面)中共享数据,依赖了同一个InheritedWidget的Widget会在InheritedWidget中数据改变的时候同步数据。
原理:
InheritedWidget中可以存放数据data,它的子组件使用了InheritedWidget中的data,那么这个子组件就依赖于该InheritedWidget,所以子组件是否依赖InheritedWidget主要在于子组件是否使用了父组件InheritedWidget中的数据。形成依赖后,当InheritedWidget中的数据data改变时,就会通知到依赖它的子组件。我们知道在StatefulWidget的State对象中有一个didChangeDependencies回调,看名字就知道,它会在依赖的组件发生变化时被调用。另外我们知道 initState -> didChangeDependencies -> build,所以这个时候就会调用build函数进行页面重构。当然,如果子组件没有使用到父组件InheritedWidget中的数据时,也就没有产生依赖关系,InheritedWidget中的data变化时也就不会通知该子组件。
这里我们先定义一个InheritedWidget:
class DataWidget extends InheritedWidget{
//定义数据
int data;
//提供子组件获取数据的方法,传入该InheritedWidget关联的BuildContext对象
static DataWidget of(BuildContext context){
//从名字就知道,从BuildContext树上取出指定类型的InheritedWidget,并建立依赖关系
//return context.inheritFromWidgetOfExactType(DataWidget);//这个方法在新版本已经过时了使用了dependOnInheritedWidgetOfExactType函数代替,所以这里可以这样写
return context.dependOnInheritedWidgetOfExactType<DataWidget>();
}
@override
bool updateShouldNotify(DataWidget old){
return old.data != data;
}
}
疑问:通过上边的描述可知,当我们InheritedWidget的子组件调用of函数获取到data的时候,子组件与InheritedWidget就产生了依赖关系,那假如我只想主动的通过of函数获取父组件InheritedWidget中的data,而并不想让InheritedWidget在数据发生改变时通知我的子组件,简单点说就是我的子组件只想主动去拿你的数据,但又不想跟你建立依赖关系,我要怎么做呢?
最简单的就是当子组件通过of取数据时,不建立依赖关系即可。没有了依赖关系,当父组件InheritedWidget中的数据发生变化时,该子组件的didChangeDependencies -> build系列函数自然就不会被调用了,也就不会重构了。
InheritedWidget作为遗传学专家,它当然知道我们的一些特殊需求啦!从上边代码中可以看出子组件获取数据是通过BuildContext的inheritFromWidgetOfExactType函数返回指定的数据类型,我们来看看inheritFromWidgetOfExactType函数的注释说明:
/// Obtains the nearest widget of the given type, which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
/// type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget.
/// This method is deprecated. Please use [dependOnInheritedWidgetOfExactType] instead.
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
@Deprecated(
'Use dependOnInheritedWidgetOfExactType instead. '
'This feature was deprecated after v1.12.1.'
)
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect });
上边这个方法在1.12.1版本中已经过时了,使用了dependOnInheritedWidgetOfExactType方法代替:
/// Obtains the nearest widget of the given type [T], which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
/// type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget.
///
/// This is typically called implicitly from `of()` static methods, e.g.
/// [Theme.of].
///
/// This method should not be called from widget constructors or from
/// [State.initState] methods, because those methods would not get called
/// again if the inherited value were to change. To ensure that the widget
/// correctly updates itself when the inherited value changes, only call this
/// (directly or indirectly) from build methods, layout and paint callbacks, or
/// from [State.didChangeDependencies].
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor in [State.didChangeDependencies].
/// It is safe to use this method from [State.deactivate], which is called
/// whenever the widget is removed from the tree.
///
/// It is also possible to call this method from interaction event handlers
/// (e.g. gesture callbacks) or timers, to obtain a value once, if that value
/// is not going to be cached and reused later.
///
/// Calling this method is O(1) with a small constant factor, but will lead to
/// the widget being rebuilt more often.
///
/// Once a widget registers a dependency on a particular type by calling this
/// method, it will be rebuilt, and [State.didChangeDependencies] will be
/// called, whenever changes occur relating to that widget until the next time
/// the widget or one of its ancestors is moved (for example, because an
/// ancestor is added or removed).
///
/// The [aspect] parameter is only used when [T] is an
/// [InheritedWidget] subclasses that supports partial updates, like
/// [InheritedModel]. It specifies what "aspect" of the inherited
/// widget this context depends on.
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object aspect });
简单翻译一下:从BuildContext的Widget树上获取最近指定的类型的Widget,指定的Widget必须是InheritedWidget的子类,并且指定的Widgt挂载到了该BuildContext上,这个Widget改变时,该BuildContext会rebuilt,这样获取数据的子组件就会重新build,所以它就可以从该InheritedWidget中获取到最新的数据。
通俗易懂点:我们的InheritedWidget是挂载到BuildContext,它的子组件自然也在该BuildContext树上,当InheritedWidget发生变化时,BuildContext会rebuilt,从而子组件就就能重新build获取最新的数据。
到此我们知道了,子组件与父组件InheritedWidget是如何建立依赖关系的,并且知道子组件是怎么取数据的,InheritedWidget数据变化时,子组件是怎么获取最新数据的。
现在我们来解决上边那个疑问,BuildContext提供了另一个函数
/// Obtains the element corresponding to the nearest widget of the given type,
/// which must be the type of a concrete [InheritedWidget] subclass.
///
/// This method is deprecated. Please use [getElementForInheritedWidgetOfExactType] instead.
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
@Deprecated(
'Use getElementForInheritedWidgetOfExactType instead. ’
‘This feature was deprecated after v1.12.1.’
)
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType);
上边可以看出这个函数已经被抛弃了,新版使用getElementForInheritedWidgetOfExactType,所以我们来看看getElementForInheritedWidgetOfExactType:
/// Obtains the element corresponding to the nearest widget of the given type [T],
/// which must be the type of a concrete [InheritedWidget] subclass.
///
/// Calling this method is O(1) with a small constant factor.
///
/// This method does not establish a relationship with the target in the way
/// that [dependOnInheritedWidgetOfExactType] does.
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor by calling
/// [dependOnInheritedWidgetOfExactType] in [State.didChangeDependencies]. It is
/// safe to use this method from [State.deactivate], which is called whenever
/// the widget is removed from the tree.
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();
从上边红色注释可以看出,使用该方法获取InheritedWidget时不会建立依赖关系。
所以我们重写of函数:
class DataWidget extends InheritedWidget{
//定义数据
int data;
//提供子组件获取数据的方法,传入该InheritedWidget关联的BuildContext对象
static DataWidget of(BuildContext context){
//从名字就知道,从BuildContext树上取出指定类型的InheritedWidget,但不建立依赖关系
//return context.ancestorInheritedElementForWidgetOfExactType(DataWidget).widget;//这个方法在新版本已经过时了使用了getElementForInheritedWidgetOfExactType函数代替,所以这里可以这样写
return context.getElementForInheritedWidgetOfExactType(DataWidget).widget;
}
@override
bool updateShouldNotify(DataWidget old){
return old.data != data;
}
}
通过以上改动我们就可以实现子组件通过InheritedWidget的of函数获取数据的时候不建立依赖关系的能力。
接下来简单看下几个方法:
/// Obtains the nearest widget of the given type [T], which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
/// type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget.
///
/// This is typically called implicitly from `of()` static methods, e.g.
/// [Theme.of].
///
/// This method should not be called from widget constructors or from
/// [State.initState] methods, because those methods would not get called
/// again if the inherited value were to change. To ensure that the widget
/// correctly updates itself when the inherited value changes, only call this
/// (directly or indirectly) from build methods, layout and paint callbacks, or
/// from [State.didChangeDependencies].
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor in [State.didChangeDependencies].
/// It is safe to use this method from [State.deactivate], which is called
/// whenever the widget is removed from the tree.
///
/// It is also possible to call this method from interaction event handlers
/// (e.g. gesture callbacks) or timers, to obtain a value once, if that value
/// is not going to be cached and reused later.
///
/// Calling this method is O(1) with a small constant factor, but will lead to
/// the widget being rebuilt more often.
///
/// Once a widget registers a dependency on a particular type by calling this
/// method, it will be rebuilt, and [State.didChangeDependencies] will be
/// called, whenever changes occur relating to that widget until the next time
/// the widget or one of its ancestors is moved (for example, because an
/// ancestor is added or removed).
///
/// The [aspect] parameter is only used when [T] is an
/// [InheritedWidget] subclasses that supports partial updates, like
/// [InheritedModel]. It specifies what "aspect" of the inherited
/// widget this context depends on.
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {//T的类型是final type = _typeOf<InheritedProvider<T>>();
assert(_debugCheckStateIsActiveForAncestorLookup());
//Map<Type, InheritedElement> _inheritedWidgets;
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];//_inheritedWidgets是在mount的时候将InheritedWidget放进去
if (ancestor != null) {
assert(ancestor is InheritedElement);
return dependOnInheritedElement(ancestor, aspect: aspect);//建立依赖关系
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);//将子组件挂载到到InheritedWidget上,建立依赖关系
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
/// Notifies all dependent elements that this inherited widget has changed, by
/// calling [Element.didChangeDependencies].
///
/// This method must only be called during the build phase. Usually this
/// method is called automatically when an inherited widget is rebuilt, e.g.
/// as a result of calling [State.setState] above the inherited widget.
///
/// See also:
///
/// * [InheritedNotifier], a subclass of [InheritedWidget] that also calls
/// this method when its [Listenable] sends a notification.
@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);//当InheritedWidget发生变化时,通知所有依赖于它的子组件
}
}
}
/// Called by [notifyClients] for each dependent.
///
/// Calls `dependent.didChangeDependencies()` by default.
///
/// Subclasses can override this method to selectively call
/// [didChangeDependencies] based on the value of [getDependencies].
///
/// See also:
///
/// * [updateDependencies], which is called each time a dependency is
/// created with [dependOnInheritedWidgetOfExactType].
/// * [getDependencies], which returns the current value for a dependent
/// element.
/// * [setDependencies], which sets the value for a dependent element.
/// * [InheritedModel], which is an example of a class that uses this method
/// to manage dependency values.
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();//调用依赖于InheritedWidget的子组件的
}
/// Obtains the element corresponding to the nearest widget of the given type [T],
/// which must be the type of a concrete [InheritedWidget] subclass.
///
/// Calling this method is O(1) with a small constant factor.
///
/// This method does not establish a relationship with the target in the way
/// that [dependOnInheritedWidgetOfExactType] does.
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor by calling
/// [dependOnInheritedWidgetOfExactType] in [State.didChangeDependencies]. It is
/// safe to use this method from [State.deactivate], which is called whenever
/// the widget is removed from the tree.
@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
return ancestor;//并没有建立依赖关系
}
ok,到此我们清楚的了解了依赖关系的建立,以及当InheritedWidget的数据发生变化时,为什么只有建立了依赖关系的子组件会重新执行build。