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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com