Unverified Commit a50bdcfd authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add `getInheritedWidgetOfExactType` to `BuildContext` (#121276)

* Add getInheritedWidgetOfExactType to BuildContext

* Use the new API in all the places

* Fix doc error

* Review Changes
parent 04872598
...@@ -319,7 +319,7 @@ abstract class FocusTraversalPolicy with Diagnosticable { ...@@ -319,7 +319,7 @@ abstract class FocusTraversalPolicy with Diagnosticable {
Iterable<FocusNode> sortDescendants(Iterable<FocusNode> descendants, FocusNode currentNode); Iterable<FocusNode> sortDescendants(Iterable<FocusNode> descendants, FocusNode currentNode);
_FocusTraversalGroupScope? _getMarker(BuildContext? context) { _FocusTraversalGroupScope? _getMarker(BuildContext? context) {
return context?.getElementForInheritedWidgetOfExactType<_FocusTraversalGroupScope>()?.widget as _FocusTraversalGroupScope?; return context?.getInheritedWidgetOfExactType<_FocusTraversalGroupScope>();
} }
// Sort all descendants, taking into account the FocusTraversalGroup // Sort all descendants, taking into account the FocusTraversalGroup
...@@ -941,7 +941,7 @@ class _ReadingOrderSortData with Diagnosticable { ...@@ -941,7 +941,7 @@ class _ReadingOrderSortData with Diagnosticable {
// Find the directionality in force for a build context without creating a // Find the directionality in force for a build context without creating a
// dependency. // dependency.
static TextDirection? _findDirectionality(BuildContext context) { static TextDirection? _findDirectionality(BuildContext context) {
return (context.getElementForInheritedWidgetOfExactType<Directionality>()?.widget as Directionality?)?.textDirection; return context.getInheritedWidgetOfExactType<Directionality>()?.textDirection;
} }
/// Finds the common Directional ancestor of an entire list of groups. /// Finds the common Directional ancestor of an entire list of groups.
...@@ -1439,7 +1439,7 @@ class FocusTraversalOrder extends InheritedWidget { ...@@ -1439,7 +1439,7 @@ class FocusTraversalOrder extends InheritedWidget {
/// If no [FocusTraversalOrder] ancestor exists, or the order is null, this /// If no [FocusTraversalOrder] ancestor exists, or the order is null, this
/// will assert in debug mode, and throw an exception in release mode. /// will assert in debug mode, and throw an exception in release mode.
static FocusOrder of(BuildContext context) { static FocusOrder of(BuildContext context) {
final FocusTraversalOrder? marker = context.getElementForInheritedWidgetOfExactType<FocusTraversalOrder>()?.widget as FocusTraversalOrder?; final FocusTraversalOrder? marker = context.getInheritedWidgetOfExactType<FocusTraversalOrder>();
assert(() { assert(() {
if (marker == null) { if (marker == null) {
throw FlutterError( throw FlutterError(
...@@ -1464,7 +1464,7 @@ class FocusTraversalOrder extends InheritedWidget { ...@@ -1464,7 +1464,7 @@ class FocusTraversalOrder extends InheritedWidget {
/// ///
/// If no [FocusTraversalOrder] ancestor exists, or the order is null, returns null. /// If no [FocusTraversalOrder] ancestor exists, or the order is null, returns null.
static FocusOrder? maybeOf(BuildContext context) { static FocusOrder? maybeOf(BuildContext context) {
final FocusTraversalOrder? marker = context.getElementForInheritedWidgetOfExactType<FocusTraversalOrder>()?.widget as FocusTraversalOrder?; final FocusTraversalOrder? marker = context.getInheritedWidgetOfExactType<FocusTraversalOrder>();
return marker?.order; return marker?.order;
} }
......
...@@ -2216,10 +2216,13 @@ abstract class BuildContext { ...@@ -2216,10 +2216,13 @@ abstract class BuildContext {
/// be called apply to this method as well. /// be called apply to this method as well.
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }); InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect });
/// Obtains the nearest widget of the given type `T`, which must be the type of a /// Returns the nearest widget of the given type `T` and creates a dependency
/// concrete [InheritedWidget] subclass, and registers this build context with /// on it, or null if no appropriate widget is found.
/// that widget such that when that widget changes (or a new widget of that ///
/// type is introduced, or the widget goes away), this build context is /// The widget found will be a concrete [InheritedWidget] subclass, and
/// calling [dependOnInheritedWidgetOfExactType] registers this build context
/// with the returned widget. When that widget changes (or a new widget of
/// that type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget. /// rebuilt so that it can obtain new values from that widget.
/// ///
/// {@template flutter.widgets.BuildContext.dependOnInheritedWidgetOfExactType} /// {@template flutter.widgets.BuildContext.dependOnInheritedWidgetOfExactType}
...@@ -2230,8 +2233,8 @@ abstract class BuildContext { ...@@ -2230,8 +2233,8 @@ abstract class BuildContext {
/// [State.initState] methods, because those methods would not get called /// [State.initState] methods, because those methods would not get called
/// again if the inherited value were to change. To ensure that the widget /// again if the inherited value were to change. To ensure that the widget
/// correctly updates itself when the inherited value changes, only call this /// correctly updates itself when the inherited value changes, only call this
/// (directly or indirectly) from build methods, layout and paint callbacks, or /// (directly or indirectly) from build methods, layout and paint callbacks,
/// from [State.didChangeDependencies]. /// or from [State.didChangeDependencies].
/// ///
/// This method should not be called from [State.dispose] because the element /// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that /// tree is no longer stable at that time. To refer to an ancestor from that
...@@ -2240,8 +2243,8 @@ abstract class BuildContext { ...@@ -2240,8 +2243,8 @@ abstract class BuildContext {
/// whenever the widget is removed from the tree. /// whenever the widget is removed from the tree.
/// ///
/// It is also possible to call this method from interaction event handlers /// It is also possible to call this method from interaction event handlers
/// (e.g. gesture callbacks) or timers, to obtain a value once, if that value /// (e.g. gesture callbacks) or timers, to obtain a value once, as long as
/// is not going to be cached and reused later. /// that value is not cached and/or reused later.
/// ///
/// Calling this method is O(1) with a small constant factor, but will lead to /// Calling this method is O(1) with a small constant factor, but will lead to
/// the widget being rebuilt more often. /// the widget being rebuilt more often.
...@@ -2252,13 +2255,34 @@ abstract class BuildContext { ...@@ -2252,13 +2255,34 @@ abstract class BuildContext {
/// the widget or one of its ancestors is moved (for example, because an /// the widget or one of its ancestors is moved (for example, because an
/// ancestor is added or removed). /// ancestor is added or removed).
/// ///
/// The [aspect] parameter is only used when `T` is an /// The [aspect] parameter is only used when `T` is an [InheritedWidget]
/// [InheritedWidget] subclasses that supports partial updates, like /// subclass that supports partial updates, like [InheritedModel]. It
/// [InheritedModel]. It specifies what "aspect" of the inherited /// specifies what "aspect" of the inherited widget this context depends on,
/// widget this context depends on. /// where the meaning of the aspect is determined by the specific subclass.
/// {@endtemplate} /// {@endtemplate}
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect }); T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
/// Returns the nearest widget of the given [InheritedWidget] subclass `T` or
/// null if an appropriate ancestor is not found.
///
/// 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
/// uncommon use cases where a dependency is undesirable.
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor in [State.didChangeDependencies].
/// It is safe to use this method from [State.deactivate], which is called
/// whenever the widget is removed from the tree.
///
/// It is also possible to call this method from interaction event handlers
/// (e.g. gesture callbacks) or timers, to obtain a value once, as long as
/// that value is not cached and/or reused later.
///
/// Calling this method is O(1) with a small constant factor.
T? getInheritedWidgetOfExactType<T extends InheritedWidget>();
/// Obtains the element corresponding to the nearest widget of the given type `T`, /// Obtains the element corresponding to the nearest widget of the given type `T`,
/// which must be the type of a concrete [InheritedWidget] subclass. /// which must be the type of a concrete [InheritedWidget] subclass.
/// ///
...@@ -4111,7 +4135,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -4111,7 +4135,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
// implementation to decide whether to rebuild based on whether we had // implementation to decide whether to rebuild based on whether we had
// dependencies here. // dependencies here.
} }
_inheritedWidgets = null; _inheritedElements = null;
_lifecycleState = _ElementLifecycle.inactive; _lifecycleState = _ElementLifecycle.inactive;
} }
...@@ -4306,7 +4330,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -4306,7 +4330,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
return null; return null;
} }
PersistentHashMap<Type, InheritedElement>? _inheritedWidgets; PersistentHashMap<Type, InheritedElement>? _inheritedElements;
Set<InheritedElement>? _dependencies; Set<InheritedElement>? _dependencies;
bool _hadUnsatisfiedDependencies = false; bool _hadUnsatisfiedDependencies = false;
...@@ -4347,7 +4371,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -4347,7 +4371,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
@override @override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) { T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup()); assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T]; final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
if (ancestor != null) { if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T; return dependOnInheritedElement(ancestor, aspect: aspect) as T;
} }
...@@ -4355,10 +4379,15 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -4355,10 +4379,15 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
return null; return null;
} }
@override
T? getInheritedWidgetOfExactType<T extends InheritedWidget>() {
return getElementForInheritedWidgetOfExactType<T>()?.widget as T?;
}
@override @override
InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() { InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
assert(_debugCheckStateIsActiveForAncestorLookup()); assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T]; final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
return ancestor; return ancestor;
} }
...@@ -4378,7 +4407,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -4378,7 +4407,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
void _updateInheritance() { void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active); assert(_lifecycleState == _ElementLifecycle.active);
_inheritedWidgets = _parent?._inheritedWidgets; _inheritedElements = _parent?._inheritedElements;
} }
@override @override
...@@ -5374,8 +5403,8 @@ class InheritedElement extends ProxyElement { ...@@ -5374,8 +5403,8 @@ class InheritedElement extends ProxyElement {
void _updateInheritance() { void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active); assert(_lifecycleState == _ElementLifecycle.active);
final PersistentHashMap<Type, InheritedElement> incomingWidgets = final PersistentHashMap<Type, InheritedElement> incomingWidgets =
_parent?._inheritedWidgets ?? const PersistentHashMap<Type, InheritedElement>.empty(); _parent?._inheritedElements ?? const PersistentHashMap<Type, InheritedElement>.empty();
_inheritedWidgets = incomingWidgets.put(widget.runtimeType, this); _inheritedElements = incomingWidgets.put(widget.runtimeType, this);
} }
@override @override
......
...@@ -359,7 +359,7 @@ class Scrollable extends StatefulWidget { ...@@ -359,7 +359,7 @@ class Scrollable extends StatefulWidget {
/// If there is no [Scrollable] in the widget tree above the [context], this /// If there is no [Scrollable] in the widget tree above the [context], this
/// method returns false. /// method returns false.
static bool recommendDeferredLoadingForContext(BuildContext context) { static bool recommendDeferredLoadingForContext(BuildContext context) {
final _ScrollableScope? widget = context.getElementForInheritedWidgetOfExactType<_ScrollableScope>()?.widget as _ScrollableScope?; final _ScrollableScope? widget = context.getInheritedWidgetOfExactType<_ScrollableScope>();
if (widget == null) { if (widget == null) {
return false; return false;
} }
......
...@@ -121,7 +121,7 @@ class SharedAppData extends StatefulWidget { ...@@ -121,7 +121,7 @@ class SharedAppData extends StatefulWidget {
/// The type parameter `K` is the type of the value's keyword and `V` /// The type parameter `K` is the type of the value's keyword and `V`
/// is the type of the value. /// is the type of the value.
static void setValue<K extends Object, V>(BuildContext context, K key, V value) { static void setValue<K extends Object, V>(BuildContext context, K key, V value) {
final _SharedAppModel? model = context.getElementForInheritedWidgetOfExactType<_SharedAppModel>()?.widget as _SharedAppModel?; final _SharedAppModel? model = context.getInheritedWidgetOfExactType<_SharedAppModel>();
assert(_debugHasSharedAppData(model, context, 'setValue')); assert(_debugHasSharedAppData(model, context, 'setValue'));
model!.sharedAppDataState.setValue<K, V>(key, value); model!.sharedAppDataState.setValue<K, V>(key, value);
} }
......
...@@ -95,7 +95,7 @@ class TickerMode extends StatefulWidget { ...@@ -95,7 +95,7 @@ class TickerMode extends StatefulWidget {
/// In the absence of a [TickerMode] widget, this function returns a /// In the absence of a [TickerMode] widget, this function returns a
/// [ValueNotifier], whose [ValueNotifier.value] is always true. /// [ValueNotifier], whose [ValueNotifier.value] is always true.
static ValueNotifier<bool> getNotifier(BuildContext context) { static ValueNotifier<bool> getNotifier(BuildContext context) {
final _EffectiveTickerMode? widget = context.getElementForInheritedWidgetOfExactType<_EffectiveTickerMode>()?.widget as _EffectiveTickerMode?; final _EffectiveTickerMode? widget = context.getInheritedWidgetOfExactType<_EffectiveTickerMode>();
return widget?.notifier ?? ValueNotifier<bool>(true); return widget?.notifier ?? ValueNotifier<bool>(true);
} }
......
...@@ -423,6 +423,32 @@ void main() { ...@@ -423,6 +423,32 @@ void main() {
expect(buildCount, equals(2)); expect(buildCount, equals(2));
}); });
testWidgets("BuildContext.getInheritedWidgetOfExactType doesn't create a dependency", (WidgetTester tester) async {
int buildCount = 0;
final GlobalKey<void> inheritedKey = GlobalKey();
final ChangeNotifier notifier = ChangeNotifier();
final Widget builder = Builder(
builder: (BuildContext context) {
expect(context.getInheritedWidgetOfExactType<ChangeNotifierInherited>(), equals(inheritedKey.currentWidget));
buildCount += 1;
return Container();
},
);
final Widget inner = ChangeNotifierInherited(
key: inheritedKey,
notifier: notifier,
child: builder,
);
await tester.pumpWidget(inner);
expect(buildCount, equals(1));
notifier.notifyListeners();
await tester.pumpWidget(inner);
expect(buildCount, equals(1));
});
testWidgets('initState() dependency on Inherited asserts', (WidgetTester tester) async { testWidgets('initState() dependency on Inherited asserts', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/5491 // This is a regression test for https://github.com/flutter/flutter/issues/5491
bool exceptionCaught = false; bool exceptionCaught = false;
......
...@@ -1360,7 +1360,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -1360,7 +1360,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
routeInformationProvider: provider, routeInformationProvider: provider,
routeInformationParser: CustomRouteInformationParser((RouteInformation information, BuildContext context) { routeInformationParser: CustomRouteInformationParser((RouteInformation information, BuildContext context) {
parserCalled = true; parserCalled = true;
final DefaultTextStyle style = context.getElementForInheritedWidgetOfExactType<DefaultTextStyle>()!.widget as DefaultTextStyle; final DefaultTextStyle style = context.getInheritedWidgetOfExactType<DefaultTextStyle>()!;
return RouteInformation(location: '${style.maxLines}'); return RouteInformation(location: '${style.maxLines}');
}), }),
routerDelegate: SimpleRouterDelegate( routerDelegate: SimpleRouterDelegate(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment