Allow WIllPopCallback to return null or false to veto the pop. (#54640)
diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart
index 585fd89..3dd804e 100644
--- a/packages/flutter/lib/src/widgets/routes.dart
+++ b/packages/flutter/lib/src/widgets/routes.dart
@@ -1239,9 +1239,11 @@
final List<WillPopCallback> _willPopCallbacks = <WillPopCallback>[];
- /// Returns the value of the first callback added with
- /// [addScopedWillPopCallback] that returns false. If they all return true,
- /// returns the inherited method's result (see [Route.willPop]).
+ /// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with
+ /// [addScopedWillPopCallback] returns either false or null. If they all
+ /// return true, the base [Route.willPop]'s result will be returned. The
+ /// callbacks will be called in the order they were added, and will only be
+ /// called if all previous callbacks returned true.
///
/// Typically this method is not overridden because applications usually
/// don't create modal routes directly, they use higher level primitives
@@ -1260,7 +1262,7 @@
final _ModalScopeState<T> scope = _scopeKey.currentState;
assert(scope != null);
for (final WillPopCallback callback in List<WillPopCallback>.from(_willPopCallbacks)) {
- if (!await callback())
+ if (await callback() != true)
return RoutePopDisposition.doNotPop;
}
return await super.willPop();
diff --git a/packages/flutter/test/material/will_pop_test.dart b/packages/flutter/test/material/will_pop_test.dart
index f41df05..de08545 100644
--- a/packages/flutter/test/material/will_pop_test.dart
+++ b/packages/flutter/test/material/will_pop_test.dart
@@ -128,6 +128,51 @@
expect(find.text('Sample Page'), findsNothing);
});
+ testWidgets('willPop will only pop if the callback returns true', (WidgetTester tester) async {
+ Widget buildFrame() {
+ return MaterialApp(
+ home: Scaffold(
+ appBar: AppBar(title: const Text('Home')),
+ body: Builder(
+ builder: (BuildContext context) {
+ return Center(
+ child: FlatButton(
+ child: const Text('X'),
+ onPressed: () {
+ Navigator.of(context).push(MaterialPageRoute<void>(
+ builder: (BuildContext context) {
+ return SampleForm(
+ callback: () => Future<bool>.value(willPopValue),
+ );
+ },
+ ));
+ },
+ ),
+ );
+ },
+ ),
+ ),
+ );
+ }
+
+ await tester.pumpWidget(buildFrame());
+ await tester.tap(find.text('X'));
+ await tester.pumpAndSettle();
+ expect(find.text('Sample Form'), findsOneWidget);
+
+ // Should not pop if callback returns null
+ willPopValue = null;
+ await tester.tap(find.byTooltip('Back'));
+ await tester.pumpAndSettle();
+ expect(find.text('Sample Form'), findsOneWidget);
+
+ // Should pop if callback returns true
+ willPopValue = true;
+ await tester.tap(find.byTooltip('Back'));
+ await tester.pumpAndSettle();
+ expect(find.text('Sample Form'), findsNothing);
+ });
+
testWidgets('Form.willPop can inhibit back button', (WidgetTester tester) async {
Widget buildFrame() {
return MaterialApp(