[go_router] Go router v5 (#2612)

diff --git a/.cirrus.yml b/.cirrus.yml
index abdb234..fbd0689 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -76,7 +76,7 @@
           # Run analysis with path-based dependencies to ensure that publishing
           # the changes won't break analysis of other packages in the respository
           # that depend on it.
-          - ./script/tool_runner.sh make-deps-path-based  --target-dependencies-with-non-breaking-updates 
+          - ./script/tool_runner.sh make-deps-path-based  --target-dependencies-with-non-breaking-updates
           # This uses --run-on-dirty-packages rather than --packages-for-branch
           # since only the packages changed by 'make-deps-path-based' need to be
           # checked.
@@ -237,7 +237,7 @@
   << : *FLUTTER_UPGRADE_TEMPLATE
   << : *MACOS_TEMPLATE
   matrix:
-   ### iOS tasks ###
+    ### iOS tasks ###
     - name: ios-platform_tests
       env:
         PATH: $PATH:/usr/local/bin
diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index 1746ece..6f2eea2 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,3 +1,17 @@
+## 5.0.0
+
+- Fixes a bug where intermediate route redirect methods are not called.
+- GoRouter implements the RouterConfig interface, allowing you to call
+  MaterialApp.router(routerConfig: _myGoRouter) instead of passing
+  the RouterDelegate, RouteInformationParser, and RouteInformationProvider
+  fields.
+- **BREAKING CHANGE**
+  - Redesigns redirection API, adds asynchronous feature, and adds build context to redirect.
+  - Removes GoRouterRefreshStream
+  - Removes navigatorBuilder
+  - Removes urlPathStrategy
+- [go_router v5 migration guide](https://flutter.dev/go/go-router-v5-breaking-changes)
+
 ## 4.5.1
 
 - Fixes an issue where GoRoutes with only a redirect were disallowed
diff --git a/packages/go_router/README.md b/packages/go_router/README.md
index 5f9a36d..66dac9f 100644
--- a/packages/go_router/README.md
+++ b/packages/go_router/README.md
@@ -24,9 +24,7 @@
   @override
   Widget build(BuildContext context) {
     return MaterialApp.router(
-      routeInformationProvider: _router.routeInformationProvider,
-      routeInformationParser: _router.routeInformationParser,
-      routerDelegate: _router.routerDelegate,
+      routerConfig: _router,
       title: 'GoRouter Example',
     );
   }
@@ -87,7 +85,7 @@
 [pageBuilder](https://pub.dev/documentation/go_router/latest/go_router/GoRoute/pageBuilder.html)
 for custom `Page` class.
 
-## Initalization
+## Initialization
 
 Create a [GoRouter](https://pub.dev/documentation/go_router/latest/go_router/GoRouter-class.html)
 object and initialize your `MaterialApp` or `CupertinoApp`:
@@ -100,9 +98,7 @@
 );
 
 MaterialApp.router(
-  routeInformationProvider: _router.routeInformationProvider,
-  routeInformationParser: _router.routeInformationParser,
-  routerDelegate: _router.routerDelegate,
+  routerConfig: _router,
 );
 ```
 
@@ -119,6 +115,42 @@
 );
 ```
 
+## Redirection
+
+You can use redirection to prevent the user from visiting a specific page. In
+go_router, redirection can be asynchronous.
+
+```dart
+GoRouter(
+  ...
+  redirect: (context, state) async {
+    if (await LoginService.of(context).isLoggedIn) {
+      return state.location;
+    }
+    return '/login';
+  },
+);
+```
+
+If the code depends on [BuildContext](https://api.flutter.dev/flutter/widgets/BuildContext-class.html)
+through the [dependOnInheritedWidgetOfExactType](https://api.flutter.dev/flutter/widgets/BuildContext/dependOnInheritedWidgetOfExactType.html)
+(which is how `of` methods are usually implemented), the redirect will be called every time the [InheritedWidget](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)
+updated.
+
+### Top-level redirect
+
+The [GoRouter.redirect](https://pub.dev/documentation/go_router/latest/go_router/GoRouter-class.html)
+is always called for every navigation regardless of which GoRoute was matched. The
+top-level redirect always takes priority over route-level redirect.
+
+### Route-level redirect
+
+If the top-level redirect does not redirect to a different location,
+the [GoRoute.redirect](https://pub.dev/documentation/go_router/latest/go_router/GoRoute/redirect.html)
+is then called if the route has matched the GoRoute. If there are multiple
+GoRoute matches, e.g. GoRoute with sub-routes, the parent route redirect takes
+priority over sub-routes' redirect.
+
 ## Navigation
 
 To navigate between routes, use the [GoRouter.go](https://pub.dev/documentation/go_router/latest/go_router/GoRouter/go.html) method:
@@ -186,6 +218,7 @@
 - [Migrating to 2.5](https://flutter.dev/go/go-router-v2-5-breaking-changes)
 - [Migrating to 3.0](https://flutter.dev/go/go-router-v3-breaking-changes)
 - [Migrating to 4.0](https://flutter.dev/go/go-router-v4-breaking-changes)
+- [Migrating to 5.0](https://flutter.dev/go/go-router-v5-breaking-changes)
 
 ## Changelog
 
diff --git a/packages/go_router/example/README.md b/packages/go_router/example/README.md
index 21f6e80..d6ffa67 100644
--- a/packages/go_router/example/README.md
+++ b/packages/go_router/example/README.md
@@ -23,9 +23,14 @@
 ## [Redirection](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart)
 `flutter run lib/redirection.dart`
 
-An example to demonstrate how to use redirect to handle a sign-in flow.
+An example to demonstrate how to use redirect to handle a synchronous sign-in flow.
 
-## [books app](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/books)
+## [Asynchronous Redirection](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/async_redirection.dart)
+`flutter run lib/async_redirection.dart`
+
+An example to demonstrate how to use handle a sign-in flow with a stream authentication service.
+
+## [Books app](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/books)
 `flutter run lib/books/main.dart`
 
 A fully fledged example that showcases various go_router APIs.
\ No newline at end of file
diff --git a/packages/go_router/example/lib/async_redirection.dart b/packages/go_router/example/lib/async_redirection.dart
new file mode 100644
index 0000000..918fada
--- /dev/null
+++ b/packages/go_router/example/lib/async_redirection.dart
@@ -0,0 +1,248 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+// This scenario demonstrates how to use redirect to handle a asynchronous
+// sign-in flow.
+//
+// The `StreamAuth` is a mock of google_sign_in. This example wraps it with an
+// InheritedNotifier, StreamAuthScope, and relies on
+// `dependOnInheritedWidgetOfExactType` to create a dependency between the
+// notifier and go_router's parsing pipeline. When StreamAuth broadcasts new
+// event, the dependency will cause the go_router to reparse the current url
+// which will also trigger the redirect.
+
+void main() => runApp(StreamAuthScope(child: App()));
+
+/// The main app.
+class App extends StatelessWidget {
+  /// Creates an [App].
+  App({Key? key}) : super(key: key);
+
+  /// The title of the app.
+  static const String title = 'GoRouter Example: Redirection';
+
+  // add the login info into the tree as app state that can change over time
+  @override
+  Widget build(BuildContext context) => MaterialApp.router(
+        routeInformationProvider: _router.routeInformationProvider,
+        routeInformationParser: _router.routeInformationParser,
+        routerDelegate: _router.routerDelegate,
+        title: title,
+        debugShowCheckedModeBanner: false,
+      );
+
+  late final GoRouter _router = GoRouter(
+    routes: <GoRoute>[
+      GoRoute(
+        path: '/',
+        builder: (BuildContext context, GoRouterState state) =>
+            const HomeScreen(),
+      ),
+      GoRoute(
+        path: '/login',
+        builder: (BuildContext context, GoRouterState state) =>
+            const LoginScreen(),
+      ),
+    ],
+
+    // redirect to the login page if the user is not logged in
+    redirect: (BuildContext context, GoRouterState state) async {
+      // Using `of` method creates a dependency of StreamAuthScope. It will
+      // cause go_router to reparse current route if StreamAuth has new sign-in
+      // information.
+      final bool loggedIn = await StreamAuthScope.of(context).isSignedIn();
+      final bool loggingIn = state.subloc == '/login';
+      if (!loggedIn) {
+        return loggingIn ? null : '/login';
+      }
+
+      // if the user is logged in but still on the login page, send them to
+      // the home page
+      if (loggingIn) {
+        return '/';
+      }
+
+      // no need to redirect at all
+      return null;
+    },
+  );
+}
+
+/// The login screen.
+class LoginScreen extends StatefulWidget {
+  /// Creates a [LoginScreen].
+  const LoginScreen({Key? key}) : super(key: key);
+
+  @override
+  State<LoginScreen> createState() => _LoginScreenState();
+}
+
+class _LoginScreenState extends State<LoginScreen>
+    with TickerProviderStateMixin {
+  bool loggingIn = false;
+  late final AnimationController controller;
+
+  @override
+  void initState() {
+    super.initState();
+    controller = AnimationController(
+      vsync: this,
+      duration: const Duration(seconds: 1),
+    )..addListener(() {
+        setState(() {});
+      });
+    controller.repeat();
+  }
+
+  @override
+  void dispose() {
+    controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) => Scaffold(
+        appBar: AppBar(title: const Text(App.title)),
+        body: Center(
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: <Widget>[
+              if (loggingIn) CircularProgressIndicator(value: controller.value),
+              if (!loggingIn)
+                ElevatedButton(
+                  onPressed: () {
+                    StreamAuthScope.of(context).signIn('test-user');
+                    setState(() {
+                      loggingIn = true;
+                    });
+                  },
+                  child: const Text('Login'),
+                ),
+            ],
+          ),
+        ),
+      );
+}
+
+/// The home screen.
+class HomeScreen extends StatelessWidget {
+  /// Creates a [HomeScreen].
+  const HomeScreen({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final StreamAuth info = StreamAuthScope.of(context);
+
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text(App.title),
+        actions: <Widget>[
+          IconButton(
+            onPressed: () => info.signOut(),
+            tooltip: 'Logout: ${info.currentUser}',
+            icon: const Icon(Icons.logout),
+          )
+        ],
+      ),
+      body: const Center(
+        child: Text('HomeScreen'),
+      ),
+    );
+  }
+}
+
+/// A scope that provides [StreamAuth] for the subtree.
+class StreamAuthScope extends InheritedNotifier<StreamAuthNotifier> {
+  /// Creates a [StreamAuthScope] sign in scope.
+  StreamAuthScope({
+    Key? key,
+    required Widget child,
+  }) : super(
+          key: key,
+          notifier: StreamAuthNotifier(),
+          child: child,
+        );
+
+  /// Gets the [StreamAuth].
+  static StreamAuth of(BuildContext context) {
+    return context
+        .dependOnInheritedWidgetOfExactType<StreamAuthScope>()!
+        .notifier!
+        .streamAuth;
+  }
+}
+
+/// A class that converts [StreamAuth] into a [ChangeNotifier].
+class StreamAuthNotifier extends ChangeNotifier {
+  /// Creates a [StreamAuthNotifier].
+  StreamAuthNotifier() : streamAuth = StreamAuth() {
+    streamAuth.onCurrentUserChanged.listen((String? string) {
+      notifyListeners();
+    });
+  }
+
+  /// The stream auth client.
+  final StreamAuth streamAuth;
+}
+
+/// An asynchronous log in services mock with stream similar to google_sign_in.
+///
+/// This class adds an artificial delay of 3 second when logging in an user, and
+/// will automatically clear the login session after [refreshInterval].
+class StreamAuth {
+  /// Creates an [StreamAuth] that clear the current user session in
+  /// [refeshInterval] second.
+  StreamAuth({this.refreshInterval = 20})
+      : _userStreamController = StreamController<String?>.broadcast() {
+    _userStreamController.stream.listen((String? currentUser) {
+      _currentUser = currentUser;
+    });
+  }
+
+  /// The current user.
+  String? get currentUser => _currentUser;
+  String? _currentUser;
+
+  /// Checks whether current user is signed in with an artificial delay to mimic
+  /// async operation.
+  Future<bool> isSignedIn() async {
+    await Future<void>.delayed(const Duration(seconds: 1));
+    return _currentUser != null;
+  }
+
+  /// A stream that notifies when current user has changed.
+  Stream<String?> get onCurrentUserChanged => _userStreamController.stream;
+  final StreamController<String?> _userStreamController;
+
+  /// The interval that automatically signs out the user.
+  final int refreshInterval;
+
+  Timer? _timer;
+  Timer _createRefreshTimer() {
+    return Timer(Duration(seconds: refreshInterval), () {
+      _userStreamController.add(null);
+      _timer = null;
+    });
+  }
+
+  /// Signs in a user with an artificial delay to mimic async operation.
+  Future<void> signIn(String newUserName) async {
+    await Future<void>.delayed(const Duration(seconds: 3));
+    _userStreamController.add(newUserName);
+    _timer?.cancel();
+    _timer = _createRefreshTimer();
+  }
+
+  /// Signs out the current user.
+  Future<void> signOut() async {
+    _timer?.cancel();
+    _timer = null;
+    _userStreamController.add(null);
+  }
+}
diff --git a/packages/go_router/example/lib/books/main.dart b/packages/go_router/example/lib/books/main.dart
index 241c368..7ee8820 100644
--- a/packages/go_router/example/lib/books/main.dart
+++ b/packages/go_router/example/lib/books/main.dart
@@ -43,7 +43,7 @@
     routes: <GoRoute>[
       GoRoute(
         path: '/',
-        redirect: (_) => '/books',
+        redirect: (_, __) => '/books',
       ),
       GoRoute(
         path: '/signin',
@@ -60,11 +60,11 @@
       ),
       GoRoute(
         path: '/books',
-        redirect: (_) => '/books/popular',
+        redirect: (_, __) => '/books/popular',
       ),
       GoRoute(
         path: '/book/:bookId',
-        redirect: (GoRouterState state) =>
+        redirect: (BuildContext context, GoRouterState state) =>
             '/books/all/${state.params['bookId']}',
       ),
       GoRoute(
@@ -92,7 +92,7 @@
       ),
       GoRoute(
         path: '/author/:authorId',
-        redirect: (GoRouterState state) =>
+        redirect: (BuildContext context, GoRouterState state) =>
             '/authors/${state.params['authorId']}',
       ),
       GoRoute(
@@ -135,7 +135,7 @@
     debugLogDiagnostics: true,
   );
 
-  String? _guard(GoRouterState state) {
+  String? _guard(BuildContext context, GoRouterState state) {
     final bool signedIn = _auth.signedIn;
     final bool signingIn = state.subloc == '/signin';
 
diff --git a/packages/go_router/example/lib/main.dart b/packages/go_router/example/lib/main.dart
index fab5a36..c47fc49 100644
--- a/packages/go_router/example/lib/main.dart
+++ b/packages/go_router/example/lib/main.dart
@@ -26,9 +26,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
       );
 
diff --git a/packages/go_router/example/lib/named_routes.dart b/packages/go_router/example/lib/named_routes.dart
index adb21e7..237581f 100644
--- a/packages/go_router/example/lib/named_routes.dart
+++ b/packages/go_router/example/lib/named_routes.dart
@@ -61,9 +61,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
         debugShowCheckedModeBanner: false,
       );
diff --git a/packages/go_router/example/lib/others/error_screen.dart b/packages/go_router/example/lib/others/error_screen.dart
index b1c4fa9..f7edca0 100644
--- a/packages/go_router/example/lib/others/error_screen.dart
+++ b/packages/go_router/example/lib/others/error_screen.dart
@@ -17,9 +17,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
       );
 
diff --git a/packages/go_router/example/lib/others/extra_param.dart b/packages/go_router/example/lib/others/extra_param.dart
index 3b6ca0e..a4d40e9 100644
--- a/packages/go_router/example/lib/others/extra_param.dart
+++ b/packages/go_router/example/lib/others/extra_param.dart
@@ -53,9 +53,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
       );
 
diff --git a/packages/go_router/example/lib/others/init_loc.dart b/packages/go_router/example/lib/others/init_loc.dart
index 899c3c1..31ca594 100644
--- a/packages/go_router/example/lib/others/init_loc.dart
+++ b/packages/go_router/example/lib/others/init_loc.dart
@@ -17,9 +17,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
       );
 
diff --git a/packages/go_router/example/lib/others/nav_builder.dart b/packages/go_router/example/lib/others/nav_builder.dart
deleted file mode 100644
index c3a3bbb..0000000
--- a/packages/go_router/example/lib/others/nav_builder.dart
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
-import 'package:provider/provider.dart';
-
-/// The login information.
-class LoginInfo extends ChangeNotifier {
-  /// The username of login.
-  String get userName => _userName;
-  String _userName = '';
-
-  /// Whether a user has logged in.
-  bool get loggedIn => _userName.isNotEmpty;
-
-  /// Logs in a user.
-  void login(String userName) {
-    _userName = userName;
-    notifyListeners();
-  }
-
-  /// Logs out the current user.
-  void logout() {
-    _userName = '';
-    notifyListeners();
-  }
-}
-
-void main() => runApp(App());
-
-/// The main app.
-class App extends StatelessWidget {
-  /// Creates an [App].
-  App({Key? key}) : super(key: key);
-
-  final LoginInfo _loginInfo = LoginInfo();
-
-  /// The title of the app.
-  static const String title = 'GoRouter Example: Navigator Builder';
-
-  @override
-  Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
-        title: title,
-      );
-
-  late final GoRouter _router = GoRouter(
-    debugLogDiagnostics: true,
-    routes: <GoRoute>[
-      GoRoute(
-        name: 'home',
-        path: '/',
-        builder: (BuildContext context, GoRouterState state) =>
-            const HomeScreenNoLogout(),
-      ),
-      GoRoute(
-        name: 'login',
-        path: '/login',
-        builder: (BuildContext context, GoRouterState state) =>
-            const LoginScreen(),
-      ),
-    ],
-
-    // changes on the listenable will cause the router to refresh it's route
-    refreshListenable: _loginInfo,
-
-    // redirect to the login page if the user is not logged in
-    redirect: (GoRouterState state) {
-      final bool loggedIn = _loginInfo.loggedIn;
-      const String loginLocation = '/login';
-      final bool loggingIn = state.subloc == loginLocation;
-
-      if (!loggedIn) {
-        return loggingIn ? null : loginLocation;
-      }
-      if (loggingIn) {
-        return state.namedLocation('home');
-      }
-      return null;
-    },
-
-    // add a wrapper around the navigator to:
-    // - put loginInfo into the widget tree, and to
-    // - add an overlay to show a logout option
-    navigatorBuilder:
-        (BuildContext context, GoRouterState state, Widget child) =>
-            ChangeNotifierProvider<LoginInfo>.value(
-      value: _loginInfo,
-      builder: (BuildContext context, Widget? _) {
-        return _loginInfo.loggedIn ? AuthOverlay(child: child) : child;
-      },
-    ),
-  );
-}
-
-/// A simple class for placing an exit button on top of all screens.
-class AuthOverlay extends StatelessWidget {
-  /// Creates an [AuthOverlay].
-  const AuthOverlay({required this.child, Key? key}) : super(key: key);
-
-  /// The child subtree.
-  final Widget child;
-
-  @override
-  Widget build(BuildContext context) => Stack(
-        children: <Widget>[
-          child,
-          Positioned(
-            top: 90,
-            right: 4,
-            child: ElevatedButton(
-              onPressed: () {
-                context.read<LoginInfo>().logout();
-                context.goNamed('home'); // clear out the `from` query param
-              },
-              child: const Icon(Icons.logout),
-            ),
-          ),
-        ],
-      );
-}
-
-/// The home screen without a logout button.
-class HomeScreenNoLogout extends StatelessWidget {
-  /// Creates a [HomeScreenNoLogout].
-  const HomeScreenNoLogout({Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) => Scaffold(
-        appBar: AppBar(title: const Text(App.title)),
-        body: const Center(
-          child: Text('home screen'),
-        ),
-      );
-}
-
-/// The login screen.
-class LoginScreen extends StatelessWidget {
-  /// Creates a [LoginScreen].
-  const LoginScreen({Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) => Scaffold(
-        appBar: AppBar(title: const Text(App.title)),
-        body: Center(
-          child: Column(
-            mainAxisAlignment: MainAxisAlignment.center,
-            children: <Widget>[
-              ElevatedButton(
-                onPressed: () {
-                  // log a user in, letting all the listeners know
-                  context.read<LoginInfo>().login('test-user');
-                },
-                child: const Text('Login'),
-              ),
-            ],
-          ),
-        ),
-      );
-}
diff --git a/packages/go_router/example/lib/others/nav_observer.dart b/packages/go_router/example/lib/others/nav_observer.dart
index 5afd016..a63023d 100644
--- a/packages/go_router/example/lib/others/nav_observer.dart
+++ b/packages/go_router/example/lib/others/nav_observer.dart
@@ -18,9 +18,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
       );
 
diff --git a/packages/go_router/example/lib/others/push.dart b/packages/go_router/example/lib/others/push.dart
index 1288f12..5d54ec2 100644
--- a/packages/go_router/example/lib/others/push.dart
+++ b/packages/go_router/example/lib/others/push.dart
@@ -17,9 +17,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
       );
 
diff --git a/packages/go_router/example/lib/others/router_neglect.dart b/packages/go_router/example/lib/others/router_neglect.dart
index 4f4acfb..d5c2280 100644
--- a/packages/go_router/example/lib/others/router_neglect.dart
+++ b/packages/go_router/example/lib/others/router_neglect.dart
@@ -17,9 +17,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
       );
 
diff --git a/packages/go_router/example/lib/others/state_restoration.dart b/packages/go_router/example/lib/others/state_restoration.dart
index 33d64ca..5c6016f 100644
--- a/packages/go_router/example/lib/others/state_restoration.dart
+++ b/packages/go_router/example/lib/others/state_restoration.dart
@@ -32,9 +32,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: App.title,
         restorationScopeId: 'app',
       );
diff --git a/packages/go_router/example/lib/others/transitions.dart b/packages/go_router/example/lib/others/transitions.dart
index 555e070..51c2a24 100644
--- a/packages/go_router/example/lib/others/transitions.dart
+++ b/packages/go_router/example/lib/others/transitions.dart
@@ -17,9 +17,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
       );
 
@@ -27,7 +25,7 @@
     routes: <GoRoute>[
       GoRoute(
         path: '/',
-        redirect: (_) => '/none',
+        redirect: (_, __) => '/none',
       ),
       GoRoute(
         path: '/fade',
diff --git a/packages/go_router/example/lib/path_and_query_parameters.dart b/packages/go_router/example/lib/path_and_query_parameters.dart
index fe9b8ba..7e52baf 100755
--- a/packages/go_router/example/lib/path_and_query_parameters.dart
+++ b/packages/go_router/example/lib/path_and_query_parameters.dart
@@ -62,9 +62,7 @@
   // add the login info into the tree as app state that can change over time
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
         debugShowCheckedModeBanner: false,
       );
diff --git a/packages/go_router/example/lib/redirection.dart b/packages/go_router/example/lib/redirection.dart
index d57b5d4..1e23573 100644
--- a/packages/go_router/example/lib/redirection.dart
+++ b/packages/go_router/example/lib/redirection.dart
@@ -51,9 +51,7 @@
   Widget build(BuildContext context) => ChangeNotifierProvider<LoginInfo>.value(
         value: _loginInfo,
         child: MaterialApp.router(
-          routeInformationProvider: _router.routeInformationProvider,
-          routeInformationParser: _router.routeInformationParser,
-          routerDelegate: _router.routerDelegate,
+          routerConfig: _router,
           title: title,
           debugShowCheckedModeBanner: false,
         ),
@@ -74,7 +72,7 @@
     ],
 
     // redirect to the login page if the user is not logged in
-    redirect: (GoRouterState state) {
+    redirect: (BuildContext context, GoRouterState state) {
       // if the user is not logged in, they need to login
       final bool loggedIn = _loginInfo.loggedIn;
       final bool loggingIn = state.subloc == '/login';
diff --git a/packages/go_router/example/lib/sub_routes.dart b/packages/go_router/example/lib/sub_routes.dart
index 6e1632a..8bd7728 100644
--- a/packages/go_router/example/lib/sub_routes.dart
+++ b/packages/go_router/example/lib/sub_routes.dart
@@ -29,9 +29,7 @@
 
   @override
   Widget build(BuildContext context) => MaterialApp.router(
-        routeInformationProvider: _router.routeInformationProvider,
-        routeInformationParser: _router.routeInformationParser,
-        routerDelegate: _router.routerDelegate,
+        routerConfig: _router,
         title: title,
       );
 
diff --git a/packages/go_router/example/pubspec.yaml b/packages/go_router/example/pubspec.yaml
index abd1598..b218565 100644
--- a/packages/go_router/example/pubspec.yaml
+++ b/packages/go_router/example/pubspec.yaml
@@ -5,7 +5,7 @@
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
-  flutter: ">=2.10.0"
+  flutter: ">=3.3.0"
 
 dependencies:
   adaptive_dialog: ^1.2.0
diff --git a/packages/go_router/lib/go_router.dart b/packages/go_router/lib/go_router.dart
index d47c63d..1d9f9dd 100644
--- a/packages/go_router/lib/go_router.dart
+++ b/packages/go_router/lib/go_router.dart
@@ -10,7 +10,6 @@
     show GoRoute, GoRouterState, RouteBase, ShellRoute;
 export 'src/misc/extensions.dart';
 export 'src/misc/inherited_router.dart';
-export 'src/misc/refresh_stream.dart';
 export 'src/pages/custom_transition_page.dart';
 export 'src/platform.dart' show UrlPathStrategy;
 export 'src/route_data.dart' show GoRouteData, TypedGoRoute;
diff --git a/packages/go_router/lib/src/builder.dart b/packages/go_router/lib/src/builder.dart
index 41cbc3e..6170984 100644
--- a/packages/go_router/lib/src/builder.dart
+++ b/packages/go_router/lib/src/builder.dart
@@ -54,6 +54,11 @@
     VoidCallback pop,
     bool routerNeglect,
   ) {
+    if (matchList.isEmpty) {
+      // The build method can be called before async redirect finishes. Build a
+      // empty box until then.
+      return const SizedBox.shrink();
+    }
     try {
       return tryBuild(
           context, matchList, pop, routerNeglect, configuration.navigatorKey);
diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart
index 06abcc2..f2ee938 100644
--- a/packages/go_router/lib/src/delegate.dart
+++ b/packages/go_router/lib/src/delegate.dart
@@ -173,6 +173,7 @@
   @override
   Future<void> setNewRoutePath(RouteMatchList configuration) {
     _matchList = configuration;
+    assert(_matchList.isNotEmpty);
     // Use [SynchronousFuture] so that the initial url is processed
     // synchronously and remove unwanted initial animations on deep-linking
     return SynchronousFuture<void>(null);
diff --git a/packages/go_router/lib/src/information_provider.dart b/packages/go_router/lib/src/information_provider.dart
index 3011d13..207ec45 100644
--- a/packages/go_router/lib/src/information_provider.dart
+++ b/packages/go_router/lib/src/information_provider.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/widgets.dart';
 import 'parser.dart';
@@ -96,18 +97,17 @@
   }
 
   @override
-  Future<bool> didPushRouteInformation(
-      RouteInformation routeInformation) async {
+  Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
     assert(hasListeners);
     _platformReportsNewRouteInformation(routeInformation);
-    return true;
+    return SynchronousFuture<bool>(true);
   }
 
   @override
-  Future<bool> didPushRoute(String route) async {
+  Future<bool> didPushRoute(String route) {
     assert(hasListeners);
     _platformReportsNewRouteInformation(RouteInformation(location: route));
-    return true;
+    return SynchronousFuture<bool>(true);
   }
 }
 
@@ -115,5 +115,5 @@
 /// in use with the [GoRouteInformationParser].
 class DebugGoRouteInformation extends RouteInformation {
   /// Creates a [DebugGoRouteInformation].
-  DebugGoRouteInformation({super.location, super.state});
+  const DebugGoRouteInformation({super.location, super.state});
 }
diff --git a/packages/go_router/lib/src/matching.dart b/packages/go_router/lib/src/matching.dart
index 060bd3b..1775c71 100644
--- a/packages/go_router/lib/src/matching.dart
+++ b/packages/go_router/lib/src/matching.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter/widgets.dart';
+
 import 'configuration.dart';
 import 'match.dart';
 import 'path_utils.dart';
@@ -83,7 +85,7 @@
   }
 
   /// An optional object provided by the app during navigation.
-  Object? get extra => _matches.last.extra;
+  Object? get extra => _matches.isEmpty ? null : _matches.last.extra;
 
   /// The last matching route.
   RouteMatch get last => _matches.last;
@@ -220,3 +222,25 @@
   // consider adding the dynamic route at the end of the routes
   return result.first;
 }
+
+/// The match used when there is an error during parsing.
+RouteMatchList errorScreen(Uri uri, String errorMessage) {
+  final Exception error = Exception(errorMessage);
+  return RouteMatchList(<RouteMatch>[
+    RouteMatch(
+      subloc: uri.path,
+      fullpath: uri.path,
+      encodedParams: <String, String>{},
+      queryParams: uri.queryParameters,
+      queryParametersAll: uri.queryParametersAll,
+      extra: null,
+      error: error,
+      route: GoRoute(
+        path: uri.toString(),
+        pageBuilder: (BuildContext context, GoRouterState state) {
+          throw UnimplementedError();
+        },
+      ),
+    ),
+  ]);
+}
diff --git a/packages/go_router/lib/src/misc/refresh_stream.dart b/packages/go_router/lib/src/misc/refresh_stream.dart
deleted file mode 100644
index 01acb43..0000000
--- a/packages/go_router/lib/src/misc/refresh_stream.dart
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-
-/// Converts a [Stream] into a [Listenable]
-///
-/// {@tool snippet}
-/// Typical usage is as follows:
-///
-/// ```dart
-/// GoRouter(
-///  refreshListenable: GoRouterRefreshStream(stream),
-/// );
-/// ```
-/// {@end-tool}
-class GoRouterRefreshStream extends ChangeNotifier {
-  /// Creates a [GoRouterRefreshStream].
-  ///
-  /// Every time the [stream] receives an event the [GoRouter] will refresh its
-  /// current route.
-  GoRouterRefreshStream(Stream<dynamic> stream) {
-    notifyListeners();
-    _subscription = stream.asBroadcastStream().listen(
-          (dynamic _) => notifyListeners(),
-        );
-  }
-
-  late final StreamSubscription<dynamic> _subscription;
-
-  @override
-  void dispose() {
-    _subscription.cancel();
-    super.dispose();
-  }
-}
diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart
index 9959553..7c7d1de 100644
--- a/packages/go_router/lib/src/parser.dart
+++ b/packages/go_router/lib/src/parser.dart
@@ -2,13 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
 import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 
 import 'configuration.dart';
 import 'information_provider.dart';
 import 'logging.dart';
-import 'match.dart';
 import 'matching.dart';
 import 'redirection.dart';
 
@@ -40,10 +41,17 @@
   /// Defaults to false.
   final bool debugRequireGoRouteInformationProvider;
 
+  /// The future of current route parsing.
+  ///
+  /// This is used for testing asynchronous redirection.
+  @visibleForTesting
+  Future<RouteMatchList>? debugParserFuture;
+
   /// Called by the [Router]. The
   @override
-  Future<RouteMatchList> parseRouteInformation(
+  Future<RouteMatchList> parseRouteInformationWithDependencies(
     RouteInformation routeInformation,
+    BuildContext context,
   ) {
     assert(() {
       if (debugRequireGoRouteInformationProvider) {
@@ -56,43 +64,46 @@
       }
       return true;
     }());
+    late final RouteMatchList initialMatches;
     try {
-      late final RouteMatchList initialMatches;
-      try {
-        initialMatches = matcher.findMatch(routeInformation.location!,
-            extra: routeInformation.state);
-      } on MatcherError {
-        log.info('No initial matches: ${routeInformation.location}');
-
-        // If there is a matching error for the initial location, we should
-        // still try to process the top-level redirects.
-        initialMatches = RouteMatchList.empty();
-      }
-      final RouteMatchList matches = redirector(
-          initialMatches, configuration, matcher,
+      initialMatches = matcher.findMatch(routeInformation.location!,
           extra: routeInformation.state);
+    } on MatcherError {
+      log.info('No initial matches: ${routeInformation.location}');
+
+      // If there is a matching error for the initial location, we should
+      // still try to process the top-level redirects.
+      initialMatches = RouteMatchList.empty();
+    }
+    Future<RouteMatchList> processRedirectorResult(RouteMatchList matches) {
       if (matches.isEmpty) {
-        return SynchronousFuture<RouteMatchList>(_errorScreen(
+        return SynchronousFuture<RouteMatchList>(errorScreen(
             Uri.parse(routeInformation.location!),
             MatcherError('no routes for location', routeInformation.location!)
                 .toString()));
       }
-
-      // Use [SynchronousFuture] so that the initial url is processed
-      // synchronously and remove unwanted initial animations on deep-linking
       return SynchronousFuture<RouteMatchList>(matches);
-    } on RedirectionError catch (e) {
-      log.info('Redirection error: ${e.message}');
-      final Uri uri = e.location;
-      return SynchronousFuture<RouteMatchList>(_errorScreen(uri, e.message));
-    } on MatcherError catch (e) {
-      // The RouteRedirector uses the matcher to find the match, so a match
-      // exception can happen during redirection. For example, the redirector
-      // redirects from `/a` to `/b`, it needs to get the matches for `/b`.
-      log.info('Match error: ${e.message}');
-      final Uri uri = Uri.parse(e.location);
-      return SynchronousFuture<RouteMatchList>(_errorScreen(uri, e.message));
     }
+
+    final FutureOr<RouteMatchList> redirectorResult = redirector(
+      context,
+      SynchronousFuture<RouteMatchList>(initialMatches),
+      configuration,
+      matcher,
+      extra: routeInformation.state,
+    );
+    if (redirectorResult is RouteMatchList) {
+      return processRedirectorResult(redirectorResult);
+    }
+
+    return debugParserFuture = redirectorResult.then(processRedirectorResult);
+  }
+
+  @override
+  Future<RouteMatchList> parseRouteInformation(
+      RouteInformation routeInformation) {
+    throw UnimplementedError(
+        'use parseRouteInformationWithDependencies instead');
   }
 
   /// for use by the Router architecture as part of the RouteInformationParser
@@ -103,26 +114,4 @@
       state: configuration.extra,
     );
   }
-
-  /// Creates a match that routes to the error page.
-  RouteMatchList _errorScreen(Uri uri, String errorMessage) {
-    final Exception error = Exception(errorMessage);
-    return RouteMatchList(<RouteMatch>[
-      RouteMatch(
-        subloc: uri.path,
-        fullpath: uri.path,
-        encodedParams: <String, String>{},
-        queryParams: uri.queryParameters,
-        queryParametersAll: uri.queryParametersAll,
-        extra: null,
-        error: error,
-        route: GoRoute(
-          path: uri.toString(),
-          pageBuilder: (BuildContext context, GoRouterState state) {
-            throw UnimplementedError();
-          },
-        ),
-      ),
-    ]);
-  }
 }
diff --git a/packages/go_router/lib/src/redirection.dart b/packages/go_router/lib/src/redirection.dart
index be09bb2..9f68716 100644
--- a/packages/go_router/lib/src/redirection.dart
+++ b/packages/go_router/lib/src/redirection.dart
@@ -2,39 +2,107 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
+import 'package:flutter/cupertino.dart';
+
 import 'configuration.dart';
 import 'logging.dart';
 import 'match.dart';
 import 'matching.dart';
-import 'typedefs.dart';
 
 /// A GoRouter redirector function.
-// TODO(johnpryan): make redirector async
-// See https://github.com/flutter/flutter/issues/105808
-typedef RouteRedirector = RouteMatchList Function(RouteMatchList matches,
-    RouteConfiguration configuration, RouteMatcher matcher,
-    {Object? extra});
+typedef RouteRedirector = FutureOr<RouteMatchList> Function(
+    BuildContext, FutureOr<RouteMatchList>, RouteConfiguration, RouteMatcher,
+    {List<RouteMatchList>? redirectHistory, Object? extra});
 
 /// Processes redirects by returning a new [RouteMatchList] representing the new
 /// location.
-RouteMatchList redirect(RouteMatchList prevMatchList,
-    RouteConfiguration configuration, RouteMatcher matcher,
-    {Object? extra}) {
-  RouteMatchList matches;
+FutureOr<RouteMatchList> redirect(
+    BuildContext context,
+    FutureOr<RouteMatchList> prevMatchListFuture,
+    RouteConfiguration configuration,
+    RouteMatcher matcher,
+    {List<RouteMatchList>? redirectHistory,
+    Object? extra}) {
+  FutureOr<RouteMatchList> processRedirect(RouteMatchList prevMatchList) {
+    FutureOr<RouteMatchList> processTopLevelRedirect(
+        String? topRedirectLocation) {
+      if (topRedirectLocation != null) {
+        final RouteMatchList newMatch = _getNewMatches(
+          topRedirectLocation,
+          prevMatchList.location,
+          configuration,
+          matcher,
+          redirectHistory!,
+        );
+        if (newMatch.isError) {
+          return newMatch;
+        }
+        return redirect(
+          context,
+          newMatch,
+          configuration,
+          matcher,
+          redirectHistory: redirectHistory,
+          extra: extra,
+        );
+      }
 
-  // Store each redirect to detect loops
-  final List<RouteMatchList> redirects = <RouteMatchList>[prevMatchList];
+      // Merge new params to keep params from previously matched paths, e.g.
+      // /users/:userId/book/:bookId provides userId and bookId to bookgit /:bookId
+      Map<String, String> previouslyMatchedParams = <String, String>{};
+      for (final RouteMatch match in prevMatchList.matches) {
+        assert(
+          !previouslyMatchedParams.keys.any(match.encodedParams.containsKey),
+          'Duplicated parameter names',
+        );
+        match.encodedParams.addAll(previouslyMatchedParams);
+        previouslyMatchedParams = match.encodedParams;
+      }
+      FutureOr<RouteMatchList> processRouteLevelRedirect(
+          String? routeRedirectLocation) {
+        if (routeRedirectLocation != null) {
+          final RouteMatchList newMatch = _getNewMatches(
+            routeRedirectLocation,
+            prevMatchList.location,
+            configuration,
+            matcher,
+            redirectHistory!,
+          );
 
-  // Keep looping until redirecting is done
-  while (true) {
-    final RouteMatchList currentMatches = redirects.last;
+          if (newMatch.isError) {
+            return newMatch;
+          }
+          return redirect(
+            context,
+            newMatch,
+            configuration,
+            matcher,
+            redirectHistory: redirectHistory,
+            extra: extra,
+          );
+        }
+        return prevMatchList;
+      }
 
+      final FutureOr<String?> routeLevelRedirectResult =
+          _getRouteLevelRedirect(context, configuration, prevMatchList, 0);
+      if (routeLevelRedirectResult is String?) {
+        return processRouteLevelRedirect(routeLevelRedirectResult);
+      }
+      return routeLevelRedirectResult
+          .then<RouteMatchList>(processRouteLevelRedirect);
+    }
+
+    redirectHistory ??= <RouteMatchList>[prevMatchList];
     // Check for top-level redirect
-    final Uri uri = currentMatches.location;
-    final String? topRedirectLocation = configuration.topRedirect(
+    final Uri uri = prevMatchList.location;
+    final FutureOr<String?> topRedirectResult = configuration.topRedirect(
+      context,
       GoRouterState(
         configuration,
-        location: currentMatches.location.toString(),
+        location: prevMatchList.location.toString(),
         name: null,
         // No name available at the top level trim the query params off the
         // sub-location to match route.redirect
@@ -45,68 +113,89 @@
       ),
     );
 
-    if (topRedirectLocation != null) {
-      final RouteMatchList newMatch = matcher.findMatch(topRedirectLocation);
-      _addRedirect(redirects, newMatch, prevMatchList.location,
-          configuration.redirectLimit);
-      continue;
+    if (topRedirectResult is String?) {
+      return processTopLevelRedirect(topRedirectResult);
     }
+    return topRedirectResult.then<RouteMatchList>(processTopLevelRedirect);
+  }
 
-    // If there's no top-level redirect, keep the matches the same as before.
-    matches = currentMatches;
+  if (prevMatchListFuture is RouteMatchList) {
+    return processRedirect(prevMatchListFuture);
+  }
+  return prevMatchListFuture.then<RouteMatchList>(processRedirect);
+}
 
-    // Merge new params to keep params from previously matched paths, e.g.
-    // /users/:userId/book/:bookId provides userId and bookId to book/:bookId
-    Map<String, String> previouslyMatchedParams = <String, String>{};
-    for (final RouteMatch match in currentMatches.matches) {
-      assert(
-        !previouslyMatchedParams.keys.any(match.encodedParams.containsKey),
-        'Duplicated parameter names',
-      );
-      match.encodedParams.addAll(previouslyMatchedParams);
-      previouslyMatchedParams = match.encodedParams;
-    }
-
-    // check top route for redirect
-    final RouteMatch? top = matches.isNotEmpty ? matches.last : null;
-    if (top == null) {
-      break;
-    }
-
-    final RouteBase topRoute = top.route;
-    assert(topRoute is GoRoute,
-        'Last RouteMatch should contain a GoRoute, but was ${topRoute.runtimeType}');
-    final GoRoute topGoRoute = topRoute as GoRoute;
-    final GoRouterRedirect? redirect = topGoRoute.redirect;
-    if (redirect == null) {
-      break;
-    }
-
-    final String? topRouteLocation = redirect(
+FutureOr<String?> _getRouteLevelRedirect(
+  BuildContext context,
+  RouteConfiguration configuration,
+  RouteMatchList matchList,
+  int currentCheckIndex,
+) {
+  if (currentCheckIndex >= matchList.matches.length) {
+    return null;
+  }
+  final RouteMatch match = matchList.matches[currentCheckIndex];
+  FutureOr<String?> processRouteRedirect(String? newLocation) =>
+      newLocation ??
+      _getRouteLevelRedirect(
+          context, configuration, matchList, currentCheckIndex + 1);
+  final RouteBase route = match.route;
+  FutureOr<String?> routeRedirectResult;
+  if (route is GoRoute && route.redirect != null) {
+    routeRedirectResult = route.redirect!(
+      context,
       GoRouterState(
         configuration,
-        location: currentMatches.location.toString(),
-        subloc: top.subloc,
-        name: topGoRoute.name,
-        path: topGoRoute.path,
-        fullpath: top.fullpath,
-        extra: top.extra,
-        params: top.decodedParams,
-        queryParams: top.queryParams,
-        queryParametersAll: top.queryParametersAll,
+        location: matchList.location.toString(),
+        subloc: match.subloc,
+        name: route.name,
+        path: route.path,
+        fullpath: match.fullpath,
+        extra: match.extra,
+        params: match.decodedParams,
+        queryParams: match.queryParams,
+        queryParametersAll: match.queryParametersAll,
       ),
     );
-
-    if (topRouteLocation == null) {
-      break;
-    }
-
-    final RouteMatchList newMatchList = matcher.findMatch(topRouteLocation);
-    _addRedirect(redirects, newMatchList, prevMatchList.location,
-        configuration.redirectLimit);
-    continue;
   }
-  return matches;
+  if (routeRedirectResult is String?) {
+    return processRouteRedirect(routeRedirectResult);
+  }
+  return routeRedirectResult.then<String?>(processRouteRedirect);
+}
+
+RouteMatchList _getNewMatches(
+  String newLocation,
+  Uri previousLocation,
+  RouteConfiguration configuration,
+  RouteMatcher matcher,
+  List<RouteMatchList> redirectHistory,
+) {
+  try {
+    final RouteMatchList newMatch = matcher.findMatch(newLocation);
+    _addRedirect(redirectHistory, newMatch, previousLocation,
+        configuration.redirectLimit);
+    return newMatch;
+  } on RedirectionError catch (e) {
+    return _handleRedirectionError(e);
+  } on MatcherError catch (e) {
+    return _handleMatcherError(e);
+  }
+}
+
+RouteMatchList _handleMatcherError(MatcherError error) {
+  // The RouteRedirector uses the matcher to find the match, so a match
+  // exception can happen during redirection. For example, the redirector
+  // redirects from `/a` to `/b`, it needs to get the matches for `/b`.
+  log.info('Match error: ${error.message}');
+  final Uri uri = Uri.parse(error.location);
+  return errorScreen(uri, error.message);
+}
+
+RouteMatchList _handleRedirectionError(RedirectionError error) {
+  log.info('Redirection error: ${error.message}');
+  final Uri uri = error.location;
+  return errorScreen(uri, error.message);
 }
 
 /// A configuration error detected while processing redirects.
diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart
index 98dd5f5..14800d2 100644
--- a/packages/go_router/lib/src/route.dart
+++ b/packages/go_router/lib/src/route.dart
@@ -279,10 +279,38 @@
   /// );
   /// ```
   ///
+  /// If there are multiple redirects in the matched routes, the parent route's
+  /// redirect takes priority over sub-route's.
+  ///
+  /// For example:
+  /// ```
+  /// final GoRouter _router = GoRouter(
+  ///   routes: <GoRoute>[
+  ///     GoRoute(
+  ///       path: '/',
+  ///       redirect: (_) => '/page1', // this takes priority over the sub-route.
+  ///       routes: <GoRoute>[
+  ///         GoRoute(
+  ///           path: 'child',
+  ///           redirect: (_) => '/page2',
+  ///         ),
+  ///       ],
+  ///     ),
+  ///   ],
+  /// );
+  /// ```
+  ///
+  /// The `context.go('/child')` will be redirected to `/page1` instead of
+  /// `/page2`.
+  ///
   /// Redirect can also be used for conditionally preventing users from visiting
   /// routes, also known as route guards. One canonical example is user
   /// authentication. See [Redirection](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart)
   /// for a complete runnable example.
+  ///
+  /// If [BuildContext.dependOnInheritedWidgetOfExactType] is used during the
+  /// redirection (which is how `of` method is usually implemented), a
+  /// re-evaluation will be triggered if the [InheritedWidget] changes.
   final GoRouterRedirect? redirect;
 
   /// An optional key specifying which Navigator to display this route's screen
diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart
index 4a2d4d4..f3a7f32 100644
--- a/packages/go_router/lib/src/route_data.dart
+++ b/packages/go_router/lib/src/route_data.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
 import 'package:flutter/widgets.dart';
 import 'package:meta/meta.dart';
 import 'package:meta/meta_meta.dart';
@@ -68,7 +70,7 @@
   /// [redirect].
   ///
   /// Corresponds to [GoRoute.redirect].
-  String? redirect() => null;
+  FutureOr<String?> redirect() => null;
 
   /// A helper function used by generated code.
   ///
@@ -108,7 +110,8 @@
     Page<void> pageBuilder(BuildContext context, GoRouterState state) =>
         factoryImpl(state).buildPageWithState(context, state);
 
-    String? redirect(GoRouterState state) => factoryImpl(state).redirect();
+    FutureOr<String?> redirect(BuildContext context, GoRouterState state) =>
+        factoryImpl(state).redirect();
 
     return GoRoute(
       path: path,
diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart
index 486a717..b52cc22 100644
--- a/packages/go_router/lib/src/router.dart
+++ b/packages/go_router/lib/src/router.dart
@@ -29,13 +29,17 @@
 ///
 /// The `redirect` does top-level redirection before the URIs are parsed by
 /// the `routes`. Consider using [GoRoute.redirect] for individual route
-/// redirection.
+/// redirection. If [BuildContext.dependOnInheritedWidgetOfExactType] is used
+/// during the redirection (which is how `of` methods are usually implemented),
+/// a re-evaluation will be triggered when the [InheritedWidget] changes.
 ///
 /// See also:
 ///  * [GoRoute], which provides APIs to define the routing table.
 ///  * [examples](https://github.com/flutter/packages/tree/main/packages/go_router/example),
 ///    which contains examples for different routing scenarios.
-class GoRouter extends ChangeNotifier with NavigatorObserver {
+class GoRouter extends ChangeNotifier
+    with NavigatorObserver
+    implements RouterConfig<RouteMatchList> {
   /// Default constructor to configure a GoRouter with a routes builder
   /// and an error page builder.
   ///
@@ -51,21 +55,11 @@
     int redirectLimit = 5,
     bool routerNeglect = false,
     String? initialLocation,
-    // TODO(johnpryan): Deprecate this parameter
-    // See https://github.com/flutter/flutter/issues/108132
-    UrlPathStrategy? urlPathStrategy,
     List<NavigatorObserver>? observers,
     bool debugLogDiagnostics = false,
-    // TODO(johnpryan): Deprecate this parameter
-    // See https://github.com/flutter/flutter/issues/108145
-    GoRouterNavigatorBuilder? navigatorBuilder,
     GlobalKey<NavigatorState>? navigatorKey,
     String? restorationScopeId,
-  }) {
-    if (urlPathStrategy != null) {
-      setUrlPathStrategy(urlPathStrategy);
-    }
-
+  }) : backButtonDispatcher = RootBackButtonDispatcher() {
     setLogging(enabled: debugLogDiagnostics);
     WidgetsFlutterBinding.ensureInitialized();
 
@@ -73,7 +67,7 @@
 
     _routeConfiguration = RouteConfiguration(
       routes: routes,
-      topRedirect: redirect ?? (_) => null,
+      topRedirect: redirect ?? (_, __) => null,
       redirectLimit: redirectLimit,
       navigatorKey: navigatorKey,
     );
@@ -104,9 +98,10 @@
           (BuildContext context, GoRouterState state, Navigator nav) =>
               InheritedGoRouter(
         goRouter: this,
-        child: navigatorBuilder?.call(context, state, nav) ?? nav,
+        child: nav,
       ),
     );
+
     assert(() {
       log.info('setting initial location $initialLocation');
       return true;
@@ -118,15 +113,21 @@
   late final GoRouterDelegate _routerDelegate;
   late final GoRouteInformationProvider _routeInformationProvider;
 
+  @override
+  final BackButtonDispatcher backButtonDispatcher;
+
   /// The router delegate. Provide this to the MaterialApp or CupertinoApp's
   /// `.router()` constructor
+  @override
   GoRouterDelegate get routerDelegate => _routerDelegate;
 
   /// The route information provider used by [GoRouter].
+  @override
   GoRouteInformationProvider get routeInformationProvider =>
       _routeInformationProvider;
 
   /// The route information parser used by [GoRouter].
+  @override
   GoRouteInformationParser get routeInformationParser =>
       _routeInformationParser;
 
@@ -185,8 +186,12 @@
       return true;
     }());
     _routeInformationParser
-        .parseRouteInformation(
-            DebugGoRouteInformation(location: location, state: extra))
+        .parseRouteInformationWithDependencies(
+      DebugGoRouteInformation(location: location, state: extra),
+      // TODO(chunhtai): avoid accessing the context directly through global key.
+      // https://github.com/flutter/flutter/issues/99112
+      _routerDelegate.navigatorKey.currentContext!,
+    )
         .then<void>((RouteMatchList matches) {
       _routerDelegate.push(matches.last);
     });
@@ -213,8 +218,11 @@
   /// * [push] which pushes the location onto the page stack.
   void replace(String location, {Object? extra}) {
     routeInformationParser
-        .parseRouteInformation(
+        .parseRouteInformationWithDependencies(
       DebugGoRouteInformation(location: location, state: extra),
+      // TODO(chunhtai): avoid accessing the context directly through global key.
+      // https://github.com/flutter/flutter/issues/99112
+      _routerDelegate.navigatorKey.currentContext!,
     )
         .then<void>((RouteMatchList matchList) {
       routerDelegate.replace(matchList.matches.last);
diff --git a/packages/go_router/lib/src/typedefs.dart b/packages/go_router/lib/src/typedefs.dart
index ab540f6..f894488 100644
--- a/packages/go_router/lib/src/typedefs.dart
+++ b/packages/go_router/lib/src/typedefs.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async' show FutureOr;
+
 import 'package:flutter/widgets.dart';
 
 import 'configuration.dart';
@@ -47,4 +49,5 @@
 );
 
 /// The signature of the redirect callback.
-typedef GoRouterRedirect = String? Function(GoRouterState state);
+typedef GoRouterRedirect = FutureOr<String?> Function(
+    BuildContext context, GoRouterState state);
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index fc097cf..5c67cb6 100644
--- a/packages/go_router/pubspec.yaml
+++ b/packages/go_router/pubspec.yaml
@@ -1,13 +1,13 @@
 name: go_router
 description: A declarative router for Flutter based on Navigation 2 supporting
   deep linking, data-driven routes and more
-version: 4.5.1
+version: 5.0.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
 
 environment:
   sdk: ">=2.17.0 <3.0.0"
-  flutter: ">=3.0.0"
+  flutter: ">=3.3.0"
 
 dependencies:
   collection: ^1.15.0
diff --git a/packages/go_router/test/builder_test.dart b/packages/go_router/test/builder_test.dart
index 19830b0..92eb17f 100644
--- a/packages/go_router/test/builder_test.dart
+++ b/packages/go_router/test/builder_test.dart
@@ -22,7 +22,7 @@
           ),
         ],
         redirectLimit: 10,
-        topRedirect: (GoRouterState state) {
+        topRedirect: (BuildContext context, GoRouterState state) {
           return null;
         },
         navigatorKey: GlobalKey<NavigatorState>(),
@@ -69,7 +69,7 @@
               ]),
         ],
         redirectLimit: 10,
-        topRedirect: (GoRouterState state) {
+        topRedirect: (BuildContext context, GoRouterState state) {
           return null;
         },
         navigatorKey: GlobalKey<NavigatorState>(),
@@ -112,7 +112,7 @@
           ),
         ],
         redirectLimit: 10,
-        topRedirect: (GoRouterState state) {
+        topRedirect: (BuildContext context, GoRouterState state) {
           return null;
         },
       );
@@ -167,7 +167,7 @@
           ),
         ],
         redirectLimit: 10,
-        topRedirect: (GoRouterState state) {
+        topRedirect: (BuildContext context, GoRouterState state) {
           return null;
         },
       );
@@ -245,7 +245,7 @@
           ),
         ],
         redirectLimit: 10,
-        topRedirect: (GoRouterState state) {
+        topRedirect: (BuildContext context, GoRouterState state) {
           return null;
         },
       );
diff --git a/packages/go_router/test/configuration_test.dart b/packages/go_router/test/configuration_test.dart
index b44d508..a01f7d1 100644
--- a/packages/go_router/test/configuration_test.dart
+++ b/packages/go_router/test/configuration_test.dart
@@ -50,7 +50,7 @@
               ),
             ],
             redirectLimit: 10,
-            topRedirect: (GoRouterState state) {
+            topRedirect: (BuildContext context, GoRouterState state) {
               return null;
             },
           );
@@ -71,7 +71,7 @@
               ShellRoute(routes: shellRouteChildren),
             ],
             redirectLimit: 10,
-            topRedirect: (GoRouterState state) {
+            topRedirect: (BuildContext context, GoRouterState state) {
               return null;
             },
           );
@@ -111,7 +111,7 @@
               ),
             ],
             redirectLimit: 10,
-            topRedirect: (GoRouterState state) {
+            topRedirect: (BuildContext context, GoRouterState state) {
               return null;
             },
           );
@@ -168,7 +168,7 @@
             ),
           ],
           redirectLimit: 10,
