[go_router] If there is more than one route to match, use the first match. (#1995)
diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index c8814d4..a37fabe 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.1.1
+
+- Uses first match if there are more than one route to match. [ [#99833](https://github.com/flutter/flutter/issues/99833)
+
## 3.1.0
- Added `GoRouteData` and `TypedGoRoute` to support `package:go_router_builder`.
diff --git a/packages/go_router/lib/src/go_route.dart b/packages/go_router/lib/src/go_route.dart
index a50831a..832c4e8 100644
--- a/packages/go_router/lib/src/go_route.dart
+++ b/packages/go_router/lib/src/go_route.dart
@@ -162,6 +162,30 @@
/// ],
/// );
///
+ /// If there are multiple routes that match the location, the first match is used.
+ /// To make predefined routes to take precedence over dynamic routes eg. '/:id'
+ /// consider adding the dynamic route at the end of the routes
+ /// For example:
+ /// ```
+ /// final GoRouter _router = GoRouter(
+ /// routes: <GoRoute>[
+ /// GoRoute(
+ /// path: '/',
+ /// redirect: (_) => '/family/${Families.data[0].id}',
+ /// ),
+ /// GoRoute(
+ /// path: '/family',
+ /// pageBuilder: (BuildContext context, GoRouterState state) => ...,
+ /// ),
+ /// GoRoute(
+ /// path: '/:username',
+ /// pageBuilder: (BuildContext context, GoRouterState state) => ...,
+ /// ),
+ /// ],
+ /// );
+ /// ```
+ /// In the above example, if /family route is matched, it will be used.
+ /// else /:username route will be used.
final List<GoRoute> routes;
/// An optional redirect function for this route.
diff --git a/packages/go_router/lib/src/go_router_delegate.dart b/packages/go_router/lib/src/go_router_delegate.dart
index bb05e3c..f901a94 100644
--- a/packages/go_router/lib/src/go_router_delegate.dart
+++ b/packages/go_router/lib/src/go_router_delegate.dart
@@ -441,30 +441,10 @@
).toList();
assert(matchStacks.isNotEmpty, 'no routes for location: $location');
- assert(() {
- if (matchStacks.length > 1) {
- final StringBuffer sb = StringBuffer()
- ..writeln('too many routes for location: $location');
- for (final List<GoRouteMatch> stack in matchStacks) {
- sb.writeln(
- '\t${stack.map((GoRouteMatch m) => m.route.path).join(' => ')}');
- }
-
- assert(false, sb.toString());
- }
-
- assert(matchStacks.length == 1);
- final GoRouteMatch match = matchStacks.first.last;
- final String loc1 = _addQueryParams(match.subloc, match.queryParams);
- final Uri uri2 = Uri.parse(location);
- final String loc2 = _addQueryParams(uri2.path, uri2.queryParameters);
-
- // NOTE: match the lower case, since subloc is canonicalized to match the
- // path case whereas the location can be any case
- assert(loc1.toLowerCase() == loc2.toLowerCase(), '$loc1 != $loc2');
- return true;
- }());
+ // If there are multiple routes that match the location, returning the first one.
+ // To make predefined routes to take precedence over dynamic routes eg. '/:id'
+ // consider adding the dynamic route at the end of the routes
return matchStacks.first;
}
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index 7b66096..552c431 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: 3.1.0
+version: 3.1.1
repository: https://github.com/flutter/packages/tree/main/packages/go_router
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart
index bc0804e..8316597 100644
--- a/packages/go_router/test/go_router_test.dart
+++ b/packages/go_router/test/go_router_test.dart
@@ -39,7 +39,7 @@
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
});
- test('match too many routes', () {
+ test('If there is more than one route to match, use the first match', () {
final List<GoRoute> routes = <GoRoute>[
GoRoute(path: '/', builder: _dummy),
GoRoute(path: '/', builder: _dummy),
@@ -50,7 +50,7 @@
final List<GoRouteMatch> matches = router.routerDelegate.matches;
expect(matches, hasLength(1));
expect(matches.first.fullpath, '/');
- expect(router.screenFor(matches.first).runtimeType, ErrorScreen);
+ expect(router.screenFor(matches.first).runtimeType, DummyScreen);
});
test('empty path', () {
@@ -275,23 +275,32 @@
}
});
- test('match too many sub-routes', () {
+ test('return first matching route if too many subroutes', () {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
- builder: _dummy,
+ builder: (BuildContext context, GoRouterState state) =>
+ const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'foo/bar',
- builder: _dummy,
+ builder: (BuildContext context, GoRouterState state) =>
+ const FamilyScreen(''),
+ ),
+ GoRoute(
+ path: 'bar',
+ builder: (BuildContext context, GoRouterState state) =>
+ const Page1Screen(),
),
GoRoute(
path: 'foo',
- builder: _dummy,
+ builder: (BuildContext context, GoRouterState state) =>
+ const Page2Screen(),
routes: <GoRoute>[
GoRoute(
path: 'bar',
- builder: _dummy,
+ builder: (BuildContext context, GoRouterState state) =>
+ const LoginScreen(),
),
],
),
@@ -300,10 +309,20 @@
];
final GoRouter router = _router(routes);
+ router.go('/bar');
+ List<GoRouteMatch> matches = router.routerDelegate.matches;
+ expect(matches, hasLength(2));
+ expect(router.screenFor(matches[1]).runtimeType, Page1Screen);
+
router.go('/foo/bar');
- final List<GoRouteMatch> matches = router.routerDelegate.matches;
- expect(matches, hasLength(1));
- expect(router.screenFor(matches.first).runtimeType, ErrorScreen);
+ matches = router.routerDelegate.matches;
+ expect(matches, hasLength(2));
+ expect(router.screenFor(matches[1]).runtimeType, FamilyScreen);
+
+ router.go('/foo');
+ matches = router.routerDelegate.matches;
+ expect(matches, hasLength(2));
+ expect(router.screenFor(matches[1]).runtimeType, Page2Screen);
});
test('router state', () {
@@ -424,17 +443,19 @@
expect(router.screenFor(matches.first).runtimeType, FamilyScreen);
});
- test('match too many routes, ignoring case', () {
+ test('If there is more than one route to match, use the first match.', () {
final List<GoRoute> routes = <GoRoute>[
+ GoRoute(path: '/', builder: _dummy),
GoRoute(path: '/page1', builder: _dummy),
- GoRoute(path: '/PaGe1', builder: _dummy),
+ GoRoute(path: '/page1', builder: _dummy),
+ GoRoute(path: '/:ok', builder: _dummy),
];
final GoRouter router = _router(routes);
- router.go('/PAGE1');
+ router.go('/user');
final List<GoRouteMatch> matches = router.routerDelegate.matches;
expect(matches, hasLength(1));
- expect(router.screenFor(matches.first).runtimeType, ErrorScreen);
+ expect(router.screenFor(matches.first).runtimeType, DummyScreen);
});
});