diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index be59077bb51..5b9849ca8b1 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -77,10 +77,14 @@ class _BottomSheetRoute extends OverlayRoute { } void didPop(dynamic result) { - completer.complete(result); - performance.reverse().then((_) { + void finish() { super.didPop(result); // clear the overlay entries - }); + completer.complete(result); + } + if (performance.isDismissed) + finish(); + else + performance.reverse().then((_) { finish(); }); } String get debugLabel => '$runtimeType'; diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart index 6b59282210b..3d5d354248d 100644 --- a/packages/flutter/lib/src/material/snack_bar.dart +++ b/packages/flutter/lib/src/material/snack_bar.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter/animation.dart'; import 'package:flutter/widgets.dart'; @@ -92,12 +94,15 @@ class _SnackBar extends StatelessComponent { } class _SnackBarRoute extends TransitionRoute { + _SnackBarRoute({ Completer completer }) : super(completer: completer); + bool get opaque => false; Duration get transitionDuration => const Duration(milliseconds: 200); } -void showSnackBar({ BuildContext context, GlobalKey placeholderKey, Widget content, List actions }) { - _SnackBarRoute route = new _SnackBarRoute(); +Future showSnackBar({ BuildContext context, GlobalKey placeholderKey, Widget content, List actions }) { + final Completer completer = new Completer(); + _SnackBarRoute route = new _SnackBarRoute(completer: completer); _SnackBar snackBar = new _SnackBar( route: route, content: content, @@ -105,4 +110,7 @@ void showSnackBar({ BuildContext context, GlobalKey placeholde ); placeholderKey.currentState.child = snackBar; Navigator.of(context).pushEphemeral(route); + return completer.future.then((_) { + placeholderKey.currentState.child = null; + }); } diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 98ec21a3993..f3dadb97016 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter/animation.dart'; import 'basic.dart'; @@ -46,6 +48,10 @@ class OverlayRoute extends Route { // TODO(abarth): Should we add a type for the result? abstract class TransitionRoute extends OverlayRoute { + TransitionRoute({ this.completer }); + + final Completer completer; + Duration get transitionDuration; bool get opaque; @@ -86,7 +92,10 @@ abstract class TransitionRoute extends OverlayRoute { void didPop(dynamic result) { _result = result; - _performance.reverse(); + if (completer != null) + _performance.reverse().then((_) { completer.complete(_result); }); + else + _performance.reverse(); } String get debugLabel => '$runtimeType'; diff --git a/packages/unit/test/widget/bottom_sheet_test.dart b/packages/unit/test/widget/bottom_sheet_test.dart index 3f277ff1a66..a391afb62f4 100644 --- a/packages/unit/test/widget/bottom_sheet_test.dart +++ b/packages/unit/test/widget/bottom_sheet_test.dart @@ -8,6 +8,8 @@ void main() { test('Verify that a tap dismisses a modal BottomSheet', () { testWidgets((WidgetTester tester) { BuildContext context; + bool showBottomSheetThenCalled = false; + tester.pumpWidget(new MaterialApp( routes: { '/': (RouteArguments args) { @@ -20,7 +22,10 @@ void main() { tester.pump(); expect(tester.findText('BottomSheet'), isNull); - showModalBottomSheet(context: context, child: new Text('BottomSheet')); + showModalBottomSheet(context: context, child: new Text('BottomSheet')).then((_) { + showBottomSheetThenCalled = true; + }); + tester.pump(); // bottom sheet show animation starts tester.pump(new Duration(seconds: 1)); // animation done expect(tester.findText('BottomSheet'), isNotNull); @@ -29,7 +34,8 @@ void main() { tester.tap(tester.findText('BottomSheet')); tester.pump(); // bottom sheet dismiss animation starts tester.pump(new Duration(seconds: 1)); // animation done - tester.pump(new Duration(seconds: 2)); // rebuild frame + tester.pump(new Duration(seconds: 1)); // rebuild frame + expect(showBottomSheetThenCalled, isTrue); expect(tester.findText('BottomSheet'), isNull); showModalBottomSheet(context: context, child: new Text('BottomSheet')); @@ -41,7 +47,7 @@ void main() { tester.tapAt(new Point(20.0, 20.0)); tester.pump(); // bottom sheet dismiss animation starts tester.pump(new Duration(seconds: 1)); // animation done - tester.pump(new Duration(seconds: 2)); // rebuild frame + tester.pump(new Duration(seconds: 1)); // rebuild frame expect(tester.findText('BottomSheet'), isNull); }); }); @@ -50,6 +56,8 @@ void main() { testWidgets((WidgetTester tester) { GlobalKey _bottomSheetPlaceholderKey = new GlobalKey(); BuildContext context; + bool showBottomSheetThenCalled = false; + tester.pumpWidget(new MaterialApp( routes: { '/': (RouteArguments args) { @@ -69,7 +77,9 @@ void main() { context: context, child: new Container(child: new Text('BottomSheet'), margin: new EdgeDims.all(40.0)), placeholderKey: _bottomSheetPlaceholderKey - ); + ).then((_) { + showBottomSheetThenCalled = true; + }); expect(_bottomSheetPlaceholderKey.currentState.child, isNotNull); tester.pump(); // bottom sheet show animation starts @@ -79,7 +89,8 @@ void main() { tester.fling(tester.findText('BottomSheet'), const Offset(0.0, 20.0), 1000.0); tester.pump(); // bottom sheet dismiss animation starts tester.pump(new Duration(seconds: 1)); // animation done - tester.pump(new Duration(seconds: 2)); // rebuild frame without the bottom sheet + tester.pump(new Duration(seconds: 1)); // rebuild frame without the bottom sheet + expect(showBottomSheetThenCalled, isTrue); expect(tester.findText('BottomSheet'), isNull); expect(_bottomSheetPlaceholderKey.currentState.child, isNull); }); diff --git a/packages/unit/test/widget/snack_bar_test.dart b/packages/unit/test/widget/snack_bar_test.dart index f79ef54c03b..92a75ea89d2 100644 --- a/packages/unit/test/widget/snack_bar_test.dart +++ b/packages/unit/test/widget/snack_bar_test.dart @@ -9,17 +9,22 @@ void main() { String helloSnackBar = 'Hello SnackBar'; GlobalKey placeholderKey = new GlobalKey(); Key tapTarget = new Key('tap-target'); + BuildContext context; + bool showSnackBarThenCalled = false; tester.pumpWidget(new MaterialApp( routes: { '/': (RouteArguments args) { + context = args.context; return new GestureDetector( onTap: () { showSnackBar( context: args.context, placeholderKey: placeholderKey, content: new Text(helloSnackBar) - ); + ).then((_) { + showSnackBarThenCalled = true; + }); }, child: new Container( decoration: const BoxDecoration( @@ -36,10 +41,16 @@ void main() { )); tester.tap(tester.findElementByKey(tapTarget)); - expect(tester.findText(helloSnackBar), isNull); tester.pump(); expect(tester.findText(helloSnackBar), isNotNull); + + Navigator.of(context).pop(); + expect(tester.findText(helloSnackBar), isNotNull); + tester.pump(new Duration(seconds: 1)); + expect(showSnackBarThenCalled, isTrue); + expect(tester.findText(helloSnackBar), isNull); + expect(placeholderKey.currentState.child, isNull); }); }); }