| 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>( |
| 92 | BuildContext context, { |
| 93 | Object? aspect, |
| 94 | }) { |
| 95 | // The following call makes sure that context depends on something so |
| 96 | // Element.didChangeDependencies is called when context moves in the tree |
| 97 | // even when requested dependency remains unfulfilled (i.e. null is |
| 98 | // returned). |
| 99 | context.dependOnInheritedWidgetOfExactType<LookupBoundary>(); |
| 100 | final InheritedElement? candidate = getElementForInheritedWidgetOfExactType<T>(context); |
| 101 | if (candidate == null) { |
| 102 | return null; |
| 103 | } |
| 104 | context.dependOnInheritedElement(candidate, aspect: aspect); |
| 105 | return candidate.widget as T; |
| 106 | } |
| 107 | |
| 108 | /// Obtains the element corresponding to the nearest widget of the given type |
| 109 | /// `T` within the current [LookupBoundary] of `context`. |
| 110 | /// |
| 111 | /// `T` must be the type of a concrete [InheritedWidget] subclass. Returns |
| 112 | /// null if no such element is found. |
| 113 | /// |
| 114 | /// This method behaves exactly like |
| 115 | /// [BuildContext.getElementForInheritedWidgetOfExactType], except it only |
| 116 | /// considers [InheritedWidget]s of the specified type `T` between the |
| 117 | /// provided [BuildContext] and its closest [LookupBoundary] ancestor. |
| 118 | /// [InheritedWidget]s past that [LookupBoundary] are invisible to this |
| 119 | /// method. The root of the tree is treated as an implicit lookup boundary. |
| 120 | /// |
| 121 | /// {@macro flutter.widgets.BuildContext.getElementForInheritedWidgetOfExactType} |
| 122 | static InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>( |
| 123 | BuildContext context, |
| 124 | ) { |
| 125 | final InheritedElement? candidate = context.getElementForInheritedWidgetOfExactType<T>(); |
| 126 | if (candidate == null) { |
| 127 | return null; |
| 128 | } |
| 129 | final Element? boundary = context.getElementForInheritedWidgetOfExactType<LookupBoundary>(); |
| 130 | if (boundary != null && boundary.depth > candidate.depth) { |
| 131 | return null; |
| 132 | } |
| 133 | return candidate; |
| 134 | } |
| 135 | |
| 136 | /// Returns the nearest ancestor widget of the given type `T` within the |
| 137 | /// current [LookupBoundary] of `context`. |
| 138 | /// |
| 139 | /// `T` must be the type of a concrete [Widget] subclass. |
| 140 | /// |
| 141 | /// This method behaves exactly like |
| 142 | /// [BuildContext.findAncestorWidgetOfExactType], except it only considers |
| 143 | /// [Widget]s of the specified type `T` between the provided [BuildContext] |
| 144 | /// and its closest [LookupBoundary] ancestor. [Widget]s past that |
| 145 | /// [LookupBoundary] are invisible to this method. The root of the tree is |
| 146 | /// treated as an implicit lookup boundary. |
| 147 | /// |
| 148 | /// {@macro flutter.widgets.BuildContext.findAncestorWidgetOfExactType} |
| 149 | static T? findAncestorWidgetOfExactType<T extends Widget>(BuildContext context) { |
| 150 | Element? target; |
| 151 | context.visitAncestorElements((Element ancestor) { |
| 152 | if (ancestor.widget.runtimeType == T) { |
| 153 | target = ancestor; |
| 154 | return false; |
| 155 | } |
| 156 | return ancestor.widget.runtimeType != LookupBoundary; |
| 157 | }); |
| 158 | return target?.widget as T?; |
| 159 | } |
| 160 | |
| 161 | /// Returns the [State] object of the nearest ancestor [StatefulWidget] widget |
| 162 | /// within the current [LookupBoundary] of `context` that is an instance of |
| 163 | /// the given type `T`. |
| 164 | /// |
| 165 | /// This method behaves exactly like |
| 166 | /// [BuildContext.findAncestorWidgetOfExactType], except it only considers |
| 167 | /// [State] objects of the specified type `T` between the provided |
| 168 | /// [BuildContext] and its closest [LookupBoundary] ancestor. [State] objects |
| 169 | /// past that [LookupBoundary] are invisible to this method. The root of the |
| 170 | /// tree is treated as an implicit lookup boundary. |
| 171 | /// |
| 172 | /// {@macro flutter.widgets.BuildContext.findAncestorStateOfType} |
| 173 | static T? findAncestorStateOfType<T extends State>(BuildContext context) { |
| 174 | StatefulElement? target; |
| 175 | context.visitAncestorElements((Element ancestor) { |
| 176 | if (ancestor is StatefulElement && ancestor.state is T) { |
| 177 | target = ancestor; |
| 178 | return false; |
| 179 | } |
| 180 | return ancestor.widget.runtimeType != LookupBoundary; |
| 181 | }); |
| 182 | return target?.state as T?; |
| 183 | } |
| 184 | |
| 185 | /// Returns the [State] object of the furthest ancestor [StatefulWidget] |
| 186 | /// widget within the current [LookupBoundary] of `context` that is an |
| 187 | /// instance of the given type `T`. |
| 188 | /// |
| 189 | /// This method behaves exactly like |
| 190 | /// [BuildContext.findRootAncestorStateOfType], except it considers the |
| 191 | /// closest [LookupBoundary] ancestor of `context` to be the root. [State] |
| 192 | /// objects past that [LookupBoundary] are invisible to this method. The root |
| 193 | /// of the tree is treated as an implicit lookup boundary. |
| 194 | /// |
| 195 | /// {@macro flutter.widgets.BuildContext.findRootAncestorStateOfType} |
| 196 | static T? findRootAncestorStateOfType<T extends State>(BuildContext context) { |
| 197 | StatefulElement? target; |
| 198 | context.visitAncestorElements((Element ancestor) { |
| 199 | if (ancestor is StatefulElement && ancestor.state is T) { |
| 200 | target = ancestor; |
| 201 | } |
| 202 | return ancestor.widget.runtimeType != LookupBoundary; |
| 203 | }); |
| 204 | return target?.state as T?; |
| 205 | } |
| 206 | |
| 207 | /// Returns the [RenderObject] object of the nearest ancestor |
| 208 | /// [RenderObjectWidget] widget within the current [LookupBoundary] of |
| 209 | /// `context` that is an instance of the given type `T`. |
| 210 | /// |
| 211 | /// This method behaves exactly like |
| 212 | /// [BuildContext.findAncestorRenderObjectOfType], except it only considers |
| 213 | /// [RenderObject]s of the specified type `T` between the provided |
| 214 | /// [BuildContext] and its closest [LookupBoundary] ancestor. [RenderObject]s |
| 215 | /// past that [LookupBoundary] are invisible to this method. The root of the |
| 216 | /// tree is treated as an implicit lookup boundary. |
| 217 | /// |
| 218 | /// {@macro flutter.widgets.BuildContext.findAncestorRenderObjectOfType} |
| 219 | static T? findAncestorRenderObjectOfType<T extends RenderObject>(BuildContext context) { |
| 220 | Element? target; |
| 221 | context.visitAncestorElements((Element ancestor) { |
| 222 | if (ancestor is RenderObjectElement && ancestor.renderObject is T) { |
| 223 | target = ancestor; |
| 224 | return false; |
| 225 | } |
| 226 | return ancestor.widget.runtimeType != LookupBoundary; |
| 227 | }); |
| 228 | return target?.renderObject as T?; |
| 229 | } |
| 230 | |
| 231 | /// Walks the ancestor chain, starting with the parent of the build context's |
| 232 | /// widget, invoking the argument for each ancestor until a [LookupBoundary] |
| 233 | /// or the root is reached. |
| 234 | /// |
| 235 | /// This method behaves exactly like [BuildContext.visitAncestorElements], |
| 236 | /// except it only walks the tree up to the closest [LookupBoundary] ancestor |
| 237 | /// of the provided context. The root of the tree is treated as an implicit |
| 238 | /// lookup boundary. |
| 239 | /// |
| 240 | /// {@macro flutter.widgets.BuildContext.visitAncestorElements} |
| 241 | static void visitAncestorElements(BuildContext context, ConditionalElementVisitor visitor) { |
| 242 | context.visitAncestorElements((Element ancestor) { |
| 243 | return visitor(ancestor) && ancestor.widget.runtimeType != LookupBoundary; |
| 244 | }); |
| 245 | } |
| 246 | |
| 247 | /// Walks the non-[LookupBoundary] child [Element]s of the provided |
| 248 | /// `context`. |
| 249 | /// |
| 250 | /// This method behaves exactly like [BuildContext.visitChildElements], |
| 251 | /// except it only visits children that are not a [LookupBoundary]. |
| 252 | /// |
| 253 | /// {@macro flutter.widgets.BuildContext.visitChildElements} |
| 254 | static void visitChildElements(BuildContext context, ElementVisitor visitor) { |
| 255 | context.visitChildElements((Element child) { |
| 256 | if (child.widget.runtimeType != LookupBoundary) { |
| 257 | visitor(child); |
| 258 | } |
| 259 | }); |
| 260 | } |
| 261 | |
| 262 | /// Returns true if a [LookupBoundary] is hiding the nearest |
| 263 | /// [Widget] of the specified type `T` from the provided [BuildContext]. |
| 264 | /// |
| 265 | /// This method throws when asserts are disabled. |
| 266 | static bool debugIsHidingAncestorWidgetOfExactType<T extends Widget>(BuildContext context) { |
| 267 | bool? result; |
| 268 | assert(() { |
| 269 | bool hiddenByBoundary = false; |
| 270 | bool ancestorFound = false; |
| 271 | context.visitAncestorElements((Element ancestor) { |
| 272 | if (ancestor.widget.runtimeType == T) { |
| 273 | ancestorFound = true; |
| 274 | return false; |
| 275 | } |
| 276 | hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary; |
| 277 | return true; |
| 278 | }); |
| 279 | result = ancestorFound & hiddenByBoundary; |
| 280 | return true; |
| 281 | }()); |
| 282 | return result!; |
| 283 | } |
| 284 | |
| 285 | /// Returns true if a [LookupBoundary] is hiding the nearest [StatefulWidget] |
| 286 | /// with a [State] of the specified type `T` from the provided [BuildContext]. |
| 287 | /// |
| 288 | /// This method throws when asserts are disabled. |
| 289 | static bool debugIsHidingAncestorStateOfType<T extends State>(BuildContext context) { |
| 290 | bool? result; |
| 291 | assert(() { |
| 292 | bool hiddenByBoundary = false; |
| 293 | bool ancestorFound = false; |
| 294 | context.visitAncestorElements((Element ancestor) { |
| 295 | if (ancestor is StatefulElement && ancestor.state is T) { |
| 296 | ancestorFound = true; |
| 297 | return false; |
| 298 | } |
| 299 | hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary; |
| 300 | return true; |
| 301 | }); |
| 302 | result = ancestorFound & hiddenByBoundary; |
| 303 | return true; |
| 304 | }()); |
| 305 | return result!; |
| 306 | } |
| 307 | |
| 308 | /// Returns true if a [LookupBoundary] is hiding the nearest |
| 309 | /// [RenderObjectWidget] with a [RenderObject] of the specified type `T` |
| 310 | /// from the provided [BuildContext]. |
| 311 | /// |
| 312 | /// This method throws when asserts are disabled. |
| 313 | static bool debugIsHidingAncestorRenderObjectOfType<T extends RenderObject>( |
| 314 | BuildContext context, |
| 315 | ) { |
| 316 | bool? result; |
| 317 | assert(() { |
| 318 | bool hiddenByBoundary = false; |
| 319 | bool ancestorFound = false; |
| 320 | context.visitAncestorElements((Element ancestor) { |
| 321 | if (ancestor is RenderObjectElement && ancestor.renderObject is T) { |
| 322 | ancestorFound = true; |
| 323 | return false; |
| 324 | } |
| 325 | hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary; |
| 326 | return true; |
| 327 | }); |
| 328 | result = ancestorFound & hiddenByBoundary; |
| 329 | return true; |
| 330 | }()); |
| 331 | return result!; |
| 332 | } |
| 333 | |
| 334 | @override |
| 335 | bool updateShouldNotify(covariant InheritedWidget oldWidget) => false; |
| 336 | } |
| 337 | |