1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'framework.dart';
/// An [InheritedWidget] that's intended to be used as the base class for
/// models whose dependents may only depend on one part or "aspect" of the
/// overall model.
///
/// An inherited widget's dependents are unconditionally rebuilt when the
/// inherited widget changes per [InheritedWidget.updateShouldNotify].
/// This widget is similar except that dependents aren't rebuilt
/// unconditionally.
///
/// Widgets that depend on an [InheritedModel] qualify their dependence
/// with a value that indicates what "aspect" of the model they depend
/// on. When the model is rebuilt, dependents will also be rebuilt, but
/// only if there was a change in the model that corresponds to the aspect
/// they provided.
///
/// The type parameter `T` is the type of the model aspect objects.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=ml5uefGgkaA}
///
/// Widgets create a dependency on an [InheritedModel] with a static method:
/// [InheritedModel.inheritFrom]. This method's `context` parameter
/// defines the subtree that will be rebuilt when the model changes.
/// Typically the `inheritFrom` method is called from a model-specific
/// static `of` method. For example:
///
/// ```dart
/// class MyModel extends InheritedModel<String> {
/// // ...
/// static MyModel of(BuildContext context, String aspect) {
/// return InheritedModel.inheritFrom<MyModel>(context, aspect: aspect);
/// }
/// }
/// ```
///
/// Calling `MyModel.of(context, 'foo')` means that `context` should only
/// be rebuilt when the `foo` aspect of `MyModel` changes. If the aspect
/// is null, then the model supports all aspects.
///
/// When the inherited model is rebuilt the [updateShouldNotify] and
/// [updateShouldNotifyDependent] methods are used to decide what
/// should be rebuilt. If [updateShouldNotify] returns true, then the
/// inherited model's [updateShouldNotifyDependent] method is tested for
/// each dependent and the set of aspect objects it depends on.
/// The [updateShouldNotifyDependent] method must compare the set of aspect
/// dependencies with the changes in the model itself.
///
/// For example:
///
/// ```dart
/// class ABModel extends InheritedModel<String> {
/// ABModel({ this.a, this.b, Widget child }) : super(child: child);
///
/// final int a;
/// final int b;
///
/// @override
/// bool updateShouldNotify(ABModel old) {
/// return a != old.a || b != old.b;
/// }
///
/// @override
/// bool updateShouldNotifyDependent(ABModel old, Set<String> aspects) {
/// return (a != old.a && aspects.contains('a'))
/// || (b != old.b && aspects.contains('b'))
/// }
///
/// // ...
/// }
/// ```
///
/// In the previous example the dependencies checked by
/// [updateShouldNotifyDependent] are just the aspect strings passed to
/// `inheritFromWidgetOfExactType`. They're represented as a [Set] because
/// one Widget can depend on more than one aspect of the model.
/// If a widget depends on the model but doesn't specify an aspect,
/// then changes in the model will cause the widget to be rebuilt
/// unconditionally.
///
/// See also:
///
/// * [InheritedWidget], an inherited widget that only notifies dependents
/// when its value is different.
/// * [InheritedNotifier], an inherited widget whose value can be a
/// [Listenable], and which will notify dependents whenever the value
/// sends notifications.
abstract class InheritedModel<T> extends InheritedWidget {
/// Creates an inherited widget that supports dependencies qualified by
/// "aspects", i.e. a descendant widget can indicate that it should
/// only be rebuilt if a specific aspect of the model changes.
const InheritedModel({ Key key, Widget child }) : super(key: key, child: child);
@override
InheritedModelElement<T> createElement() => InheritedModelElement<T>(this);
/// Return true if the changes between this model and [oldWidget] match any
/// of the [dependencies].
@protected
bool updateShouldNotifyDependent(covariant InheritedModel<T> oldWidget, Set<T> dependencies);
/// Returns true if this model supports the given [aspect].
///
/// Returns true by default: this model supports all aspects.
///
/// Subclasses may override this method to indicate that they do not support
/// all model aspects. This is typically done when a model can be used
/// to "shadow" some aspects of an ancestor.
@protected
bool isSupportedAspect(Object aspect) => true;
// The [result] will be a list of all of context's type T ancestors concluding
// with the one that supports the specified model [aspect].
static Iterable<InheritedElement> _findModels<T extends InheritedModel<Object>>(BuildContext context, Object aspect) sync* {
final InheritedElement model = context.ancestorInheritedElementForWidgetOfExactType(T);
if (model == null)
return;
yield model;
assert(model.widget is T);
final T modelWidget = model.widget;
if (modelWidget.isSupportedAspect(aspect))
return;
Element modelParent;
model.visitAncestorElements((Element ancestor) {
modelParent = ancestor;
return false;
});
if (modelParent == null)
return;
yield* _findModels<T>(modelParent, aspect);
}
/// Makes [context] dependent on the specified [aspect] of an [InheritedModel]
/// of type T.
///
/// When the given [aspect] of the model changes, the [context] will be
/// rebuilt. The [updateShouldNotifyDependent] method must determine if a
/// change in the model widget corresponds to an [aspect] value.
///
/// The dependencies created by this method target all [InheritedModel] ancestors
/// of type T up to and including the first one for which [isSupportedAspect]
/// returns true.
///
/// If [aspect] is null this method is the same as
/// `context.inheritFromWidgetOfExactType(T)`.
///
/// If no ancestor of type T exists, null is returned.
static T inheritFrom<T extends InheritedModel<Object>>(BuildContext context, { Object aspect }) {
if (aspect == null)
return context.inheritFromWidgetOfExactType(T);
// Create a dependency on all of the type T ancestor models up until
// a model is found for which isSupportedAspect(aspect) is true.
final List<InheritedElement> models = _findModels<T>(context, aspect).toList();
if (models.isEmpty) {
return null;
}
final InheritedElement lastModel = models.last;
for (InheritedElement model in models) {
final T value = context.inheritFromElement(model, aspect: aspect);
if (model == lastModel)
return value;
}
assert(false);
return null;
}
}
/// An [Element] that uses a [InheritedModel] as its configuration.
class InheritedModelElement<T> extends InheritedElement {
/// Creates an element that uses the given widget as its configuration.
InheritedModelElement(InheritedModel<T> widget) : super(widget);
@override
InheritedModel<T> get widget => super.widget;
@override
void updateDependencies(Element dependent, Object aspect) {
final Set<T> dependencies = getDependencies(dependent);
if (dependencies != null && dependencies.isEmpty)
return;
if (aspect == null) {
setDependencies(dependent, HashSet<T>());
} else {
assert(aspect is T);
setDependencies(dependent, (dependencies ?? HashSet<T>())..add(aspect));
}
}
@override
void notifyDependent(InheritedModel<T> oldWidget, Element dependent) {
final Set<T> dependencies = getDependencies(dependent);
if (dependencies == null)
return;
if (dependencies.isEmpty || widget.updateShouldNotifyDependent(oldWidget, dependencies))
dependent.didChangeDependencies();
}
}