[go_router] rewrite readme and examples (#2382)
diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index 465df98..86002df 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 4.2.4
+
+- Rewrites Readme and examples.
+
## 4.2.3
- Fixes a bug where the ValueKey to be the same when a page was pushed multiple times.
diff --git a/packages/go_router/README.md b/packages/go_router/README.md
index 848425f..de139c8 100644
--- a/packages/go_router/README.md
+++ b/packages/go_router/README.md
@@ -1,13 +1,14 @@
-# Welcome to go_router!
+# go_router, A Declarative Routing Package for Flutter
-The purpose of [the go_router package](https://pub.dev/packages/go_router) is to
-use declarative routes to reduce complexity, regardless of the platform you're
-targeting (mobile, web, desktop), handle deep and dynamic linking from
-Android, iOS and the web, along with a number of other navigation-related
-scenarios, while still (hopefully) providing an easy-to-use developer
-experience.
+This package builds on top of the Flutter framework's Router API and provides
+convenient url-based APIs to navigate between different screens. You can
+define your own url patterns, navigating to different url, handle deep and
+dynamic linking, and a number of other navigation-related scenarios.
-You can get started with go_router with code as simple as this:
+## Getting Started
+
+Follow the [package install instructions](https://pub.dev/packages/go_router/install),
+and you can start using go_router in your code:
```dart
class App extends StatelessWidget {
@@ -34,11 +35,102 @@
],
);
}
-
-class Page1Screen extends StatelessWidget {...}
-
-class Page2Screen extends StatelessWidget {...}
```
-# See [gorouter.dev](https://gorouter.dev) for go_router docs & samples
+## Define Routes
+
+go_router is governed by a set of routes which are specify as part of the
+[GoRouter](https://pub.dev/documentation/go_router/latest/go_router/GoRouter-class.html)
+constructor:
+
+```dart
+GoRouter(
+ routes: [
+ GoRoute(
+ path: '/',
+ builder: (context, state) => const Page1Screen(),
+ ),
+ GoRoute(
+ path: '/page2',
+ builder: (context, state) => const Page2Screen(),
+ ),
+ ],
+);
+```
+
+It defined two routes, `/` and `/page2`. Each route path will be matched against
+the location to which the user is navigating. The path will be matched in a
+case-insensitive way, although the case for parameters will be preserved. If
+there are multiple route matches, the <b>first</b> match in the list takes priority
+over the others.
+
+In addition to the path, each route will typically have a [builder](https://pub.dev/documentation/go_router/latest/go_router/GoRoute/builder.html)
+function that is responsible for building the `Widget` that is to take up the
+entire screen of the app. The default transition is used between pages
+depending on the app at the top of its widget tree, e.g. the use of `MaterialApp`
+will cause go_router to use the `MaterialPage` transitions. Consider using
+[pageBuilder](https://pub.dev/documentation/go_router/latest/go_router/GoRoute/pageBuilder.html)
+for custom `Page` class.
+
+## Initalization
+
+Once a [GoRouter](https://pub.dev/documentation/go_router/latest/go_router/GoRouter-class.html)
+object is created, it can be used to initialize `MaterialApp` or `CupertinoApp`.
+
+```dart
+final GoRouter _router = GoRouter(..);
+
+MaterialApp.router(
+ routeInformationProvider: _router.routeInformationProvider,
+ routeInformationParser: _router.routeInformationParser,
+ routerDelegate: _router.routerDelegate,
+);
+```
+
+## Error handling
+
+By default, go_router comes with default error screens for both `MaterialApp` and
+`CupertinoApp` as well as a default error screen in the case that none is used.
+Once can also replace the default error screen by using the [errorBuilder](https://pub.dev/documentation/go_router/latest/go_router/GoRouter/GoRouter.html):
+
+```dart
+GoRouter(
+ ...
+ errorBuilder: (context, state) => ErrorScreen(state.error),
+);
+```
+
+## Navigation
+
+To navigate between routes, use the [GoRouter.go](https://pub.dev/documentation/go_router/latest/go_router/GoRouter/go.html) method:
+
+```dart
+onTap: () => GoRouter.of(context).go('/page2')
+```
+
+go_router also provides a simplified means of navigation using Dart extension
+methods:
+
+```dart
+onTap: () => context.go('/page2')
+```
+
+<br>
+
+### Still not sure how to proceed? See [examples](https://github.com/flutter/packages/tree/main/packages/go_router/example) for complete runnable examples or visit [API documentation](https://pub.dev/documentation/go_router/latest/go_router/go_router-library.html)
+
+
+## Migration guides
+
+[Migrating to 2.0](https://flutter.dev/go/go-router-v2-breaking-changes)<br/>
+[Migrating to 2.5](https://flutter.dev/go/go-router-v2-5-breaking-changes)<br/>
+[Migrating to 3.0](https://flutter.dev/go/go-router-v3-breaking-changes)<br/>
+[Migrating to 4.0](https://flutter.dev/go/go-router-v4-breaking-changes)<br/>
+
+## Changelog
+
+See the [Changelog](https://github.com/flutter/packages/blob/main/packages/go_router/CHANGELOG.md)
+for a list of new features and breaking changes.
+
+
diff --git a/packages/go_router/example/README.md b/packages/go_router/example/README.md
new file mode 100644
index 0000000..21f6e80
--- /dev/null
+++ b/packages/go_router/example/README.md
@@ -0,0 +1,31 @@
+# Example Catalog
+
+## [Routes](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/main.dart)
+`flutter run lib/main.dart`
+
+An example to demonstrate a simple two-pages app.
+
+## [Sub-routes](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/sub_routes.dart)
+`flutter run lib/sub_routes.dart`
+
+An example to demonstrate an app with multi-level routing.
+
+## [Query parameters and path parameters](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/path_and_query_parameters.dart)
+`flutter run lib/path_and_query_parameters.dart`
+
+An example to demonstrate how to use path parameters and query parameters.
+
+## [Named routes](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/named_routes.dart)
+`flutter run lib/named_routes.dart`
+
+An example to demonstrate how to navigate using named locations instead of URLs.
+
+## [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.
+
+## [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/main.dart b/packages/go_router/example/lib/main.dart
index 91b827b..fab5a36 100644
--- a/packages/go_router/example/lib/main.dart
+++ b/packages/go_router/example/lib/main.dart
@@ -5,6 +5,15 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
+// This scenario demonstrates a simple two-page app.
+//
+// The first route '/' is mapped to Page1Screen, and the second route '/page2'
+// is mapped to Page2Screen. To navigate between pages, press the buttons on the
+// pages.
+//
+// The onPress callbacks use context.go() to navigate to another page. This is
+// equivalent to entering url to the browser url bar directly.
+
void main() => runApp(App());
/// The main app.
@@ -29,11 +38,13 @@
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const Page1Screen(),
- ),
- GoRoute(
- path: '/page2',
- builder: (BuildContext context, GoRouterState state) =>
- const Page2Screen(),
+ routes: <GoRoute>[
+ GoRoute(
+ path: 'page2',
+ builder: (BuildContext context, GoRouterState state) =>
+ const Page2Screen(),
+ ),
+ ],
),
],
);
@@ -75,7 +86,7 @@
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/'),
- child: const Text('Go to home page'),
+ child: const Text('Go back to home page'),
),
],
),
diff --git a/packages/go_router/example/lib/named_routes.dart b/packages/go_router/example/lib/named_routes.dart
index 859a4bc..ef58c52 100644
--- a/packages/go_router/example/lib/named_routes.dart
+++ b/packages/go_router/example/lib/named_routes.dart
@@ -4,10 +4,17 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
-import 'package:provider/provider.dart';
import 'shared/data.dart';
+// This scenario demonstrates how to navigate using named locations instead of
+// URLs.
+//
+// Instead of hardcoding the URI locations , you can also use the named
+// locations. To use this API, give a unique name to each GoRoute. The name can
+// then be used in context.namedLocation to be translate back to the actual URL
+// location.
+
void main() => runApp(App());
/// The main app.
@@ -15,21 +22,16 @@
/// Creates an [App].
App({Key? key}) : super(key: key);
- final LoginInfo _loginInfo = LoginInfo();
-
/// The title of the app.
static const String title = 'GoRouter Example: Named Routes';
@override
- Widget build(BuildContext context) => ChangeNotifierProvider<LoginInfo>.value(
- value: _loginInfo,
- child: MaterialApp.router(
- routeInformationProvider: _router.routeInformationProvider,
- routeInformationParser: _router.routeInformationParser,
- routerDelegate: _router.routerDelegate,
- title: title,
- debugShowCheckedModeBanner: false,
- ),
+ Widget build(BuildContext context) => MaterialApp.router(
+ routeInformationProvider: _router.routeInformationProvider,
+ routeInformationParser: _router.routeInformationParser,
+ routerDelegate: _router.routerDelegate,
+ title: title,
+ debugShowCheckedModeBanner: false,
);
late final GoRouter _router = GoRouter(
@@ -62,47 +64,7 @@
),
],
),
- GoRoute(
- name: 'login',
- path: '/login',
- builder: (BuildContext context, GoRouterState state) =>
- const LoginScreen(),
- ),
],
-
- // redirect to the login page if the user is not logged in
- redirect: (GoRouterState state) {
- // if the user is not logged in, they need to login
- final bool loggedIn = _loginInfo.loggedIn;
- final String loginloc = state.namedLocation('login');
- final bool loggingIn = state.subloc == loginloc;
-
- // bundle the location the user is coming from into a query parameter
- final String homeloc = state.namedLocation('home');
- final String fromloc = state.subloc == homeloc ? '' : state.subloc;
- if (!loggedIn) {
- return loggingIn
- ? null
- : state.namedLocation(
- 'login',
- queryParams: <String, String>{
- if (fromloc.isNotEmpty) 'from': fromloc
- },
- );
- }
-
- // if the user is logged in, send them where they were going before (or
- // home if they weren't going anywhere)
- if (loggingIn) {
- return state.queryParams['from'] ?? homeloc;
- }
-
- // no need to redirect at all
- return null;
- },
-
- // changes on the listenable will cause the router to refresh it's route
- refreshListenable: _loginInfo,
);
}
@@ -116,26 +78,17 @@
@override
Widget build(BuildContext context) {
- final LoginInfo info = context.read<LoginInfo>();
-
return Scaffold(
appBar: AppBar(
title: const Text(App.title),
- actions: <Widget>[
- IconButton(
- onPressed: info.logout,
- tooltip: 'Logout: ${info.userName}',
- icon: const Icon(Icons.logout),
- )
- ],
),
body: ListView(
children: <Widget>[
for (final Family f in families)
ListTile(
title: Text(f.name),
- onTap: () => context
- .goNamed('family', params: <String, String>{'fid': f.id}),
+ onTap: () => context.go(context.namedLocation('family',
+ params: <String, String>{'fid': f.id})),
)
],
),
@@ -188,28 +141,3 @@
body: Text('${person.name} ${family.name} is ${person.age} years old'),
);
}
-
-/// 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/async_data.dart b/packages/go_router/example/lib/others/async_data.dart
similarity index 99%
rename from packages/go_router/example/lib/async_data.dart
rename to packages/go_router/example/lib/others/async_data.dart
index 5dfa003..fbe87e2 100644
--- a/packages/go_router/example/lib/async_data.dart
+++ b/packages/go_router/example/lib/others/async_data.dart
@@ -9,7 +9,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
-import 'shared/data.dart';
+import '../shared/data.dart';
void main() => runApp(App());
diff --git a/packages/go_router/example/lib/cupertino.dart b/packages/go_router/example/lib/others/cupertino.dart
similarity index 100%
rename from packages/go_router/example/lib/cupertino.dart
rename to packages/go_router/example/lib/others/cupertino.dart
diff --git a/packages/go_router/example/lib/error_screen.dart b/packages/go_router/example/lib/others/error_screen.dart
similarity index 100%
rename from packages/go_router/example/lib/error_screen.dart
rename to packages/go_router/example/lib/others/error_screen.dart
diff --git a/packages/go_router/example/lib/extra_param.dart b/packages/go_router/example/lib/others/extra_param.dart
similarity index 99%
rename from packages/go_router/example/lib/extra_param.dart
rename to packages/go_router/example/lib/others/extra_param.dart
index efab7be..c1624a0 100644
--- a/packages/go_router/example/lib/extra_param.dart
+++ b/packages/go_router/example/lib/others/extra_param.dart
@@ -6,7 +6,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
-import 'shared/data.dart';
+import '../shared/data.dart';
void main() => runApp(App());
diff --git a/packages/go_router/example/lib/init_loc.dart b/packages/go_router/example/lib/others/init_loc.dart
similarity index 100%
rename from packages/go_router/example/lib/init_loc.dart
rename to packages/go_router/example/lib/others/init_loc.dart
diff --git a/packages/go_router/example/lib/loading_page.dart b/packages/go_router/example/lib/others/loading_page.dart
similarity index 99%
rename from packages/go_router/example/lib/loading_page.dart
rename to packages/go_router/example/lib/others/loading_page.dart
index 9f61726..2c64389 100644
--- a/packages/go_router/example/lib/loading_page.dart
+++ b/packages/go_router/example/lib/others/loading_page.dart
@@ -6,7 +6,7 @@
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
-import 'shared/data.dart';
+import '../shared/data.dart';
void main() => runApp(App());
diff --git a/packages/go_router/example/lib/nav_builder.dart b/packages/go_router/example/lib/others/nav_builder.dart
similarity index 99%
rename from packages/go_router/example/lib/nav_builder.dart
rename to packages/go_router/example/lib/others/nav_builder.dart
index 8ec6b07..5e80007 100644
--- a/packages/go_router/example/lib/nav_builder.dart
+++ b/packages/go_router/example/lib/others/nav_builder.dart
@@ -6,7 +6,7 @@
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
-import 'shared/data.dart';
+import '../shared/data.dart';
void main() => runApp(App());
diff --git a/packages/go_router/example/lib/nav_observer.dart b/packages/go_router/example/lib/others/nav_observer.dart
similarity index 100%
rename from packages/go_router/example/lib/nav_observer.dart
rename to packages/go_router/example/lib/others/nav_observer.dart
diff --git a/packages/go_router/example/lib/nested_nav.dart b/packages/go_router/example/lib/others/nested_nav.dart
similarity index 99%
rename from packages/go_router/example/lib/nested_nav.dart
rename to packages/go_router/example/lib/others/nested_nav.dart
index 04b8285..049e418 100644
--- a/packages/go_router/example/lib/nested_nav.dart
+++ b/packages/go_router/example/lib/others/nested_nav.dart
@@ -5,7 +5,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
-import 'shared/data.dart';
+import '../shared/data.dart';
void main() => runApp(App());
diff --git a/packages/go_router/example/lib/push.dart b/packages/go_router/example/lib/others/push.dart
similarity index 100%
rename from packages/go_router/example/lib/push.dart
rename to packages/go_router/example/lib/others/push.dart
diff --git a/packages/go_router/example/lib/router_neglect.dart b/packages/go_router/example/lib/others/router_neglect.dart
similarity index 100%
rename from packages/go_router/example/lib/router_neglect.dart
rename to packages/go_router/example/lib/others/router_neglect.dart
diff --git a/packages/go_router/example/lib/router_stream_refresh.dart b/packages/go_router/example/lib/others/router_stream_refresh.dart
similarity index 99%
rename from packages/go_router/example/lib/router_stream_refresh.dart
rename to packages/go_router/example/lib/others/router_stream_refresh.dart
index 7a3ecf4..a042084 100644
--- a/packages/go_router/example/lib/router_stream_refresh.dart
+++ b/packages/go_router/example/lib/others/router_stream_refresh.dart
@@ -6,7 +6,7 @@
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
-import 'shared/data.dart';
+import '../shared/data.dart';
void main() => runApp(const App());
diff --git a/packages/go_router/example/lib/shared_scaffold.dart b/packages/go_router/example/lib/others/shared_scaffold.dart
similarity index 100%
rename from packages/go_router/example/lib/shared_scaffold.dart
rename to packages/go_router/example/lib/others/shared_scaffold.dart
diff --git a/packages/go_router/example/lib/state_restoration.dart b/packages/go_router/example/lib/others/state_restoration.dart
similarity index 100%
rename from packages/go_router/example/lib/state_restoration.dart
rename to packages/go_router/example/lib/others/state_restoration.dart
diff --git a/packages/go_router/example/lib/transitions.dart b/packages/go_router/example/lib/others/transitions.dart
similarity index 100%
rename from packages/go_router/example/lib/transitions.dart
rename to packages/go_router/example/lib/others/transitions.dart
diff --git a/packages/go_router/example/lib/url_strategy.dart b/packages/go_router/example/lib/others/url_strategy.dart
similarity index 100%
rename from packages/go_router/example/lib/url_strategy.dart
rename to packages/go_router/example/lib/others/url_strategy.dart
diff --git a/packages/go_router/example/lib/user_input.dart b/packages/go_router/example/lib/others/user_input.dart
similarity index 99%
rename from packages/go_router/example/lib/user_input.dart
rename to packages/go_router/example/lib/others/user_input.dart
index 56cf9a9..93c972b 100644
--- a/packages/go_router/example/lib/user_input.dart
+++ b/packages/go_router/example/lib/others/user_input.dart
@@ -6,7 +6,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
-import 'shared/data.dart';
+import '../shared/data.dart';
void main() => runApp(App());
diff --git a/packages/go_router/example/lib/widgets_app.dart b/packages/go_router/example/lib/others/widgets_app.dart
similarity index 100%
rename from packages/go_router/example/lib/widgets_app.dart
rename to packages/go_router/example/lib/others/widgets_app.dart
diff --git a/packages/go_router/example/lib/path_and_query_parameters.dart b/packages/go_router/example/lib/path_and_query_parameters.dart
new file mode 100755
index 0000000..6a91c15
--- /dev/null
+++ b/packages/go_router/example/lib/path_and_query_parameters.dart
@@ -0,0 +1,132 @@
+// 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 'shared/data.dart';
+
+// This scenario demonstrates how to use path parameters and query parameters.
+//
+// The route segments that start with ':' are treated as path parameters when
+// defining GoRoute[s]. The parameter values can be accessed through
+// GoRouterState.params.
+//
+// The query parameters are automatically stored in GoRouterState.queryParams.
+
+void main() => runApp(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: Query Parameters';
+
+ // 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) =>
+ HomeScreen(families: Families.data),
+ routes: <GoRoute>[
+ GoRoute(
+ path: 'family/:id',
+ builder: (BuildContext context, GoRouterState state) {
+ return FamilyScreen(
+ family: Families.family(state.params['id']!),
+ asc: state.queryParams['sort'] == 'asc',
+ );
+ }),
+ ],
+ ),
+ ],
+ );
+}
+
+/// The home screen that shows a list of families.
+class HomeScreen extends StatelessWidget {
+ /// Creates a [HomeScreen].
+ const HomeScreen({required this.families, Key? key}) : super(key: key);
+
+ /// The list of families.
+ final List<Family> families;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text(App.title),
+ ),
+ body: ListView(
+ children: <Widget>[
+ for (final Family f in families)
+ ListTile(
+ title: Text(f.name),
+ onTap: () => context.go('/family/${f.id}'),
+ )
+ ],
+ ),
+ );
+ }
+}
+
+/// The screen that shows a list of persons in a family.
+class FamilyScreen extends StatelessWidget {
+ /// Creates a [FamilyScreen].
+ const FamilyScreen({required this.family, required this.asc, Key? key})
+ : super(key: key);
+
+ /// The family to display.
+ final Family family;
+
+ /// Whether to sort the name in ascending order.
+ final bool asc;
+
+ @override
+ Widget build(BuildContext context) {
+ final String curentPath = Uri.parse(GoRouter.of(context).location).path;
+ final Map<String, String> newQueries;
+ final List<String> names =
+ family.people.map<String>((Person p) => p.name).toList();
+ names.sort();
+ if (asc) {
+ newQueries = const <String, String>{'sort': 'desc'};
+ } else {
+ newQueries = const <String, String>{'sort': 'asc'};
+ }
+ final Uri iconLink = Uri(path: curentPath, queryParameters: newQueries);
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(family.name),
+ actions: <Widget>[
+ IconButton(
+ onPressed: () => context.go(iconLink.toString()),
+ tooltip: 'sort ascending or descending',
+ icon: const Icon(Icons.sort),
+ )
+ ],
+ ),
+ body: ListView(
+ children: <Widget>[
+ for (final String name in asc ? names : names.reversed)
+ ListTile(
+ title: Text(name),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/packages/go_router/example/lib/query_params.dart b/packages/go_router/example/lib/query_params.dart
deleted file mode 100644
index 656273e..0000000
--- a/packages/go_router/example/lib/query_params.dart
+++ /dev/null
@@ -1,200 +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';
-
-import 'shared/data.dart';
-
-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: Query Parameters';
-
- // add the login info into the tree as app state that can change over time
- @override
- Widget build(BuildContext context) => ChangeNotifierProvider<LoginInfo>.value(
- value: _loginInfo,
- child: 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) =>
- HomeScreen(families: Families.data),
- routes: <GoRoute>[
- GoRoute(
- path: 'family/:fid',
- builder: (BuildContext context, GoRouterState state) =>
- FamilyScreen(
- family: Families.family(state.params['fid']!),
- ),
- routes: <GoRoute>[
- GoRoute(
- path: 'person/:pid',
- builder: (BuildContext context, GoRouterState state) {
- final Family family = Families.family(state.params['fid']!);
- final Person person = family.person(state.params['pid']!);
- return PersonScreen(family: family, person: person);
- },
- ),
- ],
- ),
- ],
- ),
- GoRoute(
- path: '/login',
- builder: (BuildContext context, GoRouterState state) =>
- const LoginScreen(),
- ),
- ],
-
- // redirect to the login page if the user is not logged in
- redirect: (GoRouterState state) {
- // if the user is not logged in, they need to login
- final bool loggedIn = _loginInfo.loggedIn;
- final bool loggingIn = state.subloc == '/login';
-
- // bundle the location the user is coming from into a query parameter
- final String fromp = state.subloc == '/' ? '' : '?from=${state.subloc}';
- if (!loggedIn) {
- return loggingIn ? null : '/login$fromp';
- }
-
- // if the user is logged in, send them where they were going before (or
- // home if they weren't going anywhere)
- if (loggingIn) {
- return state.queryParams['from'] ?? '/';
- }
-
- // no need to redirect at all
- return null;
- },
-
- // changes on the listenable will cause the router to refresh it's route
- refreshListenable: _loginInfo,
- );
-}
-
-/// The home screen that shows a list of families.
-class HomeScreen extends StatelessWidget {
- /// Creates a [HomeScreen].
- const HomeScreen({required this.families, Key? key}) : super(key: key);
-
- /// The list of families.
- final List<Family> families;
-
- @override
- Widget build(BuildContext context) {
- final LoginInfo info = context.read<LoginInfo>();
-
- return Scaffold(
- appBar: AppBar(
- title: const Text(App.title),
- actions: <Widget>[
- IconButton(
- onPressed: () {
- info.logout();
- context.go('/');
- },
- tooltip: 'Logout: ${info.userName}',
- icon: const Icon(Icons.logout),
- )
- ],
- ),
- body: ListView(
- children: <Widget>[
- for (final Family f in families)
- ListTile(
- title: Text(f.name),
- onTap: () => context.go('/family/${f.id}'),
- )
- ],
- ),
- );
- }
-}
-
-/// The screen that shows a list of persons in a family.
-class FamilyScreen extends StatelessWidget {
- /// Creates a [FamilyScreen].
- const FamilyScreen({required this.family, Key? key}) : super(key: key);
-
- /// The family to display.
- final Family family;
-
- @override
- Widget build(BuildContext context) => Scaffold(
- appBar: AppBar(title: Text(family.name)),
- body: ListView(
- children: <Widget>[
- for (final Person p in family.people)
- ListTile(
- title: Text(p.name),
- onTap: () => context.go('/family/${family.id}/person/${p.id}'),
- ),
- ],
- ),
- );
-}
-
-/// The person screen.
-class PersonScreen extends StatelessWidget {
- /// Creates a [PersonScreen].
- const PersonScreen({required this.family, required this.person, Key? key})
- : super(key: key);
-
- /// The family this person belong to.
- final Family family;
-
- /// The person to be displayed.
- final Person person;
-
- @override
- Widget build(BuildContext context) => Scaffold(
- appBar: AppBar(title: Text(person.name)),
- body: Text('${person.name} ${family.name} is ${person.age} years old'),
- );
-}
-
-/// 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/redirection.dart b/packages/go_router/example/lib/redirection.dart
index 0cbe6ed..5697fd6 100644
--- a/packages/go_router/example/lib/redirection.dart
+++ b/packages/go_router/example/lib/redirection.dart
@@ -8,6 +8,12 @@
import 'shared/data.dart';
+// This scenario demonstrates how to use redirect to handle a sign-in flow.
+//
+// The GoRouter.redirect method is called before the app is navigate to a
+// new page. You can choose to redirect to a different page by returning a
+// non-null URL string.
+
void main() => runApp(App());
/// The main app.
@@ -38,26 +44,7 @@
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
- HomeScreen(families: Families.data),
- routes: <GoRoute>[
- GoRoute(
- path: 'family/:fid',
- builder: (BuildContext context, GoRouterState state) =>
- FamilyScreen(
- family: Families.family(state.params['fid']!),
- ),
- routes: <GoRoute>[
- GoRoute(
- path: 'person/:pid',
- builder: (BuildContext context, GoRouterState state) {
- final Family family = Families.family(state.params['fid']!);
- final Person person = family.person(state.params['pid']!);
- return PersonScreen(family: family, person: person);
- },
- ),
- ],
- ),
- ],
+ const HomeScreen(),
),
GoRoute(
path: '/login',
@@ -109,7 +96,6 @@
// router will automatically redirect from /login to / using
// refreshListenable
- //context.go('/');
},
child: const Text('Login'),
),
@@ -119,13 +105,10 @@
);
}
-/// The home screen that shows a list of families.
+/// The home screen.
class HomeScreen extends StatelessWidget {
/// Creates a [HomeScreen].
- const HomeScreen({required this.families, Key? key}) : super(key: key);
-
- /// The list of families.
- final List<Family> families;
+ const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -142,57 +125,9 @@
)
],
),
- body: ListView(
- children: <Widget>[
- for (final Family f in families)
- ListTile(
- title: Text(f.name),
- onTap: () => context.go('/family/${f.id}'),
- )
- ],
+ body: const Center(
+ child: Text('HomeScreen'),
),
);
}
}
-
-/// The screen that shows a list of persons in a family.
-class FamilyScreen extends StatelessWidget {
- /// Creates a [FamilyScreen].
- const FamilyScreen({required this.family, Key? key}) : super(key: key);
-
- /// The family to display.
- final Family family;
-
- @override
- Widget build(BuildContext context) => Scaffold(
- appBar: AppBar(title: Text(family.name)),
- body: ListView(
- children: <Widget>[
- for (final Person p in family.people)
- ListTile(
- title: Text(p.name),
- onTap: () => context.go('/family/${family.id}/person/${p.id}'),
- ),
- ],
- ),
- );
-}
-
-/// The person screen.
-class PersonScreen extends StatelessWidget {
- /// Creates a [PersonScreen].
- const PersonScreen({required this.family, required this.person, Key? key})
- : super(key: key);
-
- /// The family this person belong to.
- final Family family;
-
- /// The person to be displayed.
- final Person person;
-
- @override
- Widget build(BuildContext context) => Scaffold(
- appBar: AppBar(title: Text(person.name)),
- body: Text('${person.name} ${family.name} is ${person.age} years old'),
- );
-}
diff --git a/packages/go_router/example/lib/sub_routes.dart b/packages/go_router/example/lib/sub_routes.dart
index 68288dd..6e1632a 100644
--- a/packages/go_router/example/lib/sub_routes.dart
+++ b/packages/go_router/example/lib/sub_routes.dart
@@ -5,7 +5,17 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
-import 'shared/data.dart';
+// This scenario demonstrates an app with multi-level routing.
+//
+// The GoRoute '/' builds a HomeScreen page and has a sub-route, 'family'. The
+// 'family' also has its own sub-route, person'.
+//
+// If a url matches a sub-route, the page built by the sub-route is placed on
+// top of the parent's page. In this example, a url '/family/person' will create
+// a stack of three pages, [PersonScreen, FamilyScreen, HomeScreen] where the
+// PersonScreen being the top-most page and shown on the screen. Since there are
+// two more pages under it, the back button is shown on the app bar and can pop
+// the page to show the pages underneath.
void main() => runApp(App());
@@ -30,23 +40,17 @@
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
- HomeScreen(families: Families.data),
+ const HomeScreen(),
routes: <GoRoute>[
GoRoute(
- path: 'family/:fid',
+ path: 'family',
builder: (BuildContext context, GoRouterState state) =>
- FamilyScreen(
- family: Families.family(state.params['fid']!),
- ),
+ const FamilyScreen(),
routes: <GoRoute>[
GoRoute(
- path: 'person/:pid',
- builder: (BuildContext context, GoRouterState state) {
- final Family family = Families.family(state.params['fid']!);
- final Person person = family.person(state.params['pid']!);
-
- return PersonScreen(family: family, person: person);
- },
+ path: 'person',
+ builder: (BuildContext context, GoRouterState state) =>
+ const PersonScreen(),
),
],
),
@@ -59,22 +63,16 @@
/// The home screen that shows a list of families.
class HomeScreen extends StatelessWidget {
/// Creates a [HomeScreen].
- const HomeScreen({required this.families, Key? key}) : super(key: key);
-
- /// The list of families.
- final List<Family> families;
+ const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
- body: ListView(
- children: <Widget>[
- for (final Family f in families)
- ListTile(
- title: Text(f.name),
- onTap: () => context.go('/family/${f.id}'),
- )
- ],
+ body: Center(
+ child: ElevatedButton(
+ onPressed: () => context.go('/family'),
+ child: const Text('Go to family screen'),
+ ),
),
);
}
@@ -82,22 +80,16 @@
/// The screen that shows a list of persons in a family.
class FamilyScreen extends StatelessWidget {
/// Creates a [FamilyScreen].
- const FamilyScreen({required this.family, Key? key}) : super(key: key);
-
- /// The family to display.
- final Family family;
+ const FamilyScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
- appBar: AppBar(title: Text(family.name)),
- body: ListView(
- children: <Widget>[
- for (final Person p in family.people)
- ListTile(
- title: Text(p.name),
- onTap: () => context.go('/family/${family.id}/person/${p.id}'),
- ),
- ],
+ appBar: AppBar(title: const Text('Family screen')),
+ body: Center(
+ child: ElevatedButton(
+ onPressed: () => context.go('/family/person'),
+ child: const Text('Go to person screen'),
+ ),
),
);
}
@@ -105,18 +97,12 @@
/// The person screen.
class PersonScreen extends StatelessWidget {
/// Creates a [PersonScreen].
- const PersonScreen({required this.family, required this.person, Key? key})
- : super(key: key);
-
- /// The family this person belong to.
- final Family family;
-
- /// The person to be displayed.
- final Person person;
+ const PersonScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
- appBar: AppBar(title: Text(person.name)),
- body: Text('${person.name} ${family.name} is ${person.age} years old'),
- );
+ appBar: AppBar(title: const Text('Person screen')),
+ body: const Center(
+ child: Text('This is the person screen'),
+ ));
}
diff --git a/packages/go_router/example/test/main_test.dart b/packages/go_router/example/test/main_test.dart
new file mode 100644
index 0000000..f8e3ed0
--- /dev/null
+++ b/packages/go_router/example/test/main_test.dart
@@ -0,0 +1,21 @@
+// 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_test/flutter_test.dart';
+import 'package:go_router_examples/main.dart' as example;
+
+void main() {
+ testWidgets('example works', (WidgetTester tester) async {
+ await tester.pumpWidget(example.App());
+ expect(find.text('Go to page 2'), findsOneWidget);
+
+ await tester.tap(find.text('Go to page 2'));
+ await tester.pumpAndSettle();
+ expect(find.text('Go back to home page'), findsOneWidget);
+
+ await tester.tap(find.text('Go back to home page'));
+ await tester.pumpAndSettle();
+ expect(find.text('Go to page 2'), findsOneWidget);
+ });
+}
diff --git a/packages/go_router/example/test/path_and_query_params_test.dart b/packages/go_router/example/test/path_and_query_params_test.dart
new file mode 100644
index 0000000..02e2362
--- /dev/null
+++ b/packages/go_router/example/test/path_and_query_params_test.dart
@@ -0,0 +1,47 @@
+// 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/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:go_router_examples/path_and_query_parameters.dart' as example;
+
+void main() {
+ testWidgets('example works', (WidgetTester tester) async {
+ await tester.pumpWidget(example.App());
+ expect(find.text(example.App.title), findsOneWidget);
+
+ // Directly set the url through platform message.
+ Map<String, dynamic> testRouteInformation = <String, dynamic>{
+ 'location': '/family/f1?sort=asc',
+ };
+ ByteData message = const JSONMethodCodec().encodeMethodCall(
+ MethodCall('pushRouteInformation', testRouteInformation),
+ );
+ await ServicesBinding.instance.defaultBinaryMessenger
+ .handlePlatformMessage('flutter/navigation', message, (_) {});
+
+ await tester.pumpAndSettle();
+ // 'Chris' should be higher than 'Tom'.
+ expect(
+ tester.getCenter(find.text('Chris')).dy <
+ tester.getCenter(find.text('Tom')).dy,
+ isTrue);
+
+ testRouteInformation = <String, dynamic>{
+ 'location': '/family/f1?privacy=false',
+ };
+ message = const JSONMethodCodec().encodeMethodCall(
+ MethodCall('pushRouteInformation', testRouteInformation),
+ );
+ await ServicesBinding.instance.defaultBinaryMessenger
+ .handlePlatformMessage('flutter/navigation', message, (_) {});
+
+ await tester.pumpAndSettle();
+ // 'Chris' should be lower than 'Tom'.
+ expect(
+ tester.getCenter(find.text('Chris')).dy >
+ tester.getCenter(find.text('Tom')).dy,
+ isTrue);
+ });
+}
diff --git a/packages/go_router/example/test/reidrection_test.dart b/packages/go_router/example/test/reidrection_test.dart
new file mode 100644
index 0000000..21a366e
--- /dev/null
+++ b/packages/go_router/example/test/reidrection_test.dart
@@ -0,0 +1,52 @@
+// 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:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:go_router_examples/redirection.dart' as example;
+
+void main() {
+ testWidgets('example works', (WidgetTester tester) async {
+ await tester.pumpWidget(example.App());
+ expect(find.text('Login'), findsOneWidget);
+
+ // Directly set the url to the home page.
+ Map<String, dynamic> testRouteInformation = <String, dynamic>{
+ 'location': '/',
+ };
+ ByteData message = const JSONMethodCodec().encodeMethodCall(
+ MethodCall('pushRouteInformation', testRouteInformation),
+ );
+ await ServicesBinding.instance.defaultBinaryMessenger
+ .handlePlatformMessage('flutter/navigation', message, (_) {});
+
+ await tester.pumpAndSettle();
+ // Still show login page due to redirection
+ expect(find.text('Login'), findsOneWidget);
+
+ await tester.tap(find.text('Login'));
+ await tester.pumpAndSettle();
+ expect(find.text('HomeScreen'), findsOneWidget);
+
+ testRouteInformation = <String, dynamic>{
+ 'location': '/login',
+ };
+ message = const JSONMethodCodec().encodeMethodCall(
+ MethodCall('pushRouteInformation', testRouteInformation),
+ );
+ await ServicesBinding.instance.defaultBinaryMessenger
+ .handlePlatformMessage('flutter/navigation', message, (_) {});
+
+ await tester.pumpAndSettle();
+ // Got redirected back to home page.
+ expect(find.text('HomeScreen'), findsOneWidget);
+
+ // Tap logout.
+ await tester.tap(find.byType(IconButton));
+ await tester.pumpAndSettle();
+
+ expect(find.text('Login'), findsOneWidget);
+ });
+}
diff --git a/packages/go_router/example/test/sub_routes_test.dart b/packages/go_router/example/test/sub_routes_test.dart
new file mode 100644
index 0000000..9c5dc70
--- /dev/null
+++ b/packages/go_router/example/test/sub_routes_test.dart
@@ -0,0 +1,33 @@
+// 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:flutter_test/flutter_test.dart';
+import 'package:go_router_examples/sub_routes.dart' as example;
+
+void main() {
+ testWidgets('example works', (WidgetTester tester) async {
+ await tester.pumpWidget(example.App());
+ expect(find.text('Go to family screen'), findsOneWidget);
+
+ await tester.tap(find.text('Go to family screen'));
+ await tester.pumpAndSettle();
+ expect(find.text('Go to person screen'), findsOneWidget);
+
+ await tester.tap(find.text('Go to person screen'));
+ await tester.pumpAndSettle();
+ expect(find.text('This is the person screen'), findsOneWidget);
+ expect(find.byType(BackButton), findsOneWidget);
+
+ await tester.tap(find.byType(BackButton));
+ await tester.pumpAndSettle();
+ expect(find.text('Go to person screen'), findsOneWidget);
+ expect(find.byType(BackButton), findsOneWidget);
+
+ await tester.tap(find.byType(BackButton));
+ await tester.pumpAndSettle();
+ expect(find.text('Go to family screen'), findsOneWidget);
+ expect(find.byType(BackButton), findsNothing);
+ });
+}
diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart
index d753313..d7790f5 100644
--- a/packages/go_router/lib/src/route.dart
+++ b/packages/go_router/lib/src/route.dart
@@ -61,11 +61,44 @@
/// Optional name of the route.
///
/// If used, a unique string name must be provided and it can not be empty.
+ ///
+ /// This is used in [GoRouter.namedLocation] and its related API. This
+ /// property can be used to navigate to this route without knowing exact the
+ /// URI of it.
+ ///
+ /// {@tool snippet}
+ /// Typical usage is as follows:
+ ///
+ /// ```dart
+ /// GoRoute(
+ /// name: 'home',
+ /// path: '/',
+ /// builder: (BuildContext context, GoRouterState state) =>
+ /// HomeScreen(),
+ /// routes: <GoRoute>[
+ /// GoRoute(
+ /// name: 'family',
+ /// path: 'family/:fid',
+ /// builder: (BuildContext context, GoRouterState state) =>
+ /// FamilyScreen(),
+ /// ),
+ /// ],
+ /// );
+ ///
+ /// context.go(
+ /// context.namedLocation('family'),
+ /// params: <String, String>{'fid': 123},
+ /// queryParams: <String, String>{'qid': 'quid'},
+ /// );
+ /// ```
+ ///
+ /// See the [named routes example](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/named_routes.dart)
+ /// for a complete runnable app.
final String? name;
/// The path of this go route.
///
- /// For example in:
+ /// For example:
/// ```
/// GoRoute(
/// path: '/',
@@ -75,6 +108,17 @@
/// ),
/// ),
/// ```
+ ///
+ /// The path also support path parameters. For a path: `/family/:fid`, it
+ /// matches all URIs start with `/family/...`, e.g. `/family/123`,
+ /// `/family/456` and etc. The parameter values are stored in [GoRouterState]
+ /// that are passed into [pageBuilder] and [builder].
+ ///
+ /// The query parameter are also capture during the route parsing and stored
+ /// in [GoRouterState].
+ ///
+ /// See [Query parameters and path parameters](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/sub_routes.dart)
+ /// to learn more about parameters.
final String path;
/// A page builder for this route.
@@ -186,12 +230,15 @@
/// ```
/// In the above example, if /family route is matched, it will be used.
/// else /:username route will be used.
+ ///
+ /// See [Sub-routes](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/sub_routes.dart)
+ /// for a complete runnable example.
final List<GoRoute> routes;
/// An optional redirect function for this route.
///
/// In the case that you like to make a redirection decision for a specific
- /// route (or sub-route), you can do so by passing a redirect function to
+ /// route (or sub-route), consider doing so by passing a redirect function to
/// the GoRoute constructor.
///
/// For example:
@@ -209,6 +256,11 @@
/// ],
/// );
/// ```
+ ///
+ /// 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.
final GoRouterRedirect redirect;
/// Match this route against a location.
diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart
index c49c356..644480f 100644
--- a/packages/go_router/lib/src/router.dart
+++ b/packages/go_router/lib/src/router.dart
@@ -16,11 +16,30 @@
/// The top-level go router class.
///
-/// Create one of these to initialize your app's routing policy.
-// ignore: prefer_mixin
+/// This is the main entry point for defining app's routing policy.
+///
+/// The `routes` defines the routing table. It must not be empty and must
+/// contain an [GoRouter] to match `/`.
+///
+/// See [Routes](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/main.dart)
+/// for an example of defining a simple routing table.
+///
+/// See [Sub-routes](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/sub_routes.dart)
+/// for an example of defining a multi-level routing table.
+///
+/// The `redirect` does top-level redirection before the URIs are parsed by
+/// the `routes`. Consider using [GoRoute.redirect] for individual route
+/// redirection.
+///
+/// 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 {
/// Default constructor to configure a GoRouter with a routes builder
/// and an error page builder.
+ ///
+ /// The `routes` must not be null and must contain an [GoRouter] to match `/`.
GoRouter({
required List<GoRoute> routes,
// TODO(johnpryan): Change to a route, improve error API
diff --git a/packages/go_router/lib/src/state.dart b/packages/go_router/lib/src/state.dart
index e616dc4..5e9c4ab 100644
--- a/packages/go_router/lib/src/state.dart
+++ b/packages/go_router/lib/src/state.dart
@@ -7,6 +7,8 @@
import 'configuration.dart';
/// The route state during routing.
+///
+/// The state contains parsed artifacts of the current URI.
class GoRouterState {
/// Default constructor for creating route state during routing.
GoRouterState(
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index 0b2d6f0..722962e 100644
--- a/packages/go_router/pubspec.yaml
+++ b/packages/go_router/pubspec.yaml
@@ -1,7 +1,7 @@
name: go_router
description: A declarative router for Flutter based on Navigation 2 supporting
deep linking, data-driven routes and more
-version: 4.2.3
+version: 4.2.4
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