Remove asserts in popRoute (#2652)
diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index 537adf3..efc578b 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 5.0.5
+
+- Fixes issue where asserts in popRoute were preventing the app from
+ exiting on Android.
+
## 5.0.4
- Fixes a bug in ShellRoute example where NavigationBar might lose current index in a nested routes.
diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart
index f2ee938..9bcfa04 100644
--- a/packages/go_router/lib/src/delegate.dart
+++ b/packages/go_router/lib/src/delegate.dart
@@ -59,13 +59,20 @@
final RouteBase route = match.route;
if (route is GoRoute && route.parentNavigatorKey != null) {
- // It should not be possible for a GoRoute with parentNavigatorKey to be
- // the only page, so maybePop should never return false in this case.
- assert(await route.parentNavigatorKey!.currentState!.maybePop());
- return true;
+ final bool didPop =
+ await route.parentNavigatorKey!.currentState!.maybePop();
+
+ // Continue if didPop was false.
+ if (didPop) {
+ return didPop;
+ }
} else if (route is ShellRoute) {
- assert(await route.navigatorKey.currentState!.maybePop());
- return true;
+ final bool didPop = await route.navigatorKey.currentState!.maybePop();
+
+ // Continue if didPop was false.
+ if (didPop) {
+ return didPop;
+ }
}
}
@@ -116,13 +123,15 @@
final RouteBase route = match.route;
if (route is GoRoute && route.parentNavigatorKey != null) {
final bool canPop = route.parentNavigatorKey!.currentState!.canPop();
- // Similar to popRoute, it should not be possible for a GoRoute with
- // parentNavigatorKey to be the only page, so canPop should return true
- // in this case.
- assert(canPop);
- return canPop;
+
+ // Continue if canPop is false.
+ if (canPop) {
+ return canPop;
+ }
} else if (route is ShellRoute) {
final bool canPop = route.navigatorKey.currentState!.canPop();
+
+ // Continue if canPop is false.
if (canPop) {
return canPop;
}
diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart
index 297ce88..aca0495 100644
--- a/packages/go_router/lib/src/route.dart
+++ b/packages/go_router/lib/src/route.dart
@@ -440,7 +440,8 @@
super._() {
for (final RouteBase route in routes) {
if (route is GoRoute) {
- assert(route.parentNavigatorKey == null);
+ assert(route.parentNavigatorKey == null ||
+ route.parentNavigatorKey == navigatorKey);
}
}
}
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index 641d577..4281baf 100644
--- a/packages/go_router/pubspec.yaml
+++ b/packages/go_router/pubspec.yaml
@@ -1,7 +1,7 @@
name: go_router
description: A declarative router for Flutter based on Navigation 2 supporting
deep linking, data-driven routes and more
-version: 5.0.4
+version: 5.0.5
repository: https://github.com/flutter/packages/tree/main/packages/go_router
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart
index 67d3288..b884493 100644
--- a/packages/go_router/test/go_router_test.dart
+++ b/packages/go_router/test/go_router_test.dart
@@ -4,6 +4,7 @@
// ignore_for_file: cascade_invocations, diagnostic_describe_all_properties
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -535,7 +536,7 @@
expect(find.text('Screen A'), findsNothing);
expect(find.text('Screen B'), findsOneWidget);
- await simulateAndroidBackButton();
+ await simulateAndroidBackButton(tester);
await tester.pumpAndSettle();
expect(find.text('Screen A'), findsOneWidget);
expect(find.text('Screen B'), findsNothing);
@@ -606,7 +607,7 @@
expect(find.text('Screen C'), findsNothing);
expect(find.text('Screen D'), findsOneWidget);
- await simulateAndroidBackButton();
+ await simulateAndroidBackButton(tester);
await tester.pumpAndSettle();
expect(find.text('Shell'), findsOneWidget);
expect(find.text('Screen A'), findsNothing);
@@ -614,13 +615,208 @@
expect(find.text('Screen C'), findsOneWidget);
expect(find.text('Screen D'), findsNothing);
- await simulateAndroidBackButton();
+ await simulateAndroidBackButton(tester);
await tester.pumpAndSettle();
expect(find.text('Shell'), findsOneWidget);
expect(find.text('Screen A'), findsNothing);
expect(find.text('Screen B'), findsOneWidget);
expect(find.text('Screen C'), findsNothing);
});
+
+ testWidgets(
+ 'Handles the Android back button when parentNavigatorKey is set to the root navigator',
+ (WidgetTester tester) async {
+ final List<MethodCall> log = <MethodCall>[];
+ TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
+ .setMockMethodCallHandler(SystemChannels.platform,
+ (MethodCall methodCall) async {
+ log.add(methodCall);
+ return null;
+ });
+
+ Future<void> verify(AsyncCallback test, List<Object> expectations) async {
+ log.clear();
+ await test();
+ expect(log, expectations);
+ }
+
+ final GlobalKey<NavigatorState> rootNavigatorKey =
+ GlobalKey<NavigatorState>();
+
+ final List<RouteBase> routes = <RouteBase>[
+ GoRoute(
+ parentNavigatorKey: rootNavigatorKey,
+ path: '/a',
+ builder: (BuildContext context, GoRouterState state) {
+ return const Scaffold(
+ body: Text('Screen A'),
+ );
+ },
+ ),
+ ];
+
+ await createRouter(routes, tester,
+ initialLocation: '/a', navigatorKey: rootNavigatorKey);
+ expect(find.text('Screen A'), findsOneWidget);
+
+ await tester.runAsync(() async {
+ await verify(() => simulateAndroidBackButton(tester), <Object>[
+ isMethodCall('SystemNavigator.pop', arguments: null),
+ ]);
+ });
+ });
+
+ testWidgets("Handles the Android back button when ShellRoute can't pop",
+ (WidgetTester tester) async {
+ final List<MethodCall> log = <MethodCall>[];
+ TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
+ .setMockMethodCallHandler(SystemChannels.platform,
+ (MethodCall methodCall) async {
+ log.add(methodCall);
+ return null;
+ });
+
+ Future<void> verify(AsyncCallback test, List<Object> expectations) async {
+ log.clear();
+ await test();
+ expect(log, expectations);
+ }
+
+ final GlobalKey<NavigatorState> rootNavigatorKey =
+ GlobalKey<NavigatorState>();
+
+ final List<RouteBase> routes = <RouteBase>[
+ GoRoute(
+ parentNavigatorKey: rootNavigatorKey,
+ path: '/a',
+ builder: (BuildContext context, GoRouterState state) {
+ return const Scaffold(
+ body: Text('Screen A'),
+ );
+ },
+ ),
+ ShellRoute(
+ builder: (BuildContext context, GoRouterState state, Widget child) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Shell'),
+ ),
+ body: child,
+ );
+ },
+ routes: <RouteBase>[
+ GoRoute(
+ path: '/b',
+ builder: (BuildContext context, GoRouterState state) {
+ return const Scaffold(
+ body: Text('Screen B'),
+ );
+ },
+ ),
+ ],
+ ),
+ ];
+
+ await createRouter(routes, tester,
+ initialLocation: '/b', navigatorKey: rootNavigatorKey);
+ expect(find.text('Screen B'), findsOneWidget);
+
+ await tester.runAsync(() async {
+ await verify(() => simulateAndroidBackButton(tester), <Object>[
+ isMethodCall('SystemNavigator.pop', arguments: null),
+ ]);
+ });
+ });
+ });
+
+ testWidgets(
+ 'Handles the Android back button when a second Shell has a GoRoute with parentNavigator key',
+ (WidgetTester tester) async {
+ final List<MethodCall> log = <MethodCall>[];
+ TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
+ .setMockMethodCallHandler(SystemChannels.platform,
+ (MethodCall methodCall) async {
+ log.add(methodCall);
+ return null;
+ });
+
+ Future<void> verify(AsyncCallback test, List<Object> expectations) async {
+ log.clear();
+ await test();
+ expect(log, expectations);
+ }
+
+ final GlobalKey<NavigatorState> rootNavigatorKey =
+ GlobalKey<NavigatorState>();
+ final GlobalKey<NavigatorState> shellNavigatorKeyA =
+ GlobalKey<NavigatorState>();
+ final GlobalKey<NavigatorState> shellNavigatorKeyB =
+ GlobalKey<NavigatorState>();
+
+ final List<RouteBase> routes = <RouteBase>[
+ ShellRoute(
+ navigatorKey: shellNavigatorKeyA,
+ builder: (BuildContext context, GoRouterState state, Widget child) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Shell'),
+ ),
+ body: child,
+ );
+ },
+ routes: <RouteBase>[
+ GoRoute(
+ path: '/a',
+ builder: (BuildContext context, GoRouterState state) {
+ return const Scaffold(
+ body: Text('Screen A'),
+ );
+ },
+ routes: <RouteBase>[
+ ShellRoute(
+ navigatorKey: shellNavigatorKeyB,
+ builder:
+ (BuildContext context, GoRouterState state, Widget child) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Shell'),
+ ),
+ body: child,
+ );
+ },
+ routes: <RouteBase>[
+ GoRoute(
+ path: 'b',
+ parentNavigatorKey: shellNavigatorKeyB,
+ builder: (BuildContext context, GoRouterState state) {
+ return const Scaffold(
+ body: Text('Screen B'),
+ );
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ];
+
+ await createRouter(routes, tester,
+ initialLocation: '/a/b', navigatorKey: rootNavigatorKey);
+ expect(find.text('Screen B'), findsOneWidget);
+
+ // The first pop should not exit the app.
+ await tester.runAsync(() async {
+ await verify(() => simulateAndroidBackButton(tester), <Object>[]);
+ });
+
+ // The second pop should exit the app.
+ await tester.runAsync(() async {
+ await verify(() => simulateAndroidBackButton(tester), <Object>[
+ isMethodCall('SystemNavigator.pop', arguments: null),
+ ]);
+ });
});
group('named routes', () {
@@ -2141,7 +2337,7 @@
expect(find.text('Screen B'), findsNothing);
expect(find.text('Screen C'), findsOneWidget);
- await simulateAndroidBackButton();
+ await simulateAndroidBackButton(tester);
await tester.pumpAndSettle();
expect(find.text('Screen A'), findsOneWidget);
@@ -2200,7 +2396,7 @@
expect(find.text('Screen B'), findsNothing);
expect(find.text('Screen C'), findsOneWidget);
- await simulateAndroidBackButton();
+ await simulateAndroidBackButton(tester);
await tester.pumpAndSettle();
expect(find.text('Screen A'), findsOneWidget);
diff --git a/packages/go_router/test/test_helpers.dart b/packages/go_router/test/test_helpers.dart
index af9bf50..289cab4 100644
--- a/packages/go_router/test/test_helpers.dart
+++ b/packages/go_router/test/test_helpers.dart
@@ -347,9 +347,9 @@
Widget build(BuildContext context) => Container();
}
-Future<void> simulateAndroidBackButton() async {
+Future<void> simulateAndroidBackButton(WidgetTester tester) async {
final ByteData message =
const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
- await ServicesBinding.instance.defaultBinaryMessenger
+ await tester.binding.defaultBinaryMessenger
.handlePlatformMessage('flutter/navigation', message, (_) {});
}