-          topRedirect: (GoRouterState state) {
+          topRedirect: (BuildContext context, GoRouterState state) {
             return null;
           },
         );
@@ -217,7 +217,7 @@
               ),
             ],
             redirectLimit: 10,
-            topRedirect: (GoRouterState state) {
+            topRedirect: (BuildContext context, GoRouterState state) {
               return null;
             },
           ),
@@ -267,7 +267,7 @@
             ),
           ],
           redirectLimit: 10,
-          topRedirect: (GoRouterState state) {
+          topRedirect: (BuildContext context, GoRouterState state) {
             return null;
           },
         );
@@ -325,7 +325,7 @@
               ),
             ],
             redirectLimit: 10,
-            topRedirect: (GoRouterState state) {
+            topRedirect: (BuildContext context, GoRouterState state) {
               return null;
             },
           ),
@@ -383,7 +383,7 @@
           ),
         ],
         redirectLimit: 10,
-        topRedirect: (GoRouterState state) {
+        topRedirect: (BuildContext context, GoRouterState state) {
           return null;
         },
       );
@@ -410,7 +410,7 @@
             ]),
           ],
           redirectLimit: 10,
-          topRedirect: (GoRouterState state) {
+          topRedirect: (BuildContext context, GoRouterState state) {
             return null;
           },
         );
