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 | import 'framework.dart'; |
6 | |
7 | // Examples can assume: |
8 | // class MyWidget extends StatelessWidget { const MyWidget({super.key, required this.child}); final Widget child; @override Widget build(BuildContext context) => child; } |
9 | |
10 | /// A lookup boundary controls what entities are visible to descendants of the |
11 | /// boundary via the static lookup methods provided by the boundary. |
12 | /// |
13 | /// The static lookup methods of the boundary mirror the lookup methods by the |
14 | /// same name exposed on [BuildContext] and they can be used as direct |
15 | /// replacements. Unlike the methods on [BuildContext], these methods do not |
16 | /// find any ancestor entities of the closest [LookupBoundary] surrounding the |
17 | /// provided [BuildContext]. The root of the tree is an implicit lookup boundary. |
18 | /// |
19 | /// {@tool snippet} |
20 | /// In the example below, the [LookupBoundary.findAncestorWidgetOfExactType] |
21 | /// call returns null because the [LookupBoundary] "hides" `MyWidget` from the |
22 | /// [BuildContext] that was queried. |
23 | /// |
24 | /// ```dart |
25 | /// MyWidget( |
26 | /// child: LookupBoundary( |
27 | /// child: Builder( |
28 | /// builder: (BuildContext context) { |
29 | /// MyWidget? widget = LookupBoundary.findAncestorWidgetOfExactType<MyWidget>(context); |
30 | /// return Text('$widget'); // "null" |
31 | /// }, |
32 | /// ), |
33 | /// ), |
34 | /// ) |
35 | /// ``` |
36 | /// {@end-tool} |
37 | /// |
38 | /// A [LookupBoundary] only affects the behavior of the static lookup methods |
39 | /// defined on the boundary. It does not affect the behavior of the lookup |
40 | /// methods defined on [BuildContext]. |
41 | /// |
42 | /// A [LookupBoundary] is rarely instantiated directly. They are inserted at |
43 | /// locations of the widget tree where the render tree diverges from the element |
44 | /// tree, which is rather uncommon. Such anomalies are created by |
45 | /// [RenderObjectElement]s that don't attach their [RenderObject] to the closest |
46 | /// ancestor [RenderObjectElement], e.g. because they bootstrap a separate |
47 | /// stand-alone render tree. |
48 | // TODO(goderbauer): Reference the View widget here once available. |
49 | /// This behavior breaks the assumption some widgets have about the structure of |
50 | /// the render tree: These widgets may try to reach out to an ancestor widget, |
51 | /// assuming that their associated [RenderObject]s are also ancestors, which due |
52 | /// to the anomaly may not be the case. At the point where the divergence in the |
53 | /// two trees is introduced, a [LookupBoundary] can be used to hide that ancestor |
54 | /// from the querying widget. |
55 | /// |
56 | /// As an example, [Material.of] relies on lookup boundaries to hide the |
57 | /// [Material] widget from certain descendant button widget. Buttons reach out |
58 | /// to their [Material] ancestor to draw ink splashes on its associated render |
59 | /// object. This only produces the desired effect if the button render object |
60 | /// is a descendant of the [Material] render object. If the element tree and |
61 | /// the render tree are not in sync due to anomalies described above, this may |
62 | /// not be the case. To avoid incorrect visuals, the [Material] relies on |
63 | /// lookup boundaries to hide itself from descendants in subtrees with such |
64 | /// anomalies. Those subtrees are expected to introduce their own [Material] |
65 | /// widget that buttons there can utilize without crossing a lookup boundary. |
66 | class LookupBoundary extends InheritedWidget { |
67 | /// Creates a [LookupBoundary]. |
68 | /// |
69 | /// A none-null [child] widget must be provided. |
70 | const LookupBoundary({super.key, required super.child}); |
71 | |
72 | /// Obtains the nearest widget of the given type `T` within the current |
73 | /// [LookupBoundary] of `context`, which must be the type of a concrete |
74 | /// [InheritedWidget] subclass, and registers the provided build `context` |
75 | /// with that widget such that when that widget changes (or a new widget of |
76 | /// that type is introduced, or the widget goes away), the build context is |
77 | /// rebuilt so that it can obtain new values from that widget. |
78 | /// |
79 | /// This method behaves exactly like |
80 | /// [BuildContext.dependOnInheritedWidgetOfExactType], except it only |
81 | /// considers [InheritedWidget]s of the specified type `T` between the |
82 | /// provided [BuildContext] and its closest [LookupBoundary] ancestor. |
83 | /// [InheritedWidget]s past that [LookupBoundary] are invisible to this |
84 | /// method. The root of the tree is treated as an implicit lookup boundary. |
85 | /// |
86 | /// {@macro flutter.widgets.BuildContext.dependOnInheritedWidgetOfExactType} |
87 | static T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>(BuildContext context, { Object? aspect }) { |
88 | // The following call makes sure that context depends on something so |
89 | // Element.didChangeDependencies is called when context moves in the tree |
90 | // even when requested dependency remains unfulfilled (i.e. null is |
91 | // returned). |
92 | context.dependOnInheritedWidgetOfExactType<LookupBoundary>(); |
93 | final InheritedElement? candidate = getElementForInheritedWidgetOfExactType<T>(context); |
94 | if (candidate == null) { |
95 | return null; |
96 | } |
97 | context.dependOnInheritedElement(candidate, aspect: aspect); |
98 | return candidate.widget as T; |
99 | } |
100 | |
101 | /// Obtains the element corresponding to the nearest widget of the given type |
102 | /// `T` within the current [LookupBoundary] of `context`. |
103 | /// |
104 | /// `T` must be the type of a concrete [InheritedWidget] subclass. Returns |
105 | /// null if no such element is found. |
106 | /// |
107 | /// This method behaves exactly like |
108 | /// [BuildContext.getElementForInheritedWidgetOfExactType], except it only |
109 | /// considers [InheritedWidget]s of the specified type `T` between the |
110 | /// provided [BuildContext] and its closest [LookupBoundary] ancestor. |
111 | /// [InheritedWidget]s past that [LookupBoundary] are invisible to this |
112 | /// method. The root of the tree is treated as an implicit lookup boundary. |
113 | /// |
114 | /// {@macro flutter.widgets.BuildContext.getElementForInheritedWidgetOfExactType} |
115 | static InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>(BuildContext context) { |
116 | final InheritedElement? candidate = context.getElementForInheritedWidgetOfExactType<T>(); |
117 | if (candidate == null) { |
118 | return null; |
119 | } |
120 | final Element? boundary = context.getElementForInheritedWidgetOfExactType<LookupBoundary>(); |
121 | if (boundary != null && boundary.depth > candidate.depth) { |
122 | return null; |
123 | } |
124 | return candidate; |
125 | } |
126 | |
127 | /// Returns the nearest ancestor widget of the given type `T` within the |
128 | /// current [LookupBoundary] of `context`. |
129 | /// |
130 | /// `T` must be the type of a concrete [Widget] subclass. |
131 | /// |
132 | /// This method behaves exactly like |
133 | /// [BuildContext.findAncestorWidgetOfExactType], except it only considers |
134 | /// [Widget]s of the specified type `T` between the provided [BuildContext] |
135 | /// and its closest [LookupBoundary] ancestor. [Widget]s past that |
136 | /// [LookupBoundary] are invisible to this method. The root of the tree is |
137 | /// treated as an implicit lookup boundary. |
138 | /// |
139 | /// {@macro flutter.widgets.BuildContext.findAncestorWidgetOfExactType} |
140 | static T? findAncestorWidgetOfExactType<T extends Widget>(BuildContext context) { |
141 | Element? target; |
142 | context.visitAncestorElements((Element ancestor) { |
143 | if (ancestor.widget.runtimeType == T) { |
144 | target = ancestor; |
145 | return false; |
146 | } |
147 | return ancestor.widget.runtimeType != LookupBoundary; |
148 | }); |
149 | return target?.widget as T?; |
150 | } |
151 | |
152 | /// Returns the [State] object of the nearest ancestor [StatefulWidget] widget |
153 | /// within the current [LookupBoundary] of `context` that is an instance of |
154 | /// the given type `T`. |
155 | /// |
156 | /// This method behaves exactly like |
157 | /// [BuildContext.findAncestorWidgetOfExactType], except it only considers |
158 | /// [State] objects of the specified type `T` between the provided |
159 | /// [BuildContext] and its closest [LookupBoundary] ancestor. [State] objects |
160 | /// past that [LookupBoundary] are invisible to this method. The root of the |
161 | /// tree is treated as an implicit lookup boundary. |
162 | /// |
163 | /// {@macro flutter.widgets.BuildContext.findAncestorStateOfType} |
164 | static T? findAncestorStateOfType<T extends State>(BuildContext context) { |
165 | StatefulElement? target; |
166 | context.visitAncestorElements((Element ancestor) { |
167 | if (ancestor is StatefulElement && ancestor.state is T) { |
168 | target = ancestor; |
169 | return false; |
170 | } |
171 | return ancestor.widget.runtimeType != LookupBoundary; |
172 | }); |
173 | return target?.state as T?; |
174 | } |
175 | |
176 | /// Returns the [State] object of the furthest ancestor [StatefulWidget] |
177 | /// widget within the current [LookupBoundary] of `context` that is an |
178 | /// instance of the given type `T`. |
179 | /// |
180 | /// This method behaves exactly like |
181 | /// [BuildContext.findRootAncestorStateOfType], except it considers the |
182 | /// closest [LookupBoundary] ancestor of `context` to be the root. [State] |
183 | /// objects past that [LookupBoundary] are invisible to this method. The root |
184 | /// of the tree is treated as an implicit lookup boundary. |
185 | /// |
186 | /// {@macro flutter.widgets.BuildContext.findRootAncestorStateOfType} |
187 | static T? findRootAncestorStateOfType<T extends State>(BuildContext context) { |
188 | StatefulElement? target; |
189 | context.visitAncestorElements((Element ancestor) { |
190 | if (ancestor is StatefulElement && ancestor.state is T) { |
191 | target = ancestor; |
192 | } |
193 | return ancestor.widget.runtimeType != LookupBoundary; |
194 | }); |
195 | return target?.state as T?; |
196 | } |
197 | |
198 | /// Returns the [RenderObject] object of the nearest ancestor |
199 | /// [RenderObjectWidget] widget within the current [LookupBoundary] of |
200 | /// `context` that is an instance of the given type `T`. |
201 | /// |
202 | /// This method behaves exactly like |
203 | /// [BuildContext.findAncestorRenderObjectOfType], except it only considers |
204 | /// [RenderObject]s of the specified type `T` between the provided |
205 | /// [BuildContext] and its closest [LookupBoundary] ancestor. [RenderObject]s |
206 | /// past that [LookupBoundary] are invisible to this method. The root of the |
207 | /// tree is treated as an implicit lookup boundary. |
208 | /// |
209 | /// {@macro flutter.widgets.BuildContext.findAncestorRenderObjectOfType} |
210 | static T? findAncestorRenderObjectOfType<T extends RenderObject>(BuildContext context) { |
211 | Element? target; |
212 | context.visitAncestorElements((Element ancestor) { |
213 | if (ancestor is RenderObjectElement && ancestor.renderObject is T) { |
214 | target = ancestor; |
215 | return false; |
216 | } |
217 | return ancestor.widget.runtimeType != LookupBoundary; |
218 | }); |
219 | return target?.renderObject as T?; |
220 | } |
221 | |
222 | /// Walks the ancestor chain, starting with the parent of the build context's |
223 | /// widget, invoking the argument for each ancestor until a [LookupBoundary] |
224 | /// or the root is reached. |
225 | /// |
226 | /// This method behaves exactly like [BuildContext.visitAncestorElements], |
227 | /// except it only walks the tree up to the closest [LookupBoundary] ancestor |
228 | /// of the provided context. The root of the tree is treated as an implicit |
229 | /// lookup boundary. |
230 | /// |
231 | /// {@macro flutter.widgets.BuildContext.visitAncestorElements} |
232 | static void visitAncestorElements(BuildContext context, ConditionalElementVisitor visitor) { |
233 | context.visitAncestorElements((Element ancestor) { |
234 | return visitor(ancestor) && ancestor.widget.runtimeType != LookupBoundary; |
235 | }); |
236 | } |
237 | |
238 | /// Walks the non-[LookupBoundary] child [Element]s of the provided |
239 | /// `context`. |
240 | /// |
241 | /// This method behaves exactly like [BuildContext.visitChildElements], |
242 | /// except it only visits children that are not a [LookupBoundary]. |
243 | /// |
244 | /// {@macro flutter.widgets.BuildContext.visitChildElements} |
245 | static void visitChildElements(BuildContext context, ElementVisitor visitor) { |
246 | context.visitChildElements((Element child) { |
247 | if (child.widget.runtimeType != LookupBoundary) { |
248 | visitor(child); |
249 | } |
250 | }); |
251 | } |
252 | |
253 | /// Returns true if a [LookupBoundary] is hiding the nearest |
254 | /// [Widget] of the specified type `T` from the provided [BuildContext]. |
255 | /// |
256 | /// This method throws when asserts are disabled. |
257 | static bool debugIsHidingAncestorWidgetOfExactType<T extends Widget>(BuildContext context) { |
258 | bool? result; |
259 | assert(() { |
260 | bool hiddenByBoundary = false; |
261 | bool ancestorFound = false; |
262 | context.visitAncestorElements((Element ancestor) { |
263 | if (ancestor.widget.runtimeType == T) { |
264 | ancestorFound = true; |
265 | return false; |
266 | } |
267 | hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary; |
268 | return true; |
269 | }); |
270 | result = ancestorFound & hiddenByBoundary; |
271 | return true; |
272 | } ()); |
273 | return result!; |
274 | } |
275 | |
276 | /// Returns true if a [LookupBoundary] is hiding the nearest [StatefulWidget] |
277 | /// with a [State] of the specified type `T` from the provided [BuildContext]. |
278 | /// |
279 | /// This method throws when asserts are disabled. |
280 | static bool debugIsHidingAncestorStateOfType<T extends State>(BuildContext context) { |
281 | bool? result; |
282 | assert(() { |
283 | bool hiddenByBoundary = false; |
284 | bool ancestorFound = false; |
285 | context.visitAncestorElements((Element ancestor) { |
286 | if (ancestor is StatefulElement && ancestor.state is T) { |
287 | ancestorFound = true; |
288 | return false; |
289 | } |
290 | hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary; |
291 | return true; |
292 | }); |
293 | result = ancestorFound & hiddenByBoundary; |
294 | return true; |
295 | } ()); |
296 | return result!; |
297 | } |
298 | |
299 | /// Returns true if a [LookupBoundary] is hiding the nearest |
300 | /// [RenderObjectWidget] with a [RenderObject] of the specified type `T` |
301 | /// from the provided [BuildContext]. |
302 | /// |
303 | /// This method throws when asserts are disabled. |
304 | static bool debugIsHidingAncestorRenderObjectOfType<T extends RenderObject>(BuildContext context) { |
305 | bool? result; |
306 | assert(() { |
307 | bool hiddenByBoundary = false; |
308 | bool ancestorFound = false; |
309 | context.visitAncestorElements((Element ancestor) { |
310 | if (ancestor is RenderObjectElement && ancestor.renderObject is T) { |
311 | ancestorFound = true; |
312 | return false; |
313 | } |
314 | hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary; |
315 | return true; |
316 | }); |
317 | result = ancestorFound & hiddenByBoundary; |
318 | return true; |
319 | } ()); |
320 | return result!; |
321 | } |
322 | |
323 | @override |
324 | bool updateShouldNotify(covariant InheritedWidget oldWidget) => false; |
325 | } |
326 | |