Commit ea3864b1 authored by Ian Hickson's avatar Ian Hickson

Fix Scrollable.of()

Make ancestorStateOfType() and ancestorRenderObjectOfType() support
subclassing.

Fixes https://github.com/flutter/flutter/issues/1087
parent 19fb068d
...@@ -8,7 +8,7 @@ import 'material.dart'; ...@@ -8,7 +8,7 @@ import 'material.dart';
bool debugCheckHasMaterial(BuildContext context) { bool debugCheckHasMaterial(BuildContext context) {
assert(() { assert(() {
if (context.widget is Material || context.ancestorWidgetOfType(Material) != null) if (context.widget is Material || context.ancestorWidgetOfExactType(Material) != null)
return true; return true;
Element element = context; Element element = context;
debugPrint('${context.widget} needs to be placed inside a Material widget. Ownership chain:\n${element.debugGetOwnershipChain(10)}'); debugPrint('${context.widget} needs to be placed inside a Material widget. Ownership chain:\n${element.debugGetOwnershipChain(10)}');
......
...@@ -20,7 +20,7 @@ class IconTheme extends InheritedWidget { ...@@ -20,7 +20,7 @@ class IconTheme extends InheritedWidget {
/// The data from the closest instance of this class that encloses the given context. /// The data from the closest instance of this class that encloses the given context.
static IconThemeData of(BuildContext context) { static IconThemeData of(BuildContext context) {
IconTheme result = context.inheritFromWidgetOfType(IconTheme); IconTheme result = context.inheritFromWidgetOfExactType(IconTheme);
return result?.data; return result?.data;
} }
......
...@@ -95,7 +95,7 @@ class Material extends StatefulComponent { ...@@ -95,7 +95,7 @@ class Material extends StatefulComponent {
/// The ink controller from the closest instance of this class that encloses the given context. /// The ink controller from the closest instance of this class that encloses the given context.
static MaterialInkController of(BuildContext context) { static MaterialInkController of(BuildContext context) {
final RenderInkFeatures result = context.ancestorRenderObjectOfType(RenderInkFeatures); final RenderInkFeatures result = context.ancestorRenderObjectOfType(const TypeMatcher<RenderInkFeatures>());
return result; return result;
} }
......
...@@ -27,7 +27,7 @@ class ButtonTheme extends InheritedWidget { ...@@ -27,7 +27,7 @@ class ButtonTheme extends InheritedWidget {
/// ///
/// Defaults to [ButtonColor.normal] if none exists. /// Defaults to [ButtonColor.normal] if none exists.
static ButtonColor of(BuildContext context) { static ButtonColor of(BuildContext context) {
ButtonTheme result = context.inheritFromWidgetOfType(ButtonTheme); ButtonTheme result = context.inheritFromWidgetOfExactType(ButtonTheme);
return result?.color ?? ButtonColor.normal; return result?.color ?? ButtonColor.normal;
} }
......
...@@ -110,7 +110,7 @@ class Scaffold extends StatefulComponent { ...@@ -110,7 +110,7 @@ class Scaffold extends StatefulComponent {
final Widget drawer; final Widget drawer;
/// The state from the closest instance of this class that encloses the given context. /// The state from the closest instance of this class that encloses the given context.
static ScaffoldState of(BuildContext context) => context.ancestorStateOfType(ScaffoldState); static ScaffoldState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<ScaffoldState>());
ScaffoldState createState() => new ScaffoldState(); ScaffoldState createState() => new ScaffoldState();
} }
......
...@@ -26,7 +26,7 @@ class Theme extends InheritedWidget { ...@@ -26,7 +26,7 @@ class Theme extends InheritedWidget {
/// ///
/// Defaults to the fallback theme data if none exists. /// Defaults to the fallback theme data if none exists.
static ThemeData of(BuildContext context) { static ThemeData of(BuildContext context) {
Theme theme = context.inheritFromWidgetOfType(Theme); Theme theme = context.inheritFromWidgetOfExactType(Theme);
return theme?.data ?? _kFallbackTheme; return theme?.data ?? _kFallbackTheme;
} }
......
...@@ -1411,7 +1411,7 @@ class DefaultTextStyle extends InheritedWidget { ...@@ -1411,7 +1411,7 @@ class DefaultTextStyle extends InheritedWidget {
/// The style from the closest instance of this class that encloses the given context. /// The style from the closest instance of this class that encloses the given context.
static TextStyle of(BuildContext context) { static TextStyle of(BuildContext context) {
DefaultTextStyle result = context.inheritFromWidgetOfType(DefaultTextStyle); DefaultTextStyle result = context.inheritFromWidgetOfExactType(DefaultTextStyle);
return result?.style; return result?.style;
} }
...@@ -1737,7 +1737,7 @@ class DefaultAssetBundle extends InheritedWidget { ...@@ -1737,7 +1737,7 @@ class DefaultAssetBundle extends InheritedWidget {
/// The bundle from the closest instance of this class that encloses the given context. /// The bundle from the closest instance of this class that encloses the given context.
static AssetBundle of(BuildContext context) { static AssetBundle of(BuildContext context) {
DefaultAssetBundle result = context.inheritFromWidgetOfType(DefaultAssetBundle); DefaultAssetBundle result = context.inheritFromWidgetOfExactType(DefaultAssetBundle);
return result?.bundle; return result?.bundle;
} }
......
...@@ -89,7 +89,7 @@ class Focus extends StatefulComponent { ...@@ -89,7 +89,7 @@ class Focus extends StatefulComponent {
assert(context.widget != null); assert(context.widget != null);
assert(context.widget.key != null); assert(context.widget.key != null);
assert(context.widget.key is GlobalKey); assert(context.widget.key is GlobalKey);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope); _FocusScope focusScope = context.inheritFromWidgetOfExactType(_FocusScope);
if (focusScope != null) { if (focusScope != null) {
if (autofocus) if (autofocus)
focusScope._setFocusedWidgetIfUnset(context.widget.key); focusScope._setFocusedWidgetIfUnset(context.widget.key);
...@@ -113,7 +113,7 @@ class Focus extends StatefulComponent { ...@@ -113,7 +113,7 @@ class Focus extends StatefulComponent {
assert(context != null); assert(context != null);
assert(context.widget != null); assert(context.widget != null);
assert(context.widget is Focus); assert(context.widget is Focus);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope); _FocusScope focusScope = context.inheritFromWidgetOfExactType(_FocusScope);
if (focusScope != null) { if (focusScope != null) {
assert(context.widget.key != null); assert(context.widget.key != null);
if (autofocus) if (autofocus)
...@@ -131,7 +131,7 @@ class Focus extends StatefulComponent { ...@@ -131,7 +131,7 @@ class Focus extends StatefulComponent {
/// called from event listeners, e.g. in response to a finger tap or tab key. /// called from event listeners, e.g. in response to a finger tap or tab key.
static void moveTo(GlobalKey key) { static void moveTo(GlobalKey key) {
assert(key.currentContext != null); assert(key.currentContext != null);
_FocusScope focusScope = key.currentContext.ancestorWidgetOfType(_FocusScope); _FocusScope focusScope = key.currentContext.ancestorWidgetOfExactType(_FocusScope);
if (focusScope != null) if (focusScope != null)
focusScope.focusState._setFocusedWidget(key); focusScope.focusState._setFocusedWidget(key);
} }
...@@ -144,7 +144,7 @@ class Focus extends StatefulComponent { ...@@ -144,7 +144,7 @@ class Focus extends StatefulComponent {
static void moveScopeTo(GlobalKey key) { static void moveScopeTo(GlobalKey key) {
assert(key.currentWidget is Focus); assert(key.currentWidget is Focus);
assert(key.currentContext != null); assert(key.currentContext != null);
_FocusScope focusScope = key.currentContext.ancestorWidgetOfType(_FocusScope); _FocusScope focusScope = key.currentContext.ancestorWidgetOfExactType(_FocusScope);
if (focusScope != null) if (focusScope != null)
focusScope.focusState._setFocusedScope(key); focusScope.focusState._setFocusedScope(key);
} }
......
...@@ -198,6 +198,11 @@ class GlobalObjectKey extends GlobalKey { ...@@ -198,6 +198,11 @@ class GlobalObjectKey extends GlobalKey {
String toString() => '[$runtimeType ${value.runtimeType}(${value.hashCode})]'; String toString() => '[$runtimeType ${value.runtimeType}(${value.hashCode})]';
} }
/// This class is a work-around for the "is" operator not accepting a variable value as its right operand
class TypeMatcher<T> {
const TypeMatcher();
bool check(dynamic object) => object is T;
}
// WIDGETS // WIDGETS
...@@ -585,10 +590,10 @@ typedef void ElementVisitor(Element element); ...@@ -585,10 +590,10 @@ typedef void ElementVisitor(Element element);
abstract class BuildContext { abstract class BuildContext {
Widget get widget; Widget get widget;
RenderObject findRenderObject(); RenderObject findRenderObject();
InheritedWidget inheritFromWidgetOfType(Type targetType); InheritedWidget inheritFromWidgetOfExactType(Type targetType);
Widget ancestorWidgetOfType(Type targetType); Widget ancestorWidgetOfExactType(Type targetType);
State ancestorStateOfType(Type targetType); State ancestorStateOfType(TypeMatcher matcher);
RenderObject ancestorRenderObjectOfType(Type targetType); RenderObject ancestorRenderObjectOfType(TypeMatcher matcher);
void visitAncestorElements(bool visitor(Element element)); void visitAncestorElements(bool visitor(Element element));
void visitChildElements(void visitor(Element element)); void visitChildElements(void visitor(Element element));
} }
...@@ -876,24 +881,24 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -876,24 +881,24 @@ abstract class Element<T extends Widget> implements BuildContext {
RenderObject findRenderObject() => renderObject; RenderObject findRenderObject() => renderObject;
Set<Type> _dependencies; Set<Type> _dependencies;
InheritedWidget inheritFromWidgetOfType(Type targetType) { InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
if (_dependencies == null) if (_dependencies == null)
_dependencies = new Set<Type>(); _dependencies = new Set<Type>();
_dependencies.add(targetType); _dependencies.add(targetType);
return ancestorWidgetOfType(targetType); return ancestorWidgetOfExactType(targetType);
} }
Widget ancestorWidgetOfType(Type targetType) { Widget ancestorWidgetOfExactType(Type targetType) {
Element ancestor = _parent; Element ancestor = _parent;
while (ancestor != null && ancestor.widget.runtimeType != targetType) while (ancestor != null && ancestor.widget.runtimeType != targetType)
ancestor = ancestor._parent; ancestor = ancestor._parent;
return ancestor?.widget; return ancestor?.widget;
} }
State ancestorStateOfType(Type targetType) { State ancestorStateOfType(TypeMatcher matcher) {
Element ancestor = _parent; Element ancestor = _parent;
while (ancestor != null) { while (ancestor != null) {
if (ancestor is StatefulComponentElement && ancestor.state.runtimeType == targetType) if (ancestor is StatefulComponentElement && matcher.check(ancestor.state))
break; break;
ancestor = ancestor._parent; ancestor = ancestor._parent;
} }
...@@ -901,10 +906,10 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -901,10 +906,10 @@ abstract class Element<T extends Widget> implements BuildContext {
return statefulAncestor?.state; return statefulAncestor?.state;
} }
RenderObject ancestorRenderObjectOfType(Type targetType) { RenderObject ancestorRenderObjectOfType(TypeMatcher matcher) {
Element ancestor = _parent; Element ancestor = _parent;
while (ancestor != null) { while (ancestor != null) {
if (ancestor is RenderObjectElement && ancestor.renderObject.runtimeType == targetType) if (ancestor is RenderObjectElement && matcher.check(ancestor.renderObject))
break; break;
ancestor = ancestor._parent; ancestor = ancestor._parent;
} }
......
...@@ -20,7 +20,7 @@ class LocaleQuery<T extends LocaleQueryData> extends InheritedWidget { ...@@ -20,7 +20,7 @@ class LocaleQuery<T extends LocaleQueryData> extends InheritedWidget {
/// The data from the closest instance of this class that encloses the given context. /// The data from the closest instance of this class that encloses the given context.
static LocaleQueryData of(BuildContext context) { static LocaleQueryData of(BuildContext context) {
LocaleQuery query = context.inheritFromWidgetOfType(LocaleQuery); LocaleQuery query = context.inheritFromWidgetOfExactType(LocaleQuery);
return query == null ? null : query.data; return query == null ? null : query.data;
} }
......
...@@ -58,7 +58,7 @@ class MediaQuery extends InheritedWidget { ...@@ -58,7 +58,7 @@ class MediaQuery extends InheritedWidget {
/// When that information changes, your widget will be scheduled to be rebuilt, /// When that information changes, your widget will be scheduled to be rebuilt,
/// keeping your widget up-to-date. /// keeping your widget up-to-date.
static MediaQueryData of(BuildContext context) { static MediaQueryData of(BuildContext context) {
MediaQuery query = context.inheritFromWidgetOfType(MediaQuery); MediaQuery query = context.inheritFromWidgetOfExactType(MediaQuery);
return query == null ? null : query.data; return query == null ? null : query.data;
} }
......
...@@ -111,7 +111,7 @@ class MimicOverlayEntry { ...@@ -111,7 +111,7 @@ class MimicOverlayEntry {
} }
} }
RenderBox stack = context.ancestorRenderObjectOfType(RenderStack); RenderBox stack = context.ancestorRenderObjectOfType(const TypeMatcher<RenderStack>());
// TODO(abarth): Handle the case where the transform here isn't just a translation. // TODO(abarth): Handle the case where the transform here isn't just a translation.
Point localPosition = stack == null ? globalPosition: stack.globalToLocal(globalPosition); Point localPosition = stack == null ? globalPosition: stack.globalToLocal(globalPosition);
return new Positioned( return new Positioned(
......
...@@ -144,7 +144,7 @@ class Navigator extends StatefulComponent { ...@@ -144,7 +144,7 @@ class Navigator extends StatefulComponent {
} }
static bool canPop(BuildContext context) { static bool canPop(BuildContext context) {
NavigatorState navigator = context.ancestorStateOfType(NavigatorState); NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
return navigator != null && navigator.canPop(); return navigator != null && navigator.canPop();
} }
...@@ -156,7 +156,7 @@ class Navigator extends StatefulComponent { ...@@ -156,7 +156,7 @@ class Navigator extends StatefulComponent {
} }
static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) { static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
NavigatorState navigator = context.ancestorStateOfType(NavigatorState); NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
navigator.openTransaction(callback); navigator.openTransaction(callback);
} }
......
...@@ -69,7 +69,7 @@ class Overlay extends StatefulComponent { ...@@ -69,7 +69,7 @@ class Overlay extends StatefulComponent {
final List<OverlayEntry> initialEntries; final List<OverlayEntry> initialEntries;
/// The state from the closest instance of this class that encloses the given context. /// The state from the closest instance of this class that encloses the given context.
static OverlayState of(BuildContext context) => context.ancestorStateOfType(OverlayState); static OverlayState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<OverlayState>());
OverlayState createState() => new OverlayState(); OverlayState createState() => new OverlayState();
} }
......
...@@ -79,7 +79,7 @@ class PageStorage extends StatelessComponent { ...@@ -79,7 +79,7 @@ class PageStorage extends StatelessComponent {
/// ///
/// Returns null if none exists. /// Returns null if none exists.
static PageStorageBucket of(BuildContext context) { static PageStorageBucket of(BuildContext context) {
PageStorage widget = context.ancestorWidgetOfType(PageStorage); PageStorage widget = context.ancestorWidgetOfExactType(PageStorage);
return widget?.bucket; return widget?.bucket;
} }
......
...@@ -388,7 +388,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -388,7 +388,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// ///
/// Returns null if the given context is not associated with a modal route. /// Returns null if the given context is not associated with a modal route.
static ModalRoute of(BuildContext context) { static ModalRoute of(BuildContext context) {
_ModalScopeStatus widget = context.inheritFromWidgetOfType(_ModalScopeStatus); _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
return widget?.route; return widget?.route;
} }
......
...@@ -59,7 +59,7 @@ abstract class Scrollable extends StatefulComponent { ...@@ -59,7 +59,7 @@ abstract class Scrollable extends StatefulComponent {
/// The state from the closest instance of this class that encloses the given context. /// The state from the closest instance of this class that encloses the given context.
static ScrollableState of(BuildContext context) { static ScrollableState of(BuildContext context) {
return context.ancestorStateOfType(ScrollableState); return context.ancestorStateOfType(const TypeMatcher<ScrollableState>());
} }
/// Scrolls the closest enclosing scrollable to make the given context visible. /// Scrolls the closest enclosing scrollable to make the given context visible.
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:test/test.dart'; import 'package:test/test.dart' hide TypeMatcher;
class TestTransition extends TransitionComponent { class TestTransition extends TransitionComponent {
TestTransition({ TestTransition({
...@@ -99,7 +99,7 @@ void main() { ...@@ -99,7 +99,7 @@ void main() {
) )
); );
NavigatorState navigator = insideKey.currentContext.ancestorStateOfType(NavigatorState); NavigatorState navigator = insideKey.currentContext.ancestorStateOfType(const TypeMatcher<NavigatorState>());
expect(state(), equals('BC')); // transition ->1 is at 1.0 expect(state(), equals('BC')); // transition ->1 is at 1.0
......
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