@@ -439,7 +439,7 @@
             ),
           ],
           redirectLimit: 10,
-          topRedirect: (GoRouterState state) {
+          topRedirect: (BuildContext context, GoRouterState state) {
             return null;
           },
         );
diff --git a/packages/go_router/test/custom_transition_page_test.dart b/packages/go_router/test/custom_transition_page_test.dart
index 5845be6..50fe625 100644
--- a/packages/go_router/test/custom_transition_page_test.dart
+++ b/packages/go_router/test/custom_transition_page_test.dart
@@ -24,9 +24,7 @@
     );
     await tester.pumpWidget(
       MaterialApp.router(
-        routeInformationProvider: router.routeInformationProvider,
-        routeInformationParser: router.routeInformationParser,
-        routerDelegate: router.routerDelegate,
+        routerConfig: router,
         title: 'GoRouter Example',
       ),
     );
diff --git a/packages/go_router/test/delegate_test.dart b/packages/go_router/test/delegate_test.dart
index d227c3f..f60ce65 100644
--- a/packages/go_router/test/delegate_test.dart
+++ b/packages/go_router/test/delegate_test.dart
@@ -25,9 +25,8 @@
     refreshListenable: refreshListenable,
   );
   await tester.pumpWidget(MaterialApp.router(
-      routeInformationProvider: router.routeInformationProvider,
-      routeInformationParser: router.routeInformationParser,
-      routerDelegate: router.routerDelegate));
+    routerConfig: router,
+  ));
   return router;
 }
 
