From 9840922f038c93eb8be436e9bfbbbdb02f7d20d3 Mon Sep 17 00:00:00 2001 From: Vasiliy Ditsyak Date: Thu, 13 Jun 2024 18:53:57 +0200 Subject: [PATCH] [go_router] Added proper `redirect` handling for `ShellRoute.$route` and `StatefulShellRoute.$route` for proper redirection handling in case of code generation (#6841) Added proper `redirect` handling for `ShellRoute.$route` and `StatefulShellRoute.$route` for proper redirection handling in case of code generation. *List which issues are fixed by this PR. You must list at least one issue.* - I did not create an issue ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] page, which explains my responsibilities. - [x] I read and followed the [relevant style guides] and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages repo does use `dart format`.) - [x] I signed the [CLA]. - [x] The title of the PR starts with the name of the package surrounded by square brackets, e.g. `[shared_preferences]` - [ ] I [linked to at least one issue that this PR fixes] in the description above. - [x] I updated `pubspec.yaml` with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes]. - [x] I updated `CHANGELOG.md` to add a description of the change, [following repository CHANGELOG style], or this PR is [exempt from CHANGELOG changes]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md [Tree Hygiene]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md [relevant style guides]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md#style [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [linked to at least one issue that this PR fixes]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview [pub versioning philosophy]: https://dart.dev/tools/pub/versioning [exempt from version changes]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#version [following repository CHANGELOG style]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog-style [exempt from CHANGELOG changes]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog [test-exempt]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests --------- Co-authored-by: Vasiliy Ditsyak --- packages/go_router/CHANGELOG.md | 4 + packages/go_router/lib/src/route_data.dart | 25 ++++++ packages/go_router/pubspec.yaml | 2 +- packages/go_router/test/route_data_test.dart | 83 ++++++++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index b8a19b4bbb48..c8eaab75a704 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## 14.2.0 + +- Added proper `redirect` handling for `ShellRoute.$route` and `StatefulShellRoute.$route` for proper redirection handling in case of code generation. + ## 14.1.4 - Fixes a URL in `navigation.md`. diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index b10f2b11158f..afe6159cecb9 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -159,6 +159,14 @@ abstract class ShellRouteData extends RouteData { 'One of `builder` or `pageBuilder` must be implemented.', ); + /// An optional redirect function for this route. + /// + /// Subclasses must override one of [build], [buildPage], or + /// [redirect]. + /// + /// Corresponds to [GoRoute.redirect]. + FutureOr redirect(BuildContext context, GoRouterState state) => null; + /// A helper function used by generated code. /// /// Should not be used directly. @@ -174,6 +182,9 @@ abstract class ShellRouteData extends RouteData { return (_stateObjectExpando[state] ??= factory(state)) as T; } + FutureOr redirect(BuildContext context, GoRouterState state) => + factoryImpl(state).redirect(context, state); + Widget builder( BuildContext context, GoRouterState state, @@ -204,6 +215,7 @@ abstract class ShellRouteData extends RouteData { navigatorKey: navigatorKey, observers: observers, restorationScopeId: restorationScopeId, + redirect: redirect, ); } @@ -221,6 +233,14 @@ abstract class StatefulShellRouteData extends RouteData { /// Default const constructor const StatefulShellRouteData(); + /// An optional redirect function for this route. + /// + /// Subclasses must override one of [build], [buildPage], or + /// [redirect]. + /// + /// Corresponds to [GoRoute.redirect]. + FutureOr redirect(BuildContext context, GoRouterState state) => null; + /// [pageBuilder] is used to build the page Page pageBuilder( BuildContext context, @@ -275,6 +295,9 @@ abstract class StatefulShellRouteData extends RouteData { navigationShell, ); + FutureOr redirect(BuildContext context, GoRouterState state) => + factoryImpl(state).redirect(context, state); + if (navigatorContainerBuilder != null) { return StatefulShellRoute( branches: branches, @@ -283,6 +306,7 @@ abstract class StatefulShellRouteData extends RouteData { navigatorContainerBuilder: navigatorContainerBuilder, parentNavigatorKey: parentNavigatorKey, restorationScopeId: restorationScopeId, + redirect: redirect, ); } return StatefulShellRoute.indexedStack( @@ -291,6 +315,7 @@ abstract class StatefulShellRouteData extends RouteData { pageBuilder: pageBuilder, parentNavigatorKey: parentNavigatorKey, restorationScopeId: restorationScopeId, + redirect: redirect, ); } diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index 4d8331d16a6f..eec6806cf6da 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: 14.1.4 +version: 14.2.0 repository: https://github.com/flutter/packages/tree/main/packages/go_router issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22 diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index 1308d42635d1..1da35a672b64 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -10,11 +10,20 @@ import 'package:go_router/go_router.dart'; class _GoRouteDataBuild extends GoRouteData { const _GoRouteDataBuild(); + @override Widget build(BuildContext context, GoRouterState state) => const SizedBox(key: Key('build')); } +class _ShellRouteDataRedirectPage extends ShellRouteData { + const _ShellRouteDataRedirectPage(); + + @override + FutureOr redirect(BuildContext context, GoRouterState state) => + '/build-page'; +} + class _ShellRouteDataBuilder extends ShellRouteData { const _ShellRouteDataBuilder(); @@ -49,7 +58,9 @@ class _ShellRouteDataWithKey extends ShellRouteData { class _GoRouteDataBuildWithKey extends GoRouteData { const _GoRouteDataBuildWithKey(this.key); + final Key key; + @override Widget build(BuildContext context, GoRouterState state) => SizedBox(key: key); } @@ -71,6 +82,7 @@ final ShellRoute _shellRouteDataBuilder = ShellRouteData.$route( class _GoRouteDataBuildPage extends GoRouteData { const _GoRouteDataBuildPage(); + @override Page buildPage(BuildContext context, GoRouterState state) => const MaterialPage( @@ -95,6 +107,14 @@ class _ShellRouteDataPageBuilder extends ShellRouteData { ); } +class _StatefulShellRouteDataRedirectPage extends StatefulShellRouteData { + const _StatefulShellRouteDataRedirectPage(); + + @override + FutureOr redirect(BuildContext context, GoRouterState state) => + '/build-page'; +} + final GoRoute _goRouteDataBuildPage = GoRouteData.$route( path: '/build-page', factory: (GoRouterState state) => const _GoRouteDataBuildPage(), @@ -110,6 +130,21 @@ final ShellRoute _shellRouteDataPageBuilder = ShellRouteData.$route( ], ); +final ShellRoute _shellRouteDataRedirect = ShellRouteData.$route( + factory: (GoRouterState state) => const _ShellRouteDataPageBuilder(), + routes: [ + ShellRouteData.$route( + factory: (GoRouterState state) => const _ShellRouteDataRedirectPage(), + routes: [ + GoRouteData.$route( + path: '/child', + factory: (GoRouterState state) => const _GoRouteDataBuild(), + ), + ], + ), + ], +); + class _StatefulShellRouteDataBuilder extends StatefulShellRouteData { const _StatefulShellRouteDataBuilder(); @@ -174,6 +209,7 @@ final StatefulShellRoute _statefulShellRouteDataPageBuilder = class _GoRouteDataRedirectPage extends GoRouteData { const _GoRouteDataRedirectPage(); + @override FutureOr redirect(BuildContext context, GoRouterState state) => '/build-page'; @@ -311,6 +347,23 @@ void main() { expect(find.byKey(const Key('page-builder')), findsOneWidget); }, ); + + testWidgets( + 'It should redirect using the overridden redirect method', + (WidgetTester tester) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/child', + routes: [ + _goRouteDataBuildPage, + _shellRouteDataRedirect, + ], + ); + addTearDown(goRouter.dispose); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); + expect(find.byKey(const Key('build')), findsNothing); + expect(find.byKey(const Key('buildPage')), findsOneWidget); + }, + ); }); group('StatefulShellRouteData', () { @@ -381,6 +434,36 @@ void main() { }, ); + testWidgets( + 'It should redirect using the overridden StatefulShellRoute redirect method', + (WidgetTester tester) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/child', + routes: [ + _goRouteDataBuildPage, + StatefulShellRouteData.$route( + factory: (GoRouterState state) => + const _StatefulShellRouteDataRedirectPage(), + branches: [ + StatefulShellBranchData.$branch( + routes: [ + GoRouteData.$route( + path: '/child', + factory: (GoRouterState state) => const _GoRouteDataBuild(), + ), + ], + ) + ], + ) + ], + ); + addTearDown(goRouter.dispose); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); + expect(find.byKey(const Key('build')), findsNothing); + expect(find.byKey(const Key('buildPage')), findsOneWidget); + }, + ); + testWidgets( 'It should redirect using the overridden redirect method', (WidgetTester tester) async {