Commit 3000c8bb authored by Jason Simmons's avatar Jason Simmons Committed by GitHub

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

parent 5cbd1a33
...@@ -75,12 +75,12 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> { ...@@ -75,12 +75,12 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
return null; return null;
} }
Future<bool> _warnUserAboutInvalidData() { Future<bool> _warnUserAboutInvalidData() async {
final FormState form = _formKey.currentState; final FormState form = _formKey.currentState;
if (!_formWasEdited || form.validate()) if (form == null || !_formWasEdited || form.validate())
return new Future<bool>.value(true); return true;
return showDialog<bool>( return await showDialog<bool>(
context: context, context: context,
child: new AlertDialog( child: new AlertDialog(
title: new Text('This form has errors'), title: new Text('This form has errors'),
...@@ -96,7 +96,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> { ...@@ -96,7 +96,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
), ),
], ],
), ),
); ) ?? false;
} }
@override @override
......
...@@ -13,6 +13,7 @@ import 'focus_manager.dart'; ...@@ -13,6 +13,7 @@ import 'focus_manager.dart';
import 'focus_scope.dart'; import 'focus_scope.dart';
import 'framework.dart'; import 'framework.dart';
import 'overlay.dart'; import 'overlay.dart';
import 'routes.dart';
import 'ticker_provider.dart'; import 'ticker_provider.dart';
/// An abstraction for an entry managed by a [Navigator]. /// An abstraction for an entry managed by a [Navigator].
...@@ -72,7 +73,9 @@ abstract class Route<T> { ...@@ -72,7 +73,9 @@ abstract class Route<T> {
/// See also: /// See also:
/// ///
/// * [Form], which provides an `onWillPop` callback that uses this mechanism. /// * [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 /// 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 /// internally (e.g. because it has its own stack of internal state) then
...@@ -969,7 +972,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -969,7 +972,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
Future<bool> maybePop([dynamic result]) async { Future<bool> maybePop([dynamic result]) async {
final Route<dynamic> route = _history.last; final Route<dynamic> route = _history.last;
assert(route._navigator == this); assert(route._navigator == this);
if (await route.willPop() && mounted) { final RoutePopDisposition disposition = await route.willPop();
if (disposition != RoutePopDisposition.bubble && mounted) {
if (disposition == RoutePopDisposition.pop)
pop(result); pop(result);
return true; return true;
} }
......
...@@ -317,8 +317,10 @@ abstract class LocalHistoryRoute<T> extends Route<T> { ...@@ -317,8 +317,10 @@ abstract class LocalHistoryRoute<T> extends Route<T> {
} }
@override @override
Future<bool> willPop() async { Future<RoutePopDisposition> willPop() async {
return willHandlePopInternally || await super.willPop(); if (willHandlePopInternally)
return RoutePopDisposition.pop;
return await super.willPop();
} }
@override @override
...@@ -385,6 +387,28 @@ class _ModalScopeStatus extends InheritedWidget { ...@@ -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]. /// Signature for a callback that verifies that it's OK to call [Navigator.pop].
/// ///
/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback], and /// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback], and
...@@ -664,12 +688,12 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -664,12 +688,12 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// * [removeScopedWillPopCallback], which removes a callback from the list /// * [removeScopedWillPopCallback], which removes a callback from the list
/// this method checks. /// this method checks.
@override @override
Future<bool> willPop() async { Future<RoutePopDisposition> willPop() async {
final _ModalScopeState scope = _scopeKey.currentState; final _ModalScopeState scope = _scopeKey.currentState;
assert(scope != null); assert(scope != null);
for (WillPopCallback callback in new List<WillPopCallback>.from(scope._willPopCallbacks)) { for (WillPopCallback callback in new List<WillPopCallback>.from(scope._willPopCallbacks)) {
if (!await callback()) if (!await callback())
return false; return RoutePopDisposition.doNotPop;
} }
return await super.willPop(); return await super.willPop();
} }
......
...@@ -113,6 +113,12 @@ void main() { ...@@ -113,6 +113,12 @@ void main() {
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(find.text('Sample Page'), findsOneWidget); 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; willPopValue = true;
await tester.tap(find.byTooltip('Back')); await tester.tap(find.byTooltip('Back'));
await tester.pump(); await tester.pump();
......
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