@@ -127,9 +126,7 @@
         );
         await tester.pumpWidget(
           MaterialApp.router(
-            routeInformationProvider: goRouter.routeInformationProvider,
-            routeInformationParser: goRouter.routeInformationParser,
-            routerDelegate: goRouter.routerDelegate,
+            routerConfig: goRouter,
           ),
         );
 
@@ -179,9 +176,7 @@
         );
         await tester.pumpWidget(
           MaterialApp.router(
-            routeInformationProvider: goRouter.routeInformationProvider,
-            routeInformationParser: goRouter.routeInformationParser,
-            routerDelegate: goRouter.routerDelegate,
+            routerConfig: goRouter,
           ),
         );
 
diff --git a/packages/go_router/test/go_route_test.dart b/packages/go_router/test/go_route_test.dart
index f2de79b..31361aa 100644
--- a/packages/go_router/test/go_route_test.dart
+++ b/packages/go_router/test/go_route_test.dart
@@ -15,6 +15,6 @@
   });
 
   test('does not throw when only redirect is provided', () {
-    GoRoute(path: '/', redirect: (_) => '/a');
+    GoRoute(path: '/', redirect: (_, __) => '/a');
   });
 }
diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart
index 0c66068..67d3288 100644
--- a/packages/go_router/test/go_router_test.dart
+++ b/packages/go_router/test/go_router_test.dart
@@ -4,16 +4,11 @@
 
 // ignore_for_file: cascade_invocations, diagnostic_describe_all_properties
 
-import 'dart:async';
-
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:go_router/src/delegate.dart';
+import 'package:go_router/go_router.dart';
 import 'package:go_router/src/match.dart';
-import 'package:go_router/src/misc/extensions.dart';
-import 'package:go_router/src/route.dart';
-import 'package:go_router/src/router.dart';
-import 'package:go_router/src/state.dart';
 import 'package:logging/logging.dart';
 
 import 'test_helpers.dart';
@@ -21,6 +16,17 @@
 const bool enableLogs = true;
 final Logger log = Logger('GoRouter tests');
 
+Future<void> sendPlatformUrl(String url) async {
+  final Map<String, dynamic> testRouteInformation = <String, dynamic>{
+    'location': url,
+  };
+  final ByteData message = const JSONMethodCodec().encodeMethodCall(
+    MethodCall('pushRouteInformation', testRouteInformation),
+  );
+  await ServicesBinding.instance.defaultBinaryMessenger
+      .handlePlatformMessage('flutter/navigation', message, (_) {});
+}
+
 void main() {
   if (enableLogs) {
     Logger.root.onRecord.listen((LogRecord e) => debugPrint('$e'));
@@ -191,7 +197,9 @@
         (WidgetTester tester) async {
       final List<GoRoute> routes = <GoRoute>[
         GoRoute(
-            path: '/profile', builder: dummy, redirect: (_) => '/profile/foo'),
+            path: '/profile',
+            builder: dummy,
+            redirect: (_, __) => '/profile/foo'),
         GoRoute(path: '/profile/:kind', builder: dummy),
       ];
 
@@ -207,7 +215,9 @@
         (WidgetTester tester) async {
       final List<GoRoute> routes = <GoRoute>[
         GoRoute(
-            path: '/profile', builder: dummy, redirect: (_) => '/profile/foo'),
+            path: '/profile',
+            builder: dummy,
+            redirect: (_, __) => '/profile/foo'),
         GoRoute(path: '/profile/:kind', builder: dummy),
       ];
 
@@ -833,7 +843,7 @@
         GoRoute(
           path: '/',
           builder: dummy,
-          redirect: (_) => '/family/f2',
+          redirect: (_, __) => '/family/f2',
         ),
         GoRoute(
           path: '/family/:fid',
@@ -932,12 +942,24 @@
           ],
         ),
       ];
+      bool redirected = false;
 
       final GoRouter router = await createRouter(routes, tester,
-          redirect: (GoRouterState state) =>
-              state.subloc == '/login' ? null : '/login');
+          redirect: (BuildContext context, GoRouterState state) {
+        redirected = true;
+        return state.subloc == '/login' ? null : '/login';
+      });
 
       expect(router.location, '/login');
+      expect(redirected, isTrue);
+
+      redirected = false;
+      // Directly set the url through platform message.
+      await sendPlatformUrl('/dummy');
+
+      await tester.pumpAndSettle();
+      expect(router.location, '/login');
+      expect(redirected, isTrue);
     });
 
     testWidgets('top-level redirect w/ named routes',
@@ -968,7 +990,7 @@
       final GoRouter router = await createRouter(
         routes,
         tester,
-        redirect: (GoRouterState state) =>
+        redirect: (BuildContext context, GoRouterState state) =>
             state.subloc == '/login' ? null : state.namedLocation('login'),
       );
       expect(router.location, '/login');
@@ -985,7 +1007,7 @@
               path: 'dummy',
               builder: (BuildContext context, GoRouterState state) =>
                   const DummyScreen(),
-              redirect: (GoRouterState state) => '/login',
+              redirect: (BuildContext context, GoRouterState state) => '/login',
             ),
             GoRoute(
               path: 'login',
@@ -1002,6 +1024,49 @@
       expect(router.location, '/login');
     });
 
+    testWidgets('top-level redirect take priority over route level',
+        (WidgetTester tester) async {
+      final List<GoRoute> routes = <GoRoute>[
+        GoRoute(
+          path: '/',
+          builder: (BuildContext context, GoRouterState state) =>
+              const HomeScreen(),
+          routes: <GoRoute>[
+            GoRoute(
+                path: 'dummy',
+                builder: (BuildContext context, GoRouterState state) =>
+                    const DummyScreen(),
+                redirect: (BuildContext context, GoRouterState state) {
+                  // should never be reached.
+                  assert(false);
+                  return '/dummy2';
+                }),
+            GoRoute(
+                path: 'dummy2',
+                builder: (BuildContext context, GoRouterState state) =>
+                    const DummyScreen()),
+            GoRoute(
+                path: 'login',
+                builder: (BuildContext context, GoRouterState state) =>
+                    const LoginScreen()),
+          ],
+        ),
+      ];
+      bool redirected = false;
+      final GoRouter router = await createRouter(routes, tester,
+          redirect: (BuildContext context, GoRouterState state) {
+        redirected = true;
+        return state.subloc == '/login' ? null : '/login';
+      });
+      redirected = false;
+      // Directly set the url through platform message.
+      await sendPlatformUrl('/dummy');
+
+      await tester.pumpAndSettle();
+      expect(router.location, '/login');
+      expect(redirected, isTrue);
+    });
+
     testWidgets('route-level redirect w/ named routes',
         (WidgetTester tester) async {
       final List<GoRoute> routes = <GoRoute>[
@@ -1016,7 +1081,8 @@
               path: 'dummy',
               builder: (BuildContext context, GoRouterState state) =>
                   const DummyScreen(),
-              redirect: (GoRouterState state) => state.namedLocation('login'),
+              redirect: (BuildContext context, GoRouterState state) =>
+                  state.namedLocation('login'),
             ),
             GoRoute(
               name: 'login',
@@ -1050,14 +1116,14 @@
               path: 'dummy2',
               builder: (BuildContext context, GoRouterState state) =>
                   const DummyScreen(),
-              redirect: (GoRouterState state) => '/',
+              redirect: (BuildContext context, GoRouterState state) => '/',
             ),
           ],
         ),
       ];
 
       final GoRouter router = await createRouter(routes, tester,
-          redirect: (GoRouterState state) =>
+          redirect: (BuildContext context, GoRouterState state) =>
               state.subloc == '/dummy1' ? '/dummy2' : null);
       router.go('/dummy1');
       await tester.pump();
@@ -1066,11 +1132,12 @@
 
     testWidgets('top-level redirect loop', (WidgetTester tester) async {
       final GoRouter router = await createRouter(<GoRoute>[], tester,
-          redirect: (GoRouterState state) => state.subloc == '/'
-              ? '/login'
-              : state.subloc == '/login'
-                  ? '/'
-                  : null);
+          redirect: (BuildContext context, GoRouterState state) =>
+              state.subloc == '/'
+                  ? '/login'
+                  : state.subloc == '/login'
+                      ? '/'
+                      : null);
 
       final List<RouteMatch> matches = router.routerDelegate.matches.matches;
       expect(matches, hasLength(1));
@@ -1086,12 +1153,12 @@
           GoRoute(
             path: '/',
             builder: dummy,
-            redirect: (GoRouterState state) => '/login',
+            redirect: (BuildContext context, GoRouterState state) => '/login',
           ),
           GoRoute(
             path: '/login',
             builder: dummy,
-            redirect: (GoRouterState state) => '/',
+            redirect: (BuildContext context, GoRouterState state) => '/',
           ),
         ],
         tester,
@@ -1111,11 +1178,11 @@
           GoRoute(
             path: '/login',
             builder: dummy,
-            redirect: (GoRouterState state) => '/',
+            redirect: (BuildContext context, GoRouterState state) => '/',
           ),
         ],
         tester,
-        redirect: (GoRouterState state) =>
+        redirect: (BuildContext context, GoRouterState state) =>
             state.subloc == '/' ? '/login' : null,
       );
 
@@ -1132,11 +1199,12 @@
       final GoRouter router = await createRouter(
         <GoRoute>[],
         tester,
-        redirect: (GoRouterState state) => state.subloc == '/'
-            ? '/login?from=${state.location}'
-            : state.subloc == '/login'
-                ? '/'
-                : null,
+        redirect: (BuildContext context, GoRouterState state) =>
+            state.subloc == '/'
+                ? '/login?from=${state.location}'
+                : state.subloc == '/login'
+                    ? '/'
+                    : null,
       );
 
       final List<RouteMatch> matches = router.routerDelegate.matches.matches;
@@ -1158,7 +1226,7 @@
         GoRoute(
           path: '/dummy',
           builder: dummy,
-          redirect: (GoRouterState state) => '/',
+          redirect: (BuildContext context, GoRouterState state) => '/',
         ),
       ];
 
@@ -1188,7 +1256,7 @@
         routes,
         tester,
         initialLocation: '/login?from=/',
