Distinguish canceling a route pop from bubbling the pop up to the next level (#9165)

This commit is contained in:
Jason Simmons 2017-04-04 16:42:31 -07:00 committed by GitHub
parent 5cbd1a33a5
commit 3000c8bb59
4 changed files with 47 additions and 12 deletions

View file

@ -75,12 +75,12 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
return null;
}
Future<bool> _warnUserAboutInvalidData() {
Future<bool> _warnUserAboutInvalidData() async {
final FormState form = _formKey.currentState;
if (!_formWasEdited || form.validate())
return new Future<bool>.value(true);
if (form == null || !_formWasEdited || form.validate())
return true;
return showDialog<bool>(
return await showDialog<bool>(
context: context,
child: new AlertDialog(
title: new Text('This form has errors'),
@ -96,7 +96,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
),
],
),
);
) ?? false;
}
@override

View file

@ -13,6 +13,7 @@ import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';
import 'overlay.dart';
import 'routes.dart';
import 'ticker_provider.dart';
/// An abstraction for an entry managed by a [Navigator].
@ -72,7 +73,9 @@ abstract class Route<T> {
/// See also:
///
/// * [Form], which provides an `onWillPop` callback that uses this mechanism.
Future<bool> willPop() async => !isFirst;
Future<RoutePopDisposition> willPop() async {
return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop;
}
/// A request was made to pop this route. If the route can handle it
/// internally (e.g. because it has its own stack of internal state) then
@ -969,8 +972,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
Future<bool> maybePop([dynamic result]) async {
final Route<dynamic> route = _history.last;
assert(route._navigator == this);
if (await route.willPop() && mounted) {
pop(result);
final RoutePopDisposition disposition = await route.willPop();
if (disposition != RoutePopDisposition.bubble && mounted) {
if (disposition == RoutePopDisposition.pop)
pop(result);
return true;
}
return false;

View file

@ -317,8 +317,10 @@ abstract class LocalHistoryRoute<T> extends Route<T> {
}
@override
Future<bool> willPop() async {
return willHandlePopInternally || await super.willPop();
Future<RoutePopDisposition> willPop() async {
if (willHandlePopInternally)
return RoutePopDisposition.pop;
return await super.willPop();
}
@override
@ -385,6 +387,28 @@ class _ModalScopeStatus extends InheritedWidget {
}
}
/// Indicates whether the current route should be popped.
///
/// Used as the return value for [Route.willPop].
enum RoutePopDisposition {
/// Pop the route.
///
/// If [Route.willPop] returns [pop] then the back button will actually pop
/// the current route.
pop,
/// Do not pop the route.
///
/// If [Route.willPop] returns [doNotPop] then the back button will be ignored.
doNotPop,
/// Delegate this to the next level of navigation.
///
/// If [Route.willPop] return [bubble] then the back button will be handled
/// by the [SystemNavigator], which will usually close the application.
bubble,
}
/// Signature for a callback that verifies that it's OK to call [Navigator.pop].
///
/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback], and
@ -664,12 +688,12 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// * [removeScopedWillPopCallback], which removes a callback from the list
/// this method checks.
@override
Future<bool> willPop() async {
Future<RoutePopDisposition> willPop() async {
final _ModalScopeState scope = _scopeKey.currentState;
assert(scope != null);
for (WillPopCallback callback in new List<WillPopCallback>.from(scope._willPopCallbacks)) {
if (!await callback())
return false;
return RoutePopDisposition.doNotPop;
}
return await super.willPop();
}

View file

@ -113,6 +113,12 @@ void main() {
await tester.pump(const Duration(seconds: 1));
expect(find.text('Sample Page'), findsOneWidget);
// Use didPopRoute() to simulate the system back button. Check that
// didPopRoute() indicates that the notification was handled.
final dynamic widgetsAppState = tester.state(find.byType(WidgetsApp));
expect(await widgetsAppState.didPopRoute(), isTrue);
expect(find.text('Sample Page'), findsOneWidget);
willPopValue = true;
await tester.tap(find.byTooltip('Back'));
await tester.pump();