diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 95f4bb07cbd..97f023e0a15 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -2449,6 +2449,7 @@ abstract class BuildContext { /// Returns the nearest widget of the given [InheritedWidget] subclass `T` or /// null if an appropriate ancestor is not found. /// + /// {@template flutter.widgets.BuildContext.getInheritedWidgetOfExactType} /// This method does not introduce a dependency the way that the more typical /// [dependOnInheritedWidgetOfExactType] does, so this context will not be /// rebuilt if the [InheritedWidget] changes. This function is meant for those @@ -2465,6 +2466,7 @@ abstract class BuildContext { /// that value is not cached and/or reused later. /// /// Calling this method is O(1) with a small constant factor. + /// {@endtemplate} T? getInheritedWidgetOfExactType(); /// Obtains the element corresponding to the nearest widget of the given type `T`, diff --git a/packages/flutter/lib/src/widgets/lookup_boundary.dart b/packages/flutter/lib/src/widgets/lookup_boundary.dart index 9cf91d52844..bf11566c0d1 100644 --- a/packages/flutter/lib/src/widgets/lookup_boundary.dart +++ b/packages/flutter/lib/src/widgets/lookup_boundary.dart @@ -105,6 +105,29 @@ class LookupBoundary extends InheritedWidget { return candidate.widget as T; } + /// 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. + /// + /// This method behaves exactly like + /// [BuildContext.getInheritedWidgetOfExactType], 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.getInheritedWidgetOfExactType} + static T? getInheritedWidgetOfExactType( + BuildContext context, { + Object? aspect, + }) { + final InheritedElement? candidate = getElementForInheritedWidgetOfExactType(context); + if (candidate == null) { + return null; + } + return candidate.widget as T; + } + /// Obtains the element corresponding to the nearest widget of the given type /// `T` within the current [LookupBoundary] of `context`. /// diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index a6eb67d0278..20c2f96c05a 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -421,7 +421,9 @@ class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> { child: _RenderTheaterMarker( theater: _theater, overlayEntryWidgetState: this, - child: widget.entry.builder(context), + // Use a Builder so that the `widget.entry.builder` can have access to + // _RenderTheaterMarker.of + child: Builder(builder: widget.entry.builder), ), ); } @@ -536,11 +538,11 @@ class Overlay extends StatefulWidget { /// OverlayState overlay = Overlay.of(context); /// ``` /// + /// {@template flutter.widgets.Overlay.of} /// If `rootOverlay` is set to true, the state from the furthest instance of /// this class is given instead. Useful for installing overlay entries above /// all subsequent instances of [Overlay]. - /// - /// This method can be expensive (it walks the element tree). + /// {@endtemplate} /// /// See also: /// @@ -599,21 +601,18 @@ class Overlay extends StatefulWidget { /// OverlayState? overlay = Overlay.maybeOf(context); /// ``` /// - /// If `rootOverlay` is set to true, the state from the furthest instance of - /// this class is given instead. Useful for installing overlay entries above - /// all subsequent instances of [Overlay]. - /// - /// This method can be expensive (it walks the element tree). + /// {@macro flutter.widgets.Overlay.of} /// /// See also: /// /// * [Overlay.of] for a similar function that returns a non-nullable result /// and throws if an [Overlay] is not found. - static OverlayState? maybeOf(BuildContext context, {bool rootOverlay = false}) { - return rootOverlay - ? LookupBoundary.findRootAncestorStateOfType(context) - : LookupBoundary.findAncestorStateOfType(context); + return _RenderTheaterMarker.maybeOf( + context, + targetRootOverlay: rootOverlay, + createDependency: false, + )?.overlayEntryWidgetState.widget.overlayState; } @override @@ -2162,18 +2161,7 @@ class _RenderTheaterMarker extends InheritedWidget { } static _RenderTheaterMarker of(BuildContext context, {bool targetRootOverlay = false}) { - final _RenderTheaterMarker? marker; - if (targetRootOverlay) { - final InheritedElement? ancestor = _rootRenderTheaterMarkerOf( - context.getElementForInheritedWidgetOfExactType<_RenderTheaterMarker>(), - ); - assert(ancestor == null || ancestor.widget is _RenderTheaterMarker); - marker = ancestor != null - ? context.dependOnInheritedElement(ancestor) as _RenderTheaterMarker? - : null; - } else { - marker = context.dependOnInheritedWidgetOfExactType<_RenderTheaterMarker>(); - } + final _RenderTheaterMarker? marker = maybeOf(context, targetRootOverlay: targetRootOverlay); if (marker != null) { return marker; } @@ -2192,6 +2180,32 @@ class _RenderTheaterMarker extends InheritedWidget { ]); } + static _RenderTheaterMarker? maybeOf( + BuildContext context, { + bool targetRootOverlay = false, + bool createDependency = true, + }) { + if (targetRootOverlay) { + final InheritedElement? ancestor = _rootRenderTheaterMarkerOf( + LookupBoundary.getElementForInheritedWidgetOfExactType<_RenderTheaterMarker>(context), + ); + assert(ancestor == null || ancestor.widget is _RenderTheaterMarker); + if (ancestor == null) { + return null; + } + if (createDependency) { + return context.dependOnInheritedElement(ancestor) as _RenderTheaterMarker; + } + return ancestor.widget as _RenderTheaterMarker; + } + + if (createDependency) { + return LookupBoundary.dependOnInheritedWidgetOfExactType<_RenderTheaterMarker>(context); + } + + return LookupBoundary.getInheritedWidgetOfExactType<_RenderTheaterMarker>(context); + } + static InheritedElement? _rootRenderTheaterMarkerOf(InheritedElement? theaterMarkerElement) { assert(theaterMarkerElement == null || theaterMarkerElement.widget is _RenderTheaterMarker); if (theaterMarkerElement == null) { @@ -2199,7 +2213,9 @@ class _RenderTheaterMarker extends InheritedWidget { } InheritedElement? ancestor; theaterMarkerElement.visitAncestorElements((Element element) { - ancestor = element.getElementForInheritedWidgetOfExactType<_RenderTheaterMarker>(); + ancestor = LookupBoundary.getElementForInheritedWidgetOfExactType<_RenderTheaterMarker>( + element, + ); return false; }); return ancestor == null ? theaterMarkerElement : _rootRenderTheaterMarkerOf(ancestor); diff --git a/packages/flutter/test/widgets/overlay_test.dart b/packages/flutter/test/widgets/overlay_test.dart index 5e5c15a3470..3efefb0594e 100644 --- a/packages/flutter/test/widgets/overlay_test.dart +++ b/packages/flutter/test/widgets/overlay_test.dart @@ -206,6 +206,13 @@ void main() { ); }); + test('of method calls getElementForInheritedWidgetOfExactType', () async { + final FakeBuildContext context = FakeBuildContext(); + expect(context.called, isFalse); + expect(Overlay.maybeOf(context), isNull); + expect(context.called, isTrue); + }); + testWidgets('insert top', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); final List buildOrder = []; @@ -1956,5 +1963,105 @@ class StatefulTestState extends State { } } +class FakeBuildContext extends BuildContext { + bool called = false; + + @override + bool get debugDoingBuild => throw UnimplementedError(); + + @override + InheritedWidget dependOnInheritedElement(InheritedElement ancestor, {Object? aspect}) { + throw UnimplementedError(); + } + + @override + T? dependOnInheritedWidgetOfExactType({Object? aspect}) { + throw UnimplementedError(); + } + + @override + DiagnosticsNode describeElement( + String name, { + DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty, + }) { + throw UnimplementedError(); + } + + @override + List describeMissingAncestor({required Type expectedAncestorType}) { + throw UnimplementedError(); + } + + @override + DiagnosticsNode describeOwnershipChain(String name) { + throw UnimplementedError(); + } + + @override + DiagnosticsNode describeWidget( + String name, { + DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty, + }) { + throw UnimplementedError(); + } + + @override + void dispatchNotification(Notification notification) {} + + @override + T? findAncestorRenderObjectOfType() { + throw UnimplementedError(); + } + + @override + T? findAncestorStateOfType>() { + throw UnimplementedError(); + } + + @override + T? findAncestorWidgetOfExactType() { + throw UnimplementedError(); + } + + @override + RenderObject? findRenderObject() { + throw UnimplementedError(); + } + + @override + T? findRootAncestorStateOfType>() { + throw UnimplementedError(); + } + + @override + InheritedElement? getElementForInheritedWidgetOfExactType() { + called = true; + return null; + } + + @override + T? getInheritedWidgetOfExactType() { + throw UnimplementedError(); + } + + @override + bool get mounted => throw UnimplementedError(); + + @override + BuildOwner? get owner => throw UnimplementedError(); + + @override + Size? get size => throw UnimplementedError(); + + @override + void visitAncestorElements(ConditionalElementVisitor visitor) {} + + @override + void visitChildElements(ElementVisitor visitor) {} + + @override + Widget get widget => throw UnimplementedError(); +} + /// This helper makes leak tracker forgiving the entry is not disposed. OverlayEntry _buildOverlayEntry(WidgetBuilder builder) => OverlayEntry(builder: builder);