-        redirect: (GoRouterState state) {
+        redirect: (BuildContext context, GoRouterState state) {
           expect(Uri.parse(state.location).queryParameters, isNotEmpty);
           expect(Uri.parse(state.subloc).queryParameters, isEmpty);
           expect(state.path, isNull);
@@ -1210,7 +1278,7 @@
       final List<GoRoute> routes = <GoRoute>[
         GoRoute(
           path: '/book/:bookId',
-          redirect: (GoRouterState state) {
+          redirect: (BuildContext context, GoRouterState state) {
             expect(state.location, loc);
             expect(state.subloc, loc);
             expect(state.path, '/book/:bookId');
@@ -1248,7 +1316,7 @@
               routes: <GoRoute>[
                 GoRoute(
                   path: 'person/:pid',
-                  redirect: (GoRouterState s) {
+                  redirect: (BuildContext context, GoRouterState s) {
                     expect(s.params['fid'], 'f2');
                     expect(s.params['pid'], 'p1');
                     return null;
@@ -1283,7 +1351,8 @@
       final GoRouter router = await createRouter(
         <GoRoute>[],
         tester,
-        redirect: (GoRouterState state) => '/${state.location}+',
+        redirect: (BuildContext context, GoRouterState state) =>
+            '/${state.location}+',
         redirectLimit: 10,
       );
 
@@ -1312,7 +1381,7 @@
               builder: (BuildContext context, GoRouterState state) {
                 return const LoginScreen();
               },
-              redirect: (GoRouterState state) {
+              redirect: (BuildContext context, GoRouterState state) {
                 isCallRouteRedirect = true;
                 expect(state.extra, isNotNull);
                 return null;
@@ -1326,7 +1395,7 @@
       final GoRouter router = await createRouter(
         routes,
         tester,
-        redirect: (GoRouterState state) {
+        redirect: (BuildContext context, GoRouterState state) {
           if (state.location == '/login') {
             isCallTopRedirect = true;
             expect(state.extra, isNotNull);
@@ -1342,6 +1411,52 @@
       expect(isCallTopRedirect, true);
       expect(isCallRouteRedirect, true);
     });
+
+    testWidgets('parent route level redirect take priority over child',
+        (WidgetTester tester) async {
+      final List<GoRoute> routes = <GoRoute>[
+        GoRoute(
+          path: '/',
+          builder: (BuildContext context, GoRouterState state) =>
+              const HomeScreen(),
+          routes: <GoRoute>[
+            GoRoute(
+                path: 'dummy',
+                builder: (BuildContext context, GoRouterState state) =>
+                    const DummyScreen(),
+                redirect: (BuildContext context, GoRouterState state) =>
+                    '/other',
+                routes: <GoRoute>[
+                  GoRoute(
+                    path: 'dummy2',
+                    builder: (BuildContext context, GoRouterState state) =>
+                        const DummyScreen(),
+                    redirect: (BuildContext context, GoRouterState state) {
+                      assert(false);
+                      return '/other2';
+                    },
+                  ),
+                ]),
+            GoRoute(
+                path: 'other',
+                builder: (BuildContext context, GoRouterState state) =>
+                    const DummyScreen()),
+            GoRoute(
+                path: 'other2',
+                builder: (BuildContext context, GoRouterState state) =>
+                    const DummyScreen()),
+          ],
+        ),
+      ];
+
+      final GoRouter router = await createRouter(routes, tester);
+
+      // Directly set the url through platform message.
+      await sendPlatformUrl('/dummy/dummy2');
+
+      await tester.pumpAndSettle();
+      expect(router.location, '/other');
+    });
   });
 
   group('initial location', () {
@@ -1379,7 +1494,7 @@
         GoRoute(
           path: '/dummy',
           builder: dummy,
-          redirect: (GoRouterState state) => '/',
+          redirect: (BuildContext context, GoRouterState state) => '/',
         ),
       ];
 
@@ -1813,54 +1928,6 @@
     );
   });
 
-  group('refresh listenable', () {
-    late StreamController<int> streamController;
-
-    setUpAll(() async {
-      streamController = StreamController<int>.broadcast();
-      await streamController.addStream(Stream<int>.value(0));
-    });
-
-    tearDownAll(() {
-      streamController.close();
-    });
-
-    group('stream', () {
-      test('no stream emits', () async {
-        // Act
-        final GoRouterRefreshStreamSpy notifyListener =
-            GoRouterRefreshStreamSpy(
-          streamController.stream,
-        );
-
-        // Assert
-        expect(notifyListener.notifyCount, equals(1));
-
-        // Cleanup
-        notifyListener.dispose();
-      });
-
-      test('three stream emits', () async {
-        // Arrange
-        final List<int> toEmit = <int>[1, 2, 3];
-
-        // Act
-        final GoRouterRefreshStreamSpy notifyListener =
-            GoRouterRefreshStreamSpy(
-          streamController.stream,
-        );
-
-        await streamController.addStream(Stream<int>.fromIterable(toEmit));
-
-        // Assert
-        expect(notifyListener.notifyCount, equals(toEmit.length + 1));
-
-        // Cleanup
-        notifyListener.dispose();
-      });
-    });
-  });
-
   group('GoRouterHelper extensions', () {
     final GlobalKey<DummyStatefulWidgetState> key =
         GlobalKey<DummyStatefulWidgetState>();
@@ -1895,9 +1962,7 @@
           GoRouterNamedLocationSpy(routes: routes);
       await tester.pumpWidget(
         MaterialApp.router(
-          routeInformationProvider: router.routeInformationProvider,
-          routeInformationParser: router.routeInformationParser,
-          routerDelegate: router.routerDelegate,
+          routerConfig: router,
           title: 'GoRouter Example',
         ),
       );
@@ -1915,9 +1980,7 @@
       final GoRouterGoSpy router = GoRouterGoSpy(routes: routes);
       await tester.pumpWidget(
         MaterialApp.router(
-          routeInformationProvider: router.routeInformationProvider,
-          routeInformationParser: router.routeInformationParser,
-          routerDelegate: router.routerDelegate,
+          routerConfig: router,
           title: 'GoRouter Example',
         ),
       );
@@ -1934,9 +1997,7 @@
       final GoRouterGoNamedSpy router = GoRouterGoNamedSpy(routes: routes);
       await tester.pumpWidget(
         MaterialApp.router(
-          routeInformationProvider: router.routeInformationProvider,
-          routeInformationParser: router.routeInformationParser,
-          routerDelegate: router.routerDelegate,
+          routerConfig: router,
           title: 'GoRouter Example',
         ),
       );
@@ -1957,9 +2018,7 @@
       final GoRouterPushSpy router = GoRouterPushSpy(routes: routes);
       await tester.pumpWidget(
         MaterialApp.router(
-          routeInformationProvider: router.routeInformationProvider,
-          routeInformationParser: router.routeInformationParser,
-          routerDelegate: router.routerDelegate,
+          routerConfig: router,
           title: 'GoRouter Example',
         ),
       );
@@ -1976,9 +2035,7 @@
       final GoRouterPushNamedSpy router = GoRouterPushNamedSpy(routes: routes);
       await tester.pumpWidget(
         MaterialApp.router(
-          routeInformationProvider: router.routeInformationProvider,
-          routeInformationParser: router.routeInformationParser,
-          routerDelegate: router.routerDelegate,
+          routerConfig: router,
           title: 'GoRouter Example',
         ),
       );
@@ -1998,9 +2055,7 @@
       final GoRouterPopSpy router = GoRouterPopSpy(routes: routes);
       await tester.pumpWidget(
         MaterialApp.router(
-          routeInformationProvider: router.routeInformationProvider,
-          routeInformationParser: router.routeInformationParser,
-          routerDelegate: router.routerDelegate,
+          routerConfig: router,
           title: 'GoRouter Example',
         ),
       );
@@ -2462,30 +2517,5 @@
         },
       );
     });
-
-    testWidgets('uses navigatorBuilder when provided',
-        (WidgetTester tester) async {
-      final Func3<Widget, BuildContext, GoRouterState, Widget>
-          navigatorBuilder = expectAsync3(fakeNavigationBuilder);
-      final GoRouter router = GoRouter(
-        initialLocation: '/',
-        routes: <GoRoute>[
-          GoRoute(path: '/', builder: (_, __) => const DummyStatefulWidget()),
-          GoRoute(
-            path: '/error',
-            builder: (_, __) => TestErrorScreen(TestFailure('exception')),
-          ),
-        ],
-        navigatorBuilder: navigatorBuilder,
-      );
-
-      final GoRouterDelegate delegate = router.routerDelegate;
-      delegate.builder.builderWithNav(
-        DummyBuildContext(),
-        GoRouterState(router.routeConfiguration,
-            location: '/foo', subloc: '/bar', name: 'baz'),
-        const Navigator(),
-      );
-    });
   });
 }
diff --git a/packages/go_router/test/helpers/error_screen_helpers.dart b/packages/go_router/test/helpers/error_screen_helpers.dart
index f804f44..26822e2 100644
--- a/packages/go_router/test/helpers/error_screen_helpers.dart
+++ b/packages/go_router/test/helpers/error_screen_helpers.dart
@@ -51,18 +51,14 @@
 
 Widget materialAppRouterBuilder(GoRouter router) {
   return MaterialApp.router(
-    routeInformationProvider: router.routeInformationProvider,
-    routeInformationParser: router.routeInformationParser,
-    routerDelegate: router.routerDelegate,
+    routerConfig: router,
     title: 'GoRouter Example',
   );
 }
 
 Widget cupertinoAppRouterBuilder(GoRouter router) {
   return CupertinoApp.router(
-    routeInformationProvider: router.routeInformationProvider,
-    routeInformationParser: router.routeInformationParser,
-    routerDelegate: router.routerDelegate,
+    routerConfig: router,
     title: 'GoRouter Example',
   );
 }
diff --git a/packages/go_router/test/inherited_test.dart b/packages/go_router/test/inherited_test.dart
index 920a61c..9fbb915 100644
--- a/packages/go_router/test/inherited_test.dart
+++ b/packages/go_router/test/inherited_test.dart
@@ -135,4 +135,7 @@
       Object? extra}) {
     latestPushedName = name;
   }
+
+  @override
+  BackButtonDispatcher get backButtonDispatcher => RootBackButtonDispatcher();
 }
diff --git a/packages/go_router/test/parser_test.dart b/packages/go_router/test/parser_test.dart
index aa67ad0..0165466 100644
--- a/packages/go_router/test/parser_test.dart
+++ b/packages/go_router/test/parser_test.dart
@@ -4,13 +4,33 @@
 
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:go_router/go_router.dart';
 import 'package:go_router/src/configuration.dart';
+import 'package:go_router/src/information_provider.dart';
 import 'package:go_router/src/match.dart';
 import 'package:go_router/src/matching.dart';
 import 'package:go_router/src/parser.dart';
 
 void main() {
-  test('GoRouteInformationParser can parse route', () async {
+  Future<GoRouteInformationParser> createParser(
+    WidgetTester tester, {
+    required List<RouteBase> routes,
+    int redirectLimit = 5,
+    GoRouterRedirect? redirect,
+  }) async {
+    final GoRouter router = GoRouter(
+      routes: routes,
+      redirectLimit: redirectLimit,
+      redirect: redirect,
+    );
+    await tester.pumpWidget(MaterialApp.router(
+      routerConfig: router,
+    ));
+    return router.routeInformationParser;
+  }
+
+  testWidgets('GoRouteInformationParser can parse route',
+      (WidgetTester tester) async {
     final List<GoRoute> routes = <GoRoute>[
       GoRoute(
         path: '/',
@@ -23,17 +43,18 @@
         ],
       ),
     ];
-    final GoRouteInformationParser parser = GoRouteInformationParser(
-      configuration: RouteConfiguration(
-        routes: routes,
-        redirectLimit: 100,
-        topRedirect: (_) => null,
-        navigatorKey: GlobalKey<NavigatorState>(),
-      ),
+    final GoRouteInformationParser parser = await createParser(
+      tester,
+      routes: routes,
+      redirectLimit: 100,
+      redirect: (_, __) => null,
     );
 
-    RouteMatchList matchesObj = await parser
-        .parseRouteInformation(const RouteInformation(location: '/'));
+    final BuildContext context = tester.element(find.byType(Router<Object>));
+
+    RouteMatchList matchesObj =
+        await parser.parseRouteInformationWithDependencies(
+            const DebugGoRouteInformation(location: '/'), context);
     List<RouteMatch> matches = matchesObj.matches;
     expect(matches.length, 1);
     expect(matches[0].queryParams.isEmpty, isTrue);
@@ -43,8 +64,9 @@
     expect(matches[0].route, routes[0]);
 
     final Object extra = Object();
-    matchesObj = await parser.parseRouteInformation(
-        RouteInformation(location: '/abc?def=ghi', state: extra));
+    matchesObj = await parser.parseRouteInformationWithDependencies(
+        DebugGoRouteInformation(location: '/abc?def=ghi', state: extra),
+        context);
     matches = matchesObj.matches;
     expect(matches.length, 2);
     expect(matches[0].queryParams.length, 1);
@@ -90,7 +112,7 @@
     final RouteConfiguration configuration = RouteConfiguration(
       routes: routes,
       redirectLimit: 100,
-      topRedirect: (_) => null,
+      topRedirect: (_, __) => null,
       navigatorKey: GlobalKey<NavigatorState>(),
     );
 
@@ -133,7 +155,7 @@
     final RouteConfiguration configuration = RouteConfiguration(
       routes: routes,
       redirectLimit: 100,
-      topRedirect: (_) => null,
+      topRedirect: (_, __) => null,
       navigatorKey: GlobalKey<NavigatorState>(),
     );
 
@@ -147,7 +169,8 @@
     );
   });
 
-  test('GoRouteInformationParser returns error when unknown route', () async {
+  testWidgets('GoRouteInformationParser returns error when unknown route',
+      (WidgetTester tester) async {
     final List<GoRoute> routes = <GoRoute>[
       GoRoute(
         path: '/',
@@ -160,17 +183,18 @@
         ],
       ),
     ];
-    final GoRouteInformationParser parser = GoRouteInformationParser(
-      configuration: RouteConfiguration(
-        routes: routes,
-        redirectLimit: 100,
-        topRedirect: (_) => null,
-        navigatorKey: GlobalKey<NavigatorState>(),
-      ),
+    final GoRouteInformationParser parser = await createParser(
+      tester,
+      routes: routes,
+      redirectLimit: 100,
+      redirect: (_, __) => null,
     );
 
-    final RouteMatchList matchesObj = await parser
-        .parseRouteInformation(const RouteInformation(location: '/def'));
+    final BuildContext context = tester.element(find.byType(Router<Object>));
+
+    final RouteMatchList matchesObj =
+        await parser.parseRouteInformationWithDependencies(
+            const DebugGoRouteInformation(location: '/def'), context);
     final List<RouteMatch> matches = matchesObj.matches;
     expect(matches.length, 1);
     expect(matches[0].queryParams.isEmpty, isTrue);
@@ -181,7 +205,8 @@
         'Exception: no routes for location: /def');
   });
 
-  test('GoRouteInformationParser can work with route parameters', () async {
+  testWidgets('GoRouteInformationParser can work with route parameters',
+      (WidgetTester tester) async {
     final List<GoRoute> routes = <GoRoute>[
       GoRoute(
         path: '/',
@@ -194,17 +219,18 @@
         ],
       ),
     ];
-    final GoRouteInformationParser parser = GoRouteInformationParser(
-      configuration: RouteConfiguration(
-        routes: routes,
-        redirectLimit: 100,
-        topRedirect: (_) => null,
-        navigatorKey: GlobalKey<NavigatorState>(),
-      ),
+    final GoRouteInformationParser parser = await createParser(
+      tester,
+      routes: routes,
+      redirectLimit: 100,
+      redirect: (_, __) => null,
     );
 
-    final RouteMatchList matchesObj = await parser.parseRouteInformation(
-        const RouteInformation(location: '/123/family/456'));
+    final BuildContext context = tester.element(find.byType(Router<Object>));
+    final RouteMatchList matchesObj =
+        await parser.parseRouteInformationWithDependencies(
+            const DebugGoRouteInformation(location: '/123/family/456'),
+            context);
     final List<RouteMatch> matches = matchesObj.matches;
 
     expect(matches.length, 2);
@@ -222,9 +248,9 @@
     expect(matches[1].encodedParams['fid'], '456');
   });
 
-  test(
+  testWidgets(
       'GoRouteInformationParser processes top level redirect when there is no match',
-      () async {
+      (WidgetTester tester) async {
     final List<GoRoute> routes = <GoRoute>[
       GoRoute(
         path: '/',
@@ -237,22 +263,22 @@
         ],
       ),
     ];
-    final GoRouteInformationParser parser = GoRouteInformationParser(
-      configuration: RouteConfiguration(
-        routes: routes,
-        redirectLimit: 100,
-        topRedirect: (GoRouterState state) {
-          if (state.location != '/123/family/345') {
-            return '/123/family/345';
-          }
-          return null;
-        },
-        navigatorKey: GlobalKey<NavigatorState>(),
-      ),
+    final GoRouteInformationParser parser = await createParser(
+      tester,
+      routes: routes,
+      redirectLimit: 100,
+      redirect: (BuildContext context, GoRouterState state) {
+        if (state.location != '/123/family/345') {
+          return '/123/family/345';
+        }
+        return null;
+      },
     );
 
-    final RouteMatchList matchesObj = await parser
-        .parseRouteInformation(const RouteInformation(location: '/random/uri'));
+    final BuildContext context = tester.element(find.byType(Router<Object>));
+    final RouteMatchList matchesObj =
+        await parser.parseRouteInformationWithDependencies(
+            const DebugGoRouteInformation(location: '/random/uri'), context);
     final List<RouteMatch> matches = matchesObj.matches;
 
     expect(matches.length, 2);
@@ -263,9 +289,9 @@
     expect(matches[1].subloc, '/123/family/345');
   });
 
-  test(
+  testWidgets(
       'GoRouteInformationParser can do route level redirect when there is a match',
-      () async {
+      (WidgetTester tester) async {
     final List<GoRoute> routes = <GoRoute>[
       GoRoute(
         path: '/',
@@ -277,23 +303,23 @@
           ),
           GoRoute(
             path: 'redirect',
-            redirect: (_) => '/123/family/345',
+            redirect: (_, __) => '/123/family/345',
             builder: (_, __) => throw UnimplementedError(),
           ),
         ],
       ),
     ];
-    final GoRouteInformationParser parser = GoRouteInformationParser(
-      configuration: RouteConfiguration(
-        routes: routes,
-        redirectLimit: 100,
-        topRedirect: (_) => null,
-        navigatorKey: GlobalKey<NavigatorState>(),
-      ),
+    final GoRouteInformationParser parser = await createParser(
+      tester,
+      routes: routes,
+      redirectLimit: 100,
+      redirect: (_, __) => null,
     );
 
-    final RouteMatchList matchesObj = await parser
-        .parseRouteInformation(const RouteInformation(location: '/redirect'));
+    final BuildContext context = tester.element(find.byType(Router<Object>));
+    final RouteMatchList matchesObj =
+        await parser.parseRouteInformationWithDependencies(
+            const DebugGoRouteInformation(location: '/redirect'), context);
     final List<RouteMatch> matches = matchesObj.matches;
 
     expect(matches.length, 2);
@@ -304,56 +330,57 @@
     expect(matches[1].subloc, '/123/family/345');
   });
 
-  test('GoRouteInformationParser throws an exception when route is malformed',
-      () async {
+  testWidgets(
+      'GoRouteInformationParser throws an exception when route is malformed',
+      (WidgetTester tester) async {
     final List<GoRoute> routes = <GoRoute>[
       GoRoute(
         path: '/abc',
         builder: (_, __) => const Placeholder(),
       ),
     ];
-    final GoRouteInformationParser parser = GoRouteInformationParser(
-      configuration: RouteConfiguration(
-        routes: routes,
-        redirectLimit: 100,
-        topRedirect: (_) => null,
-        navigatorKey: GlobalKey<NavigatorState>(),
-      ),
+    final GoRouteInformationParser parser = await createParser(
+      tester,
+      routes: routes,
+      redirectLimit: 100,
+      redirect: (_, __) => null,
     );
 
+    final BuildContext context = tester.element(find.byType(Router<Object>));
     expect(() async {
-      await parser.parseRouteInformation(
-          const RouteInformation(location: '::Not valid URI::'));
+      await parser.parseRouteInformationWithDependencies(
+          const DebugGoRouteInformation(location: '::Not valid URI::'),
+          context);
     }, throwsA(isA<FormatException>()));
   });
 
-  test('GoRouteInformationParser returns an error if a redirect is detected.',
-      () async {
+  testWidgets(
+      'GoRouteInformationParser returns an error if a redirect is detected.',
+      (WidgetTester tester) async {
     final List<GoRoute> routes = <GoRoute>[
       GoRoute(
         path: '/abc',
         builder: (_, __) => const Placeholder(),
-        redirect: (GoRouterState state) => state.location,
+        redirect: (BuildContext context, GoRouterState state) => state.location,
       ),
     ];
-    final GoRouteInformationParser parser = GoRouteInformationParser(
-      configuration: RouteConfiguration(
-        routes: routes,
-        redirectLimit: 5,
-        topRedirect: (_) => null,
-        navigatorKey: GlobalKey<NavigatorState>(),
-      ),
+    final GoRouteInformationParser parser = await createParser(
+      tester,
+      routes: routes,
+      redirect: (_, __) => null,
     );
 
-    final RouteMatchList matchesObj = await parser
-        .parseRouteInformation(const RouteInformation(location: '/abd'));
+    final BuildContext context = tester.element(find.byType(Router<Object>));
+    final RouteMatchList matchesObj =
+        await parser.parseRouteInformationWithDependencies(
+            const DebugGoRouteInformation(location: '/abd'), context);
     final List<RouteMatch> matches = matchesObj.matches;
 
     expect(matches, hasLength(1));
     expect(matches.first.error, isNotNull);
   });
 
-  test('Creates a match for ShellRoute', () async {
+  testWidgets('Creates a match for ShellRoute', (WidgetTester tester) async {
     final List<RouteBase> routes = <RouteBase>[
       ShellRoute(
         builder: (BuildContext context, GoRouterState state, Widget child) {
@@ -381,17 +408,16 @@
         ],
       ),
     ];
-    final GoRouteInformationParser parser = GoRouteInformationParser(
-      configuration: RouteConfiguration(
-        routes: routes,
-        redirectLimit: 5,
-        topRedirect: (_) => null,
-        navigatorKey: GlobalKey<NavigatorState>(),
-      ),
+    final GoRouteInformationParser parser = await createParser(
+      tester,
+      routes: routes,
+      redirect: (_, __) => null,
     );
 
-    final RouteMatchList matchesObj = await parser
-        .parseRouteInformation(const RouteInformation(location: '/a'));
+    final BuildContext context = tester.element(find.byType(Router<Object>));
+    final RouteMatchList matchesObj =
+        await parser.parseRouteInformationWithDependencies(
+            const DebugGoRouteInformation(location: '/a'), context);
     final List<RouteMatch> matches = matchesObj.matches;
 
     expect(matches, hasLength(2));
diff --git a/packages/go_router/test/test_helpers.dart b/packages/go_router/test/test_helpers.dart
index 71fb55ae..af9bf50 100644
--- a/packages/go_router/test/test_helpers.dart
+++ b/packages/go_router/test/test_helpers.dart
@@ -11,12 +11,8 @@
 import 'package:go_router/go_router.dart';
 import 'package:go_router/src/match.dart';
 import 'package:go_router/src/matching.dart';
-import 'package:go_router/src/typedefs.dart';
 
-Future<GoRouter> createGoRouter(
-  WidgetTester tester, {
-  GoRouterNavigatorBuilder? navigatorBuilder,
-}) async {
+Future<GoRouter> createGoRouter(WidgetTester tester) async {
   final GoRouter goRouter = GoRouter(
     initialLocation: '/',
     routes: <GoRoute>[
@@ -26,12 +22,10 @@
         builder: (_, __) => TestErrorScreen(TestFailure('Exception')),
       ),
     ],
-    navigatorBuilder: navigatorBuilder,
   );
   await tester.pumpWidget(MaterialApp.router(
-      routeInformationProvider: goRouter.routeInformationProvider,
-      routeInformationParser: goRouter.routeInformationParser,
-      routerDelegate: goRouter.routerDelegate));
+    routerConfig: goRouter,
+  ));
   return goRouter;
 }
 
@@ -143,20 +137,6 @@
   }
 }
 
-class GoRouterRefreshStreamSpy extends GoRouterRefreshStream {
-  GoRouterRefreshStreamSpy(
-    super.stream,
-  ) : notifyCount = 0;
-
-  late int notifyCount;
-
-  @override
-  void notifyListeners() {
-    notifyCount++;
-    super.notifyListeners();
-  }
-}
-
 Future<GoRouter> createRouter(
   List<RouteBase> routes,
   WidgetTester tester, {
@@ -176,9 +156,7 @@
   );
   await tester.pumpWidget(
     MaterialApp.router(
-      routeInformationProvider: goRouter.routeInformationProvider,
-      routeInformationParser: goRouter.routeInformationParser,
-      routerDelegate: goRouter.routerDelegate,
+      routerConfig: goRouter,
     ),
   );
   return goRouter;