[go_router] Add `canPop()` method (#2276)

* :sparkles: Add canPop() method to GoRouterDelegate

* :white_check_mark: Test on GoRouterDelegate.canPop()

* :memo: Update pubspec and CHANGELOG

* Apply suggestions from code review

Co-authored-by: John Ryan <ryjohn@google.com>
diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index 897e856..463f23d 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 4.1.0
+
+- Adds `bool canPop()` to `GoRouterDelegate`, `GoRouter` and `GoRouterHelper`.
+
 ## 4.0.3
 
 - Adds missed popping log.
diff --git a/packages/go_router/lib/go_router.dart b/packages/go_router/lib/go_router.dart
index c91c360..56bd957 100644
--- a/packages/go_router/lib/go_router.dart
+++ b/packages/go_router/lib/go_router.dart
@@ -70,6 +70,9 @@
         extra: extra,
       );
 
+  /// Returns `true` if there is more than 1 page on the stack.
+  bool canPop() => GoRouter.of(this).canPop();
+
   /// Pop the top page off the Navigator's page stack by calling
   /// [Navigator.pop].
   void pop() => GoRouter.of(this).pop();
diff --git a/packages/go_router/lib/src/go_router.dart b/packages/go_router/lib/src/go_router.dart
index a57c9b7..17a7af1 100644
--- a/packages/go_router/lib/src/go_router.dart
+++ b/packages/go_router/lib/src/go_router.dart
@@ -160,6 +160,9 @@
         extra: extra,
       );
 
+  /// Returns `true` if there is more than 1 page on the stack.
+  bool canPop() => routerDelegate.canPop();
+
   /// Pop the top page off the GoRouter's page stack.
   void pop() {
     assert(() {
diff --git a/packages/go_router/lib/src/go_router_delegate.dart b/packages/go_router/lib/src/go_router_delegate.dart
index fed213c..3ea7e62 100644
--- a/packages/go_router/lib/src/go_router_delegate.dart
+++ b/packages/go_router/lib/src/go_router_delegate.dart
@@ -65,6 +65,11 @@
     notifyListeners();
   }
 
+  /// Returns `true` if there is more than 1 page on the stack.
+  bool canPop() {
+    return _matches.length > 1;
+  }
+
   /// Pop the top page off the GoRouter's page stack.
   void pop() {
     _matches.remove(_matches.last);
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index f3b1923..f122200 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: 4.0.3
+version: 4.1.0
 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_delegate_test.dart b/packages/go_router/test/go_router_delegate_test.dart
index 9ad9745..73592a2 100644
--- a/packages/go_router/test/go_router_delegate_test.dart
+++ b/packages/go_router/test/go_router_delegate_test.dart
@@ -56,6 +56,28 @@
     });
   });
 
+  group('canPop', () {
+    testWidgets(
+      'It should return false if there is only 1 match in the stack',
+      (WidgetTester tester) async {
+        final GoRouter goRouter = await createGoRouter(tester);
+
+        expect(goRouter.routerDelegate.matches.length, 1);
+        expect(goRouter.routerDelegate.canPop(), false);
+      },
+    );
+    testWidgets(
+      'It should return true if there is more than 1 match in the stack',
+      (WidgetTester tester) async {
+        final GoRouter goRouter = await createGoRouter(tester)
+          ..push('/error');
+
+        expect(goRouter.routerDelegate.matches.length, 2);
+        expect(goRouter.routerDelegate.canPop(), true);
+      },
+    );
+  });
+
   testWidgets('dispose unsubscribes from refreshListenable',
       (WidgetTester tester) async {
     final FakeRefreshListenable refreshListenable = FakeRefreshListenable();