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, (_) {});
 }