diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index 84c976ec9e..cb79e557e1 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -182,6 +182,7 @@ class _DropDownRoute extends PopupRoute<_DropDownRouteResult> { ModalPosition getPosition(BuildContext context) { RenderBox overlayBox = Overlay.of(context).context.findRenderObject(); + assert(overlayBox != null); // can't be null; routes get inserted by Navigator which has its own Overlay Size overlaySize = overlayBox.size; RelativeRect menuRect = new RelativeRect.fromSize(rect, overlaySize); return new ModalPosition( diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index 35757bc25c..66292f64db 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -46,6 +46,7 @@ class Tooltip extends StatefulComponent { assert(preferBelow != null); assert(fadeDuration != null); assert(showDuration != null); + assert(child != null); } final String message; @@ -150,7 +151,7 @@ class _TooltipState extends State { preferBelow: config.preferBelow ); }); - Overlay.of(context).insert(_entry); + Overlay.of(context, debugRequiredFor: config).insert(_entry); } _timer?.cancel(); if (_controller.status != AnimationStatus.completed) { @@ -175,7 +176,7 @@ class _TooltipState extends State { } Widget build(BuildContext context) { - assert(Overlay.of(context) != null); + assert(Overlay.of(context, debugRequiredFor: config) != null); return new GestureDetector( behavior: HitTestBehavior.opaque, onLongPress: showTooltip, diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 015094ae77..8f129d099a 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -234,7 +234,7 @@ class _DraggableState extends State> { _activeCount += 1; }); return new _DragAvatar( - overlay: Overlay.of(context), + overlay: Overlay.of(context, debugRequiredFor: config), data: config.data, initialPosition: position, dragStartPoint: dragStartPoint, @@ -249,6 +249,7 @@ class _DraggableState extends State> { } Widget build(BuildContext context) { + assert(Overlay.of(context, debugRequiredFor: config) != null); final bool canDrag = config.maxSimultaneousDrags == null || _activeCount < config.maxSimultaneousDrags; final bool showChild = _activeCount == 0 || config.childWhenDragging == null; diff --git a/packages/flutter/lib/src/widgets/mimic.dart b/packages/flutter/lib/src/widgets/mimic.dart index 5fb4818009..ec0d5769dd 100644 --- a/packages/flutter/lib/src/widgets/mimic.dart +++ b/packages/flutter/lib/src/widgets/mimic.dart @@ -137,6 +137,9 @@ class Mimic extends StatelessComponent { } /// A widget that can be copied by a [Mimic]. +/// +/// This widget's State, [MimicableState], contains an API for initiating the +/// mimic operation. class Mimicable extends StatefulComponent { Mimicable({ Key key, this.child }) : super(key: key); @@ -193,8 +196,7 @@ class MimicableState extends State { /// had when the mimicking process started and (2) the child will be /// placed in the enclosing overlay. MimicOverlayEntry liftToOverlay() { - OverlayState overlay = Overlay.of(context); - assert(overlay != null); // You need an overlay to lift into. + OverlayState overlay = Overlay.of(context, debugRequiredFor: config); MimicOverlayEntry entry = new MimicOverlayEntry._(startMimic()); overlay.insert(entry._overlayEntry); return entry; diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index 015248b7f6..6b9bad218c 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -71,7 +71,32 @@ class Overlay extends StatefulComponent { final List initialEntries; /// The state from the closest instance of this class that encloses the given context. - static OverlayState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher()); + /// + /// In checked mode, if the [debugRequiredFor] argument is provided then this + /// function will assert that an overlay was found and will throw an exception + /// if not. The exception attempts to explain that the calling [Widget] (the + /// one given by the [debugRequiredFor] argument) needs an [Overlay] to be + /// present to function. + static OverlayState of(BuildContext context, { Widget debugRequiredFor }) { + OverlayState result = context.ancestorStateOfType(const TypeMatcher()); + assert(() { + if (debugRequiredFor != null && result == null) { + String additional = context.widget != debugRequiredFor + ? '\nThe context from which that widget was searching for an overlay was:\n $context' + : ''; + throw new WidgetError( + 'No Overlay widget found.\n' + '${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.\n' + 'The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.\n' + 'The specific widget that failed to find an overlay was:\n' + ' $debugRequiredFor' + '$additional' + ); + } + return true; + }); + return result; + } OverlayState createState() => new OverlayState(); }