diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index a18cd8808d..6dc44e97e3 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -34,7 +34,7 @@ import 'ticker_provider.dart'; /// follows the user's finger across the screen after the drag begins. Using the /// overlay to display the drag avatar lets the avatar float over the other /// widgets in the app. As the user's finger moves, draggable calls -/// [markNeedsBuild] on the overlay entry to cause it to rebuild. It its build, +/// [markNeedsBuild] on the overlay entry to cause it to rebuild. In its build, /// the entry includes a [Positioned] with its top and left property set to /// position the drag avatar near the user's finger. When the drag is over, /// [Draggable] removes the entry from the overlay to remove the drag avatar @@ -209,7 +209,7 @@ class Overlay extends StatefulWidget { /// [OverlayState] is initialized. /// /// Rather than creating an overlay, consider using the overlay that is - /// created by the [WidgetsApp] or the [MaterialApp] for the application. + /// created by the [Navigator] in a [WidgetsApp] or a [MaterialApp] for the application. const Overlay({ Key key, this.initialEntries = const [], @@ -308,18 +308,7 @@ class OverlayState extends State with TickerProviderStateMixin { /// /// It is an error to specify both `above` and `below`. void insert(OverlayEntry entry, { OverlayEntry below, OverlayEntry above }) { - assert( - above == null || below == null, - 'Only one of `above` and `below` may be specified.', - ); - assert( - above == null || (above._overlay == this && _entries.contains(above)), - 'The provided entry for `above` is not present in the Overlay.', - ); - assert( - below == null || (below._overlay == this && _entries.contains(below)), - 'The provided entry for `below` is not present in the Overlay.', - ); + assert(_debugVerifyInsertPosition(above, below)); assert(!_entries.contains(entry), 'The specified entry is already present in the Overlay.'); assert(entry._overlay == null, 'The specified entry is already present in another Overlay.'); entry._overlay = this; @@ -336,18 +325,7 @@ class OverlayState extends State with TickerProviderStateMixin { /// /// It is an error to specify both `above` and `below`. void insertAll(Iterable entries, { OverlayEntry below, OverlayEntry above }) { - assert( - above == null || below == null, - 'Only one of `above` and `below` may be specified.', - ); - assert( - above == null || (above._overlay == this && _entries.contains(above)), - 'The provided entry for `above` is not present in the Overlay.', - ); - assert( - below == null || (below._overlay == this && _entries.contains(below)), - 'The provided entry for `below` is not present in the Overlay.', - ); + assert(_debugVerifyInsertPosition(above, below)); assert( entries.every((OverlayEntry entry) => !_entries.contains(entry)), 'One or more of the specified entries are already present in the Overlay.' @@ -367,6 +345,22 @@ class OverlayState extends State with TickerProviderStateMixin { }); } + bool _debugVerifyInsertPosition(OverlayEntry above, OverlayEntry below, { Iterable newEntries }) { + assert( + above == null || below == null, + 'Only one of `above` and `below` may be specified.', + ); + assert( + above == null || (above._overlay == this && _entries.contains(above) && (newEntries?.contains(above) ?? true)), + 'The provided entry used for `above` must be present in the Overlay${newEntries != null ? ' and in the `newEntriesList`' : ''}.', + ); + assert( + below == null || (below._overlay == this && _entries.contains(below) && (newEntries?.contains(below) ?? true)), + 'The provided entry used for `below` must be present in the Overlay${newEntries != null ? ' and in the `newEntriesList`' : ''}.', + ); + return true; + } + /// Remove all the entries listed in the given iterable, then reinsert them /// into the overlay in the given order. /// @@ -386,18 +380,7 @@ class OverlayState extends State with TickerProviderStateMixin { /// It is an error to specify both `above` and `below`. void rearrange(Iterable newEntries, { OverlayEntry below, OverlayEntry above }) { final List newEntriesList = newEntries is List ? newEntries : newEntries.toList(growable: false); - assert( - above == null || below == null, - 'Only one of `above` and `below` may be specified.', - ); - assert( - above == null || (above._overlay == this && _entries.contains(above) && newEntriesList.contains(above)), - 'The entry used for `above` must be in the Overlay and in the `newEntriesList`.' - ); - assert( - below == null || (below._overlay == this && _entries.contains(below) && newEntriesList.contains(below)), - 'The entry used for `below` must be in the Overlay and in the `newEntriesList`.' - ); + assert(_debugVerifyInsertPosition(above, below, newEntries: newEntriesList)); assert( newEntriesList.every((OverlayEntry entry) => entry._overlay == null || entry._overlay == this), 'One or more of the specified entries are already present in another Overlay.' diff --git a/packages/flutter/test/widgets/overlay_test.dart b/packages/flutter/test/widgets/overlay_test.dart index f3f791830b..874e000073 100644 --- a/packages/flutter/test/widgets/overlay_test.dart +++ b/packages/flutter/test/widgets/overlay_test.dart @@ -619,6 +619,87 @@ void main() { expect(buildOrder, [3, 4, 1, 2, 0]); }); + testWidgets('debugVerifyInsertPosition', (WidgetTester tester) async { + final GlobalKey overlayKey = GlobalKey(); + OverlayEntry base; + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + key: overlayKey, + initialEntries: [ + base = OverlayEntry( + builder: (BuildContext context) { + return Container(); + }, + ), + ], + ), + ), + ); + + final OverlayState overlay = overlayKey.currentState as OverlayState; + + try { + overlay.insert( + OverlayEntry(builder: (BuildContext context) { + return Container(); + }), + above: OverlayEntry( + builder: (BuildContext context) { + return Container(); + }, + ), + below: OverlayEntry( + builder: (BuildContext context) { + return Container(); + }, + ), + ); + } catch (e) { + expect(e, isAssertionError); + expect(e.message, 'Only one of `above` and `below` may be specified.'); + } + + expect(() => overlay.insert( + OverlayEntry(builder: (BuildContext context) { + return Container(); + }), + above: base, + ), isNot(throwsAssertionError)); + + try { + overlay.insert( + OverlayEntry(builder: (BuildContext context) { + return Container(); + }), + above: OverlayEntry( + builder: (BuildContext context) { + return Container(); + }, + ), + ); + } catch (e) { + expect(e, isAssertionError); + expect(e.message, 'The provided entry used for `above` must be present in the Overlay.'); + } + + try { + overlay.rearrange([base], above: OverlayEntry( + builder: (BuildContext context) { + return Container(); + }, + )); + + } catch (e) { + expect(e, isAssertionError); + expect(e.message, 'The provided entry used for `above` must be present in the Overlay and in the `newEntriesList`.'); + } + + await tester.pump(); + }); + testWidgets('OverlayState.of() called without Overlay being exist', (WidgetTester tester) async { await tester.pumpWidget( Directionality(