| // Copyright 2014 The Flutter 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 'framework.dart'; |
| |
| // Examples can assume: |
| // class MyWidget extends StatelessWidget { const MyWidget({super.key, required this.child}); final Widget child; @override Widget build(BuildContext context) => child; } |
| |
| /// A lookup boundary controls what entities are visible to descendants of the |
| /// boundary via the static lookup methods provided by the boundary. |
| /// |
| /// The static lookup methods of the boundary mirror the lookup methods by the |
| /// same name exposed on [BuildContext] and they can be used as direct |
| /// replacements. Unlike the methods on [BuildContext], these methods do not |
| /// find any ancestor entities of the closest [LookupBoundary] surrounding the |
| /// provided [BuildContext]. The root of the tree is an implicit lookup boundary. |
| /// |
| /// {@tool snippet} |
| /// In the example below, the [LookupBoundary.findAncestorWidgetOfExactType] |
| /// call returns null because the [LookupBoundary] "hides" `MyWidget` from the |
| /// [BuildContext] that was queried. |
| /// |
| /// ```dart |
| /// MyWidget( |
| /// child: LookupBoundary( |
| /// child: Builder( |
| /// builder: (BuildContext context) { |
| /// MyWidget? widget = LookupBoundary.findAncestorWidgetOfExactType<MyWidget>(context); |
| /// return Text('$widget'); // "null" |
| /// }, |
| /// ), |
| /// ), |
| /// ) |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// A [LookupBoundary] only affects the behavior of the static lookup methods |
| /// defined on the boundary. It does not affect the behavior of the lookup |
| /// methods defined on [BuildContext]. |
| /// |
| /// A [LookupBoundary] is rarely instantiated directly. They are inserted at |
| /// locations of the widget tree where the render tree diverges from the element |
| /// tree, which is rather uncommon. Such anomalies are created by |
| /// [RenderObjectElement]s that don't attach their [RenderObject] to the closest |
| /// ancestor [RenderObjectElement], e.g. because they bootstrap a separate |
| /// stand-alone render tree. |
| // TODO(goderbauer): Reference the View widget here once available. |
| /// This behavior breaks the assumption some widgets have about the structure of |
| /// the render tree: These widgets may try to reach out to an ancestor widget, |
| /// assuming that their associated [RenderObject]s are also ancestors, which due |
| /// to the anomaly may not be the case. At the point where the divergence in the |
| /// two trees is introduced, a [LookupBoundary] can be used to hide that ancestor |
| /// from the querying widget. |
| /// |
| /// As an example, [Material.of] relies on lookup boundaries to hide the |
| /// [Material] widget from certain descendant button widget. Buttons reach out |
| /// to their [Material] ancestor to draw ink splashes on its associated render |
| /// object. This only produces the desired effect if the button render object |
| /// is a descendant of the [Material] render object. If the element tree and |
| /// the render tree are not in sync due to anomalies described above, this may |
| /// not be the case. To avoid incorrect visuals, the [Material] relies on |
| /// lookup boundaries to hide itself from descendants in subtrees with such |
| /// anomalies. Those subtrees are expected to introduce their own [Material] |
| /// widget that buttons there can utilize without crossing a lookup boundary. |
| class LookupBoundary extends InheritedWidget { |
| /// Creates a [LookupBoundary]. |
| /// |
| /// A none-null [child] widget must be provided. |
| const LookupBoundary({super.key, required super.child}); |
| |
| /// Obtains the nearest widget of the given type `T` within the current |
| /// [LookupBoundary] of `context`, which must be the type of a concrete |
| /// [InheritedWidget] subclass, and registers the provided build `context` |
| /// with that widget such that when that widget changes (or a new widget of |
| /// that type is introduced, or the widget goes away), the build context is |
| /// rebuilt so that it can obtain new values from that widget. |
| /// |
| /// This method behaves exactly like |
| /// [BuildContext.dependOnInheritedWidgetOfExactType], except it only |
| /// considers [InheritedWidget]s of the specified type `T` between the |
| /// provided [BuildContext] and its closest [LookupBoundary] ancestor. |
| /// [InheritedWidget]s past that [LookupBoundary] are invisible to this |
| /// method. The root of the tree is treated as an implicit lookup boundary. |
| /// |
| /// {@macro flutter.widgets.BuildContext.dependOnInheritedWidgetOfExactType} |
| static T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>(BuildContext context, { Object? aspect }) { |
| // The following call makes sure that context depends on something so |
| // Element.didChangeDependencies is called when context moves in the tree |
| // even when requested dependency remains unfulfilled (i.e. null is |
| // returned). |
| context.dependOnInheritedWidgetOfExactType<LookupBoundary>(); |
| final InheritedElement? candidate = getElementForInheritedWidgetOfExactType<T>(context); |
| if (candidate == null) { |
| return null; |
| } |
| context.dependOnInheritedElement(candidate, aspect: aspect); |
| return candidate.widget as T; |
| } |
| |
| /// Obtains the element corresponding to the nearest widget of the given type |
| /// `T` within the current [LookupBoundary] of `context`. |
| /// |
| /// `T` must be the type of a concrete [InheritedWidget] subclass. Returns |
| /// null if no such element is found. |
| /// |
| /// This method behaves exactly like |
| /// [BuildContext.getElementForInheritedWidgetOfExactType], except it only |
| /// considers [InheritedWidget]s of the specified type `T` between the |
| /// provided [BuildContext] and its closest [LookupBoundary] ancestor. |
| /// [InheritedWidget]s past that [LookupBoundary] are invisible to this |
| /// method. The root of the tree is treated as an implicit lookup boundary. |
| /// |
| /// {@macro flutter.widgets.BuildContext.getElementForInheritedWidgetOfExactType} |
| static InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>(BuildContext context) { |
| final InheritedElement? candidate = context.getElementForInheritedWidgetOfExactType<T>(); |
| if (candidate == null) { |
| return null; |
| } |
| final Element? boundary = context.getElementForInheritedWidgetOfExactType<LookupBoundary>(); |
| if (boundary != null && boundary.depth > candidate.depth) { |
| return null; |
| } |
| return candidate; |
| } |
| |
| /// Returns the nearest ancestor widget of the given type `T` within the |
| /// current [LookupBoundary] of `context`. |
| /// |
| /// `T` must be the type of a concrete [Widget] subclass. |
| /// |
| /// This method behaves exactly like |
| /// [BuildContext.findAncestorWidgetOfExactType], except it only considers |
| /// [Widget]s of the specified type `T` between the provided [BuildContext] |
| /// and its closest [LookupBoundary] ancestor. [Widget]s past that |
| /// [LookupBoundary] are invisible to this method. The root of the tree is |
| /// treated as an implicit lookup boundary. |
| /// |
| /// {@macro flutter.widgets.BuildContext.findAncestorWidgetOfExactType} |
| static T? findAncestorWidgetOfExactType<T extends Widget>(BuildContext context) { |
| Element? target; |
| context.visitAncestorElements((Element ancestor) { |
| if (ancestor.widget.runtimeType == T) { |
| target = ancestor; |
| return false; |
| } |
| return ancestor.widget.runtimeType != LookupBoundary; |
| }); |
| return target?.widget as T?; |
| } |
| |
| /// Returns the [State] object of the nearest ancestor [StatefulWidget] widget |
| /// within the current [LookupBoundary] of `context` that is an instance of |
| /// the given type `T`. |
| /// |
| /// This method behaves exactly like |
| /// [BuildContext.findAncestorWidgetOfExactType], except it only considers |
| /// [State] objects of the specified type `T` between the provided |
| /// [BuildContext] and its closest [LookupBoundary] ancestor. [State] objects |
| /// past that [LookupBoundary] are invisible to this method. The root of the |
| /// tree is treated as an implicit lookup boundary. |
| /// |
| /// {@macro flutter.widgets.BuildContext.findAncestorStateOfType} |
| static T? findAncestorStateOfType<T extends State>(BuildContext context) { |
| StatefulElement? target; |
| context.visitAncestorElements((Element ancestor) { |
| if (ancestor is StatefulElement && ancestor.state is T) { |
| target = ancestor; |
| return false; |
| } |
| return ancestor.widget.runtimeType != LookupBoundary; |
| }); |
| return target?.state as T?; |
| } |
| |
| /// Returns the [State] object of the furthest ancestor [StatefulWidget] |
| /// widget within the current [LookupBoundary] of `context` that is an |
| /// instance of the given type `T`. |
| /// |
| /// This method behaves exactly like |
| /// [BuildContext.findRootAncestorStateOfType], except it considers the |
| /// closest [LookupBoundary] ancestor of `context` to be the root. [State] |
| /// objects past that [LookupBoundary] are invisible to this method. The root |
| /// of the tree is treated as an implicit lookup boundary. |
| /// |
| /// {@macro flutter.widgets.BuildContext.findRootAncestorStateOfType} |
| static T? findRootAncestorStateOfType<T extends State>(BuildContext context) { |
| StatefulElement? target; |
| context.visitAncestorElements((Element ancestor) { |
| if (ancestor is StatefulElement && ancestor.state is T) { |
| target = ancestor; |
| } |
| return ancestor.widget.runtimeType != LookupBoundary; |
| }); |
| return target?.state as T?; |
| } |
| |
| /// Returns the [RenderObject] object of the nearest ancestor |
| /// [RenderObjectWidget] widget within the current [LookupBoundary] of |
| /// `context` that is an instance of the given type `T`. |
| /// |
| /// This method behaves exactly like |
| /// [BuildContext.findAncestorRenderObjectOfType], except it only considers |
| /// [RenderObject]s of the specified type `T` between the provided |
| /// [BuildContext] and its closest [LookupBoundary] ancestor. [RenderObject]s |
| /// past that [LookupBoundary] are invisible to this method. The root of the |
| /// tree is treated as an implicit lookup boundary. |
| /// |
| /// {@macro flutter.widgets.BuildContext.findAncestorRenderObjectOfType} |
| static T? findAncestorRenderObjectOfType<T extends RenderObject>(BuildContext context) { |
| Element? target; |
| context.visitAncestorElements((Element ancestor) { |
| if (ancestor is RenderObjectElement && ancestor.renderObject is T) { |
| target = ancestor; |
| return false; |
| } |
| return ancestor.widget.runtimeType != LookupBoundary; |
| }); |
| return target?.renderObject as T?; |
| } |
| |
| /// Walks the ancestor chain, starting with the parent of the build context's |
| /// widget, invoking the argument for each ancestor until a [LookupBoundary] |
| /// or the root is reached. |
| /// |
| /// This method behaves exactly like [BuildContext.visitAncestorElements], |
| /// except it only walks the tree up to the closest [LookupBoundary] ancestor |
| /// of the provided context. The root of the tree is treated as an implicit |
| /// lookup boundary. |
| /// |
| /// {@macro flutter.widgets.BuildContext.visitAncestorElements} |
| static void visitAncestorElements(BuildContext context, ConditionalElementVisitor visitor) { |
| context.visitAncestorElements((Element ancestor) { |
| return visitor(ancestor) && ancestor.widget.runtimeType != LookupBoundary; |
| }); |
| } |
| |
| /// Walks the non-[LookupBoundary] child [Element]s of the provided |
| /// `context`. |
| /// |
| /// This method behaves exactly like [BuildContext.visitChildElements], |
| /// except it only visits children that are not a [LookupBoundary]. |
| /// |
| /// {@macro flutter.widgets.BuildContext.visitChildElements} |
| static void visitChildElements(BuildContext context, ElementVisitor visitor) { |
| context.visitChildElements((Element child) { |
| if (child.widget.runtimeType != LookupBoundary) { |
| visitor(child); |
| } |
| }); |
| } |
| |
| /// Returns true if a [LookupBoundary] is hiding the nearest |
| /// [Widget] of the specified type `T` from the provided [BuildContext]. |
| /// |
| /// This method throws when asserts are disabled. |
| static bool debugIsHidingAncestorWidgetOfExactType<T extends Widget>(BuildContext context) { |
| bool? result; |
| assert(() { |
| bool hiddenByBoundary = false; |
| bool ancestorFound = false; |
| context.visitAncestorElements((Element ancestor) { |
| if (ancestor.widget.runtimeType == T) { |
| ancestorFound = true; |
| return false; |
| } |
| hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary; |
| return true; |
| }); |
| result = ancestorFound & hiddenByBoundary; |
| return true; |
| } ()); |
| return result!; |
| } |
| |
| /// Returns true if a [LookupBoundary] is hiding the nearest [StatefulWidget] |
| /// with a [State] of the specified type `T` from the provided [BuildContext]. |
| /// |
| /// This method throws when asserts are disabled. |
| static bool debugIsHidingAncestorStateOfType<T extends State>(BuildContext context) { |
| bool? result; |
| assert(() { |
| bool hiddenByBoundary = false; |
| bool ancestorFound = false; |
| context.visitAncestorElements((Element ancestor) { |
| if (ancestor is StatefulElement && ancestor.state is T) { |
| ancestorFound = true; |
| return false; |
| } |
| hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary; |
| return true; |
| }); |
| result = ancestorFound & hiddenByBoundary; |
| return true; |
| } ()); |
| return result!; |
| } |
| |
| /// Returns true if a [LookupBoundary] is hiding the nearest |
| /// [RenderObjectWidget] with a [RenderObject] of the specified type `T` |
| /// from the provided [BuildContext]. |
| /// |
| /// This method throws when asserts are disabled. |
| static bool debugIsHidingAncestorRenderObjectOfType<T extends RenderObject>(BuildContext context) { |
| bool? result; |
| assert(() { |
| bool hiddenByBoundary = false; |
| bool ancestorFound = false; |
| context.visitAncestorElements((Element ancestor) { |
| if (ancestor is RenderObjectElement && ancestor.renderObject is T) { |
| ancestorFound = true; |
| return false; |
| } |
| hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary; |
| return true; |
| }); |
| result = ancestorFound & hiddenByBoundary; |
| return true; |
| } ()); |
| return result!; |
| } |
| |
| @override |
| bool updateShouldNotify(covariant InheritedWidget oldWidget) => false; |
| } |