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