From 990c2934525a158bc65e311f4c5cba1ad5279c9c Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Thu, 15 Jun 2023 18:17:17 -0700 Subject: [PATCH 01/28] implemented helpers for StatefulShellRoute --- packages/go_router/CHANGELOG.md | 4 + packages/go_router/lib/go_router.dart | 6 +- packages/go_router/lib/src/route.dart | 4 +- packages/go_router/lib/src/route_data.dart | 159 ++++++++++++++++++- packages/go_router/pubspec.yaml | 2 +- packages/go_router/test/route_data_test.dart | 106 ++++++++++++- 6 files changed, 274 insertions(+), 7 deletions(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 28e8c8d12ac..cd084db787f 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## 8.0.3 + +- Adds helpers for go_router_builder for StatefulShellRoute support + ## 8.0.2 - Fixes a bug in `debugLogDiagnostics` to support StatefulShellRoute. diff --git a/packages/go_router/lib/go_router.dart b/packages/go_router/lib/go_router.dart index 31ed9c865b1..c8c47b8c792 100644 --- a/packages/go_router/lib/go_router.dart +++ b/packages/go_router/lib/go_router.dart @@ -25,9 +25,13 @@ export 'src/route_data.dart' RouteData, GoRouteData, ShellRouteData, + StatefulShellBranchData, + StatefulShellRouteData, TypedRoute, TypedGoRoute, - TypedShellRoute; + TypedShellRoute, + TypedStatefulShellBranch, + TypedStatefulShellRoute; export 'src/router.dart'; export 'src/typedefs.dart' show diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart index ba656326e32..2a40d077fdf 100644 --- a/packages/go_router/lib/src/route.dart +++ b/packages/go_router/lib/src/route.dart @@ -655,8 +655,8 @@ class StatefulShellRoute extends ShellRouteBase { required this.navigatorContainerBuilder, this.restorationScopeId, }) : assert(branches.isNotEmpty), - assert((pageBuilder != null) ^ (builder != null), - 'One of builder or pageBuilder must be provided, but not both'), + assert((pageBuilder != null) || (builder != null), + 'One of builder or pageBuilder must be provided'), assert(_debugUniqueNavigatorKeys(branches).length == branches.length, 'Navigator keys must be unique'), assert(_debugValidateParentNavigatorKeys(branches)), diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index 23e6740342f..1c9a709272c 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -137,7 +137,7 @@ abstract class ShellRouteData extends RouteData { ) => const NoOpPage(); - /// [pageBuilder] is used to build the page + /// [builder] is used to build the widget Widget builder( BuildContext context, GoRouterState state, @@ -159,7 +159,7 @@ abstract class ShellRouteData extends RouteData { final Object? extra = state.extra; // If the "extra" value is of type `T` then we know it's the source - // instance of `GoRouteData`, so it doesn't need to be recreated. + // instance of `ShellRouteData`, so it doesn't need to be recreated. if (extra is T) { return extra; } @@ -205,6 +205,131 @@ abstract class ShellRouteData extends RouteData { ); } + + +/// Base class for supporting +/// [StatefulShellRoute](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute-class.html) +abstract class StatefulShellRouteData extends RouteData { + /// Default const constructor + const StatefulShellRouteData(); + + + /// [pageBuilder] is used to build the page + Page pageBuilder( + BuildContext context, + GoRouterState state, + Widget navigator, + ) => + const NoOpPage(); + + /// [builder] is used to build the widget + Widget builder( + BuildContext context, + GoRouterState state, + StatefulNavigationShell navigationShell, + ) => + throw UnimplementedError( + 'One of `builder` or `pageBuilder` must be implemented.', + ); + + /// A helper function used by generated code. + /// + /// Should not be used directly. + static StatefulShellRoute $route({ + required T Function(GoRouterState) factory, + required List branches, + String? restorationScopeId, + }) { + T factoryImpl(GoRouterState state) { + final Object? extra = state.extra; + + // If the "extra" value is of type `T` then we know it's the source + // instance of `StatefulShellRouteData`, so it doesn't need to be recreated. + if (extra is T) { + return extra; + } + + return (_stateObjectExpando[state] ??= factory(state)) as T; + } + + Widget builder( + BuildContext context, + GoRouterState state, + StatefulNavigationShell navigationShell, + ) => + factoryImpl(state).builder( + context, + state, + navigationShell, + ); + + Page pageBuilder( + BuildContext context, + GoRouterState state, + StatefulNavigationShell navigationShell, + ) => + factoryImpl(state).pageBuilder( + context, + state, + navigationShell, + ); + + return StatefulShellRoute.indexedStack( + branches: branches, + builder: builder, + pageBuilder: pageBuilder, + restorationScopeId: restorationScopeId, + ); + } + + /// Used to cache [StatefulShellRouteData] that corresponds to a given [GoRouterState] + /// to minimize the number of times it has to be deserialized. + static final Expando _stateObjectExpando = + Expando( + 'GoRouteState to StatefulShellRouteData expando', + ); +} + +/// Base class for supporting +/// [StatefulShellRoute](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute-class.html) +abstract class StatefulShellBranchData extends RouteData { + /// Default const constructor + const StatefulShellBranchData(); + + /// A helper function used by generated code. + /// + /// Should not be used directly. + static StatefulShellBranch $route({ + required T Function(GoRouterState) factory, + GlobalKey? navigatorKey, + List routes = const [], + }) { + T factoryImpl(GoRouterState state) { + final Object? extra = state.extra; + + // If the "extra" value is of type `T` then we know it's the source + // instance of `StatefulShellBranchData`, so it doesn't need to be recreated. + if (extra is T) { + return extra; + } + + return (_stateObjectExpando[state] ??= factory(state)) as T; + } + + return StatefulShellBranch( + routes: routes, + navigatorKey: navigatorKey, + ); + } + + /// Used to cache [StatefulShellBranchData] that corresponds to a given [GoRouterState] + /// to minimize the number of times it has to be deserialized. + static final Expando _stateObjectExpando = + Expando( + 'GoRouteState to StatefulShellBranchData expando', + ); +} + /// A superclass for each typed route descendant class TypedRoute { /// Default const constructor @@ -256,6 +381,36 @@ class TypedShellRoute extends TypedRoute { final List> routes; } +/// A superclass for each typed shell route descendant +@Target({TargetKind.library, TargetKind.classType}) +class TypedStatefulShellRoute + extends TypedRoute { + /// Default const constructor + const TypedStatefulShellRoute({ + this.routes = const >[], + }); + + /// Child route definitions. + /// + /// See [RouteBase.routes]. + final List> routes; +} + +/// A superclass for each typed shell route descendant +@Target({TargetKind.library, TargetKind.classType}) +class TypedStatefulShellBranch + extends TypedRoute { + /// Default const constructor + const TypedStatefulShellBranch({ + this.routes = const >[], + }); + + /// Child route definitions. + /// + /// See [RouteBase.routes]. + final List> routes; +} + /// Internal class used to signal that the default page behavior should be used. @internal class NoOpPage extends Page { diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index f5cd92323bf..a2f48a79a43 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: 8.0.2 +version: 8.0.3 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 15419406f64..9abd15d6dd8 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -44,7 +44,6 @@ final ShellRoute _shellRouteDataBuilder = ShellRouteData.$route( ), ], ); - class _GoRouteDataBuildPage extends GoRouteData { const _GoRouteDataBuildPage(); @override @@ -86,6 +85,71 @@ final ShellRoute _shellRouteDataPageBuilder = ShellRouteData.$route( ], ); +class _StatefulShellBranchDataBuilder extends StatefulShellBranchData { + const _StatefulShellBranchDataBuilder(); +} +class _StatefulShellRouteDataBuilder extends StatefulShellRouteData { + const _StatefulShellRouteDataBuilder(); + + @override + Widget builder( + BuildContext context, + GoRouterState state, + Widget navigator, + ) => + SizedBox( + key: const Key('builder'), + child: navigator, + ); +} +final StatefulShellRoute _statefulShellRouteDataBuilder = + StatefulShellRouteData.$route( + factory: (GoRouterState state) => const _StatefulShellRouteDataBuilder(), + branches: [ + StatefulShellBranchData.$route( + factory: (GoRouterState state) => const _StatefulShellBranchDataBuilder(), + routes: [ + GoRouteData.$route( + path: '/child', + factory: (GoRouterState state) => const _GoRouteDataBuild(), + ), + ], + ), + ], +); + +class _StatefulShellRouteDataPageBuilder extends StatefulShellRouteData { + const _StatefulShellRouteDataPageBuilder(); + + @override + Page pageBuilder( + BuildContext context, + GoRouterState state, + Widget navigator, + ) => + MaterialPage( + child: SizedBox( + key: const Key('page-builder'), + child: navigator, + ), + ); +} + +final StatefulShellRoute _statefulShellRouteDataPageBuilder = StatefulShellRouteData.$route( + factory: (GoRouterState state) => const _StatefulShellRouteDataPageBuilder(), + branches: [ + StatefulShellBranchData.$route( + factory: (GoRouterState state) => const _StatefulShellBranchDataBuilder(), + routes: [ + GoRouteData.$route( + path: '/child', + factory: (GoRouterState state) => const _GoRouteDataBuild(), + ), + ], + ), + ], +); + class _GoRouteDataRedirectPage extends GoRouteData { const _GoRouteDataRedirectPage(); @override @@ -181,6 +245,46 @@ void main() { ); }); + group('StatefulShellRouteData', () { + testWidgets( + 'It should build the page from the overridden build method', + (WidgetTester tester) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/child', + routes: [ + _statefulShellRouteDataBuilder, + ], + ); + await tester.pumpWidget(MaterialApp.router( + routeInformationProvider: goRouter.routeInformationProvider, + routeInformationParser: goRouter.routeInformationParser, + routerDelegate: goRouter.routerDelegate, + )); + expect(find.byKey(const Key('builder')), findsOneWidget); + expect(find.byKey(const Key('page-builder')), findsNothing); + }, + ); + + testWidgets( + 'It should build the page from the overridden buildPage method', + (WidgetTester tester) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/child', + routes: [ + _statefulShellRouteDataPageBuilder, + ], + ); + await tester.pumpWidget(MaterialApp.router( + routeInformationProvider: goRouter.routeInformationProvider, + routeInformationParser: goRouter.routeInformationParser, + routerDelegate: goRouter.routerDelegate, + )); + expect(find.byKey(const Key('builder')), findsNothing); + expect(find.byKey(const Key('page-builder')), findsOneWidget); + }, + ); + }); + testWidgets( 'It should redirect using the overridden redirect method', (WidgetTester tester) async { From c6bde6d4a7d2c495408f023a6cb760ace9244b64 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Thu, 15 Jun 2023 18:22:25 -0700 Subject: [PATCH 02/28] lint --- packages/go_router/CHANGELOG.md | 1 - packages/go_router/test/route_data_test.dart | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 19ae2fb0a7f..d0734afc11f 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -14,7 +14,6 @@ - Makes namedLocation and route name related APIs case sensitive. - ## 8.0.2 - Fixes a bug in `debugLogDiagnostics` to support StatefulShellRoute. diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index 9abd15d6dd8..88e305f11ce 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -44,6 +44,7 @@ final ShellRoute _shellRouteDataBuilder = ShellRouteData.$route( ), ], ); + class _GoRouteDataBuildPage extends GoRouteData { const _GoRouteDataBuildPage(); @override From 52ce037f8cd5c1de2e334f3a0dbbbd37eb962b81 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Thu, 15 Jun 2023 18:26:31 -0700 Subject: [PATCH 03/28] Update route_data.dart --- packages/go_router/lib/src/route_data.dart | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index 1c9a709272c..b09318af16b 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -304,30 +304,11 @@ abstract class StatefulShellBranchData extends RouteData { GlobalKey? navigatorKey, List routes = const [], }) { - T factoryImpl(GoRouterState state) { - final Object? extra = state.extra; - - // If the "extra" value is of type `T` then we know it's the source - // instance of `StatefulShellBranchData`, so it doesn't need to be recreated. - if (extra is T) { - return extra; - } - - return (_stateObjectExpando[state] ??= factory(state)) as T; - } - return StatefulShellBranch( routes: routes, navigatorKey: navigatorKey, ); } - - /// Used to cache [StatefulShellBranchData] that corresponds to a given [GoRouterState] - /// to minimize the number of times it has to be deserialized. - static final Expando _stateObjectExpando = - Expando( - 'GoRouteState to StatefulShellBranchData expando', - ); } /// A superclass for each typed route descendant From b9c80807992d236f1a5e9035627d545147c51b5b Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Thu, 15 Jun 2023 18:27:12 -0700 Subject: [PATCH 04/28] lint --- packages/go_router/test/route_data_test.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index 88e305f11ce..0fbd36ab615 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -89,6 +89,7 @@ final ShellRoute _shellRouteDataPageBuilder = ShellRouteData.$route( class _StatefulShellBranchDataBuilder extends StatefulShellBranchData { const _StatefulShellBranchDataBuilder(); } + class _StatefulShellRouteDataBuilder extends StatefulShellRouteData { const _StatefulShellRouteDataBuilder(); @@ -103,6 +104,7 @@ class _StatefulShellRouteDataBuilder extends StatefulShellRouteData { child: navigator, ); } + final StatefulShellRoute _statefulShellRouteDataBuilder = StatefulShellRouteData.$route( factory: (GoRouterState state) => const _StatefulShellRouteDataBuilder(), @@ -136,7 +138,8 @@ class _StatefulShellRouteDataPageBuilder extends StatefulShellRouteData { ); } -final StatefulShellRoute _statefulShellRouteDataPageBuilder = StatefulShellRouteData.$route( +final StatefulShellRoute _statefulShellRouteDataPageBuilder = + StatefulShellRouteData.$route( factory: (GoRouterState state) => const _StatefulShellRouteDataPageBuilder(), branches: [ StatefulShellBranchData.$route( @@ -266,7 +269,7 @@ void main() { }, ); - testWidgets( + testWidgets( 'It should build the page from the overridden buildPage method', (WidgetTester tester) async { final GoRouter goRouter = GoRouter( From 9005a8cc9cd614d9e6e8b81561e1e98dd1cab643 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Fri, 16 Jun 2023 12:28:07 -0700 Subject: [PATCH 05/28] Update route_data.dart --- packages/go_router/lib/src/route_data.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index b09318af16b..85a221199b9 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -205,15 +205,12 @@ abstract class ShellRouteData extends RouteData { ); } - - /// Base class for supporting /// [StatefulShellRoute](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute-class.html) abstract class StatefulShellRouteData extends RouteData { /// Default const constructor const StatefulShellRouteData(); - /// [pageBuilder] is used to build the page Page pageBuilder( BuildContext context, From 8cf69b6d37ad96799dfe1dec443ce622802e7841 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:53:14 -0700 Subject: [PATCH 06/28] update --- packages/go_router/lib/src/route_data.dart | 31 ++++++++++---------- packages/go_router/test/route_data_test.dart | 4 +-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index 85a221199b9..f057c452f83 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -215,7 +215,7 @@ abstract class StatefulShellRouteData extends RouteData { Page pageBuilder( BuildContext context, GoRouterState state, - Widget navigator, + StatefulNavigationShell navigationShell, ) => const NoOpPage(); @@ -235,17 +235,10 @@ abstract class StatefulShellRouteData extends RouteData { static StatefulShellRoute $route({ required T Function(GoRouterState) factory, required List branches, + ShellNavigationContainerBuilder? navigatorContainerBuilder, String? restorationScopeId, }) { T factoryImpl(GoRouterState state) { - final Object? extra = state.extra; - - // If the "extra" value is of type `T` then we know it's the source - // instance of `StatefulShellRouteData`, so it doesn't need to be recreated. - if (extra is T) { - return extra; - } - return (_stateObjectExpando[state] ??= factory(state)) as T; } @@ -271,6 +264,14 @@ abstract class StatefulShellRouteData extends RouteData { navigationShell, ); + if (navigatorContainerBuilder != null) { + return StatefulShellRoute( + branches: branches, + builder: builder, + pageBuilder: pageBuilder, + navigatorContainerBuilder: navigatorContainerBuilder, + ); + } return StatefulShellRoute.indexedStack( branches: branches, builder: builder, @@ -289,15 +290,14 @@ abstract class StatefulShellRouteData extends RouteData { /// Base class for supporting /// [StatefulShellRoute](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute-class.html) -abstract class StatefulShellBranchData extends RouteData { +abstract class StatefulShellBranchData { /// Default const constructor const StatefulShellBranchData(); /// A helper function used by generated code. /// /// Should not be used directly. - static StatefulShellBranch $route({ - required T Function(GoRouterState) factory, + static StatefulShellBranch $branch({ GlobalKey? navigatorKey, List routes = const [], }) { @@ -365,19 +365,18 @@ class TypedStatefulShellRoute extends TypedRoute { /// Default const constructor const TypedStatefulShellRoute({ - this.routes = const >[], + this.branches = const >[], }); /// Child route definitions. /// /// See [RouteBase.routes]. - final List> routes; + final List> branches; } /// A superclass for each typed shell route descendant @Target({TargetKind.library, TargetKind.classType}) -class TypedStatefulShellBranch - extends TypedRoute { +class TypedStatefulShellBranch { /// Default const constructor const TypedStatefulShellBranch({ this.routes = const >[], diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index 0fbd36ab615..e34e3d233a6 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -109,7 +109,7 @@ final StatefulShellRoute _statefulShellRouteDataBuilder = StatefulShellRouteData.$route( factory: (GoRouterState state) => const _StatefulShellRouteDataBuilder(), branches: [ - StatefulShellBranchData.$route( + StatefulShellBranchData.$branch( factory: (GoRouterState state) => const _StatefulShellBranchDataBuilder(), routes: [ GoRouteData.$route( @@ -142,7 +142,7 @@ final StatefulShellRoute _statefulShellRouteDataPageBuilder = StatefulShellRouteData.$route( factory: (GoRouterState state) => const _StatefulShellRouteDataPageBuilder(), branches: [ - StatefulShellBranchData.$route( + StatefulShellBranchData.$branch( factory: (GoRouterState state) => const _StatefulShellBranchDataBuilder(), routes: [ GoRouteData.$route( From 5963020937a7763ff58f0f2f87cb03ea6c4ec258 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:06:29 -0700 Subject: [PATCH 07/28] resolve comments --- packages/go_router/lib/src/route_data.dart | 8 --- packages/go_router/test/route_data_test.dart | 58 ++++---------------- 2 files changed, 10 insertions(+), 56 deletions(-) diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index f057c452f83..a06435df8d6 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -156,14 +156,6 @@ abstract class ShellRouteData extends RouteData { List routes = const [], }) { T factoryImpl(GoRouterState state) { - final Object? extra = state.extra; - - // If the "extra" value is of type `T` then we know it's the source - // instance of `ShellRouteData`, so it doesn't need to be recreated. - if (extra is T) { - return extra; - } - return (_stateObjectExpando[state] ??= factory(state)) as T; } diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index e34e3d233a6..684c6c3bcbb 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -86,10 +86,6 @@ final ShellRoute _shellRouteDataPageBuilder = ShellRouteData.$route( ], ); -class _StatefulShellBranchDataBuilder extends StatefulShellBranchData { - const _StatefulShellBranchDataBuilder(); -} - class _StatefulShellRouteDataBuilder extends StatefulShellRouteData { const _StatefulShellRouteDataBuilder(); @@ -97,7 +93,7 @@ class _StatefulShellRouteDataBuilder extends StatefulShellRouteData { Widget builder( BuildContext context, GoRouterState state, - Widget navigator, + StatefulNavigationShell navigator, ) => SizedBox( key: const Key('builder'), @@ -110,7 +106,6 @@ final StatefulShellRoute _statefulShellRouteDataBuilder = factory: (GoRouterState state) => const _StatefulShellRouteDataBuilder(), branches: [ StatefulShellBranchData.$branch( - factory: (GoRouterState state) => const _StatefulShellBranchDataBuilder(), routes: [ GoRouteData.$route( path: '/child', @@ -128,7 +123,7 @@ class _StatefulShellRouteDataPageBuilder extends StatefulShellRouteData { Page pageBuilder( BuildContext context, GoRouterState state, - Widget navigator, + StatefulNavigationShell navigator, ) => MaterialPage( child: SizedBox( @@ -143,7 +138,6 @@ final StatefulShellRoute _statefulShellRouteDataPageBuilder = factory: (GoRouterState state) => const _StatefulShellRouteDataPageBuilder(), branches: [ StatefulShellBranchData.$branch( - factory: (GoRouterState state) => const _StatefulShellBranchDataBuilder(), routes: [ GoRouteData.$route( path: '/child', @@ -181,11 +175,7 @@ void main() { initialLocation: '/build', routes: _routes, ); - await tester.pumpWidget(MaterialApp.router( - routeInformationProvider: goRouter.routeInformationProvider, - routeInformationParser: goRouter.routeInformationParser, - routerDelegate: goRouter.routerDelegate, - )); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('build')), findsOneWidget); expect(find.byKey(const Key('buildPage')), findsNothing); }, @@ -198,11 +188,7 @@ void main() { initialLocation: '/build-page', routes: _routes, ); - await tester.pumpWidget(MaterialApp.router( - routeInformationProvider: goRouter.routeInformationProvider, - routeInformationParser: goRouter.routeInformationParser, - routerDelegate: goRouter.routerDelegate, - )); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('build')), findsNothing); expect(find.byKey(const Key('buildPage')), findsOneWidget); }, @@ -219,11 +205,7 @@ void main() { _shellRouteDataBuilder, ], ); - await tester.pumpWidget(MaterialApp.router( - routeInformationProvider: goRouter.routeInformationProvider, - routeInformationParser: goRouter.routeInformationParser, - routerDelegate: goRouter.routerDelegate, - )); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('builder')), findsOneWidget); expect(find.byKey(const Key('page-builder')), findsNothing); }, @@ -238,11 +220,7 @@ void main() { _shellRouteDataPageBuilder, ], ); - await tester.pumpWidget(MaterialApp.router( - routeInformationProvider: goRouter.routeInformationProvider, - routeInformationParser: goRouter.routeInformationParser, - routerDelegate: goRouter.routerDelegate, - )); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('builder')), findsNothing); expect(find.byKey(const Key('page-builder')), findsOneWidget); }, @@ -259,11 +237,7 @@ void main() { _statefulShellRouteDataBuilder, ], ); - await tester.pumpWidget(MaterialApp.router( - routeInformationProvider: goRouter.routeInformationProvider, - routeInformationParser: goRouter.routeInformationParser, - routerDelegate: goRouter.routerDelegate, - )); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('builder')), findsOneWidget); expect(find.byKey(const Key('page-builder')), findsNothing); }, @@ -278,11 +252,7 @@ void main() { _statefulShellRouteDataPageBuilder, ], ); - await tester.pumpWidget(MaterialApp.router( - routeInformationProvider: goRouter.routeInformationProvider, - routeInformationParser: goRouter.routeInformationParser, - routerDelegate: goRouter.routerDelegate, - )); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('builder')), findsNothing); expect(find.byKey(const Key('page-builder')), findsOneWidget); }, @@ -296,11 +266,7 @@ void main() { initialLocation: '/redirect', routes: _routes, ); - await tester.pumpWidget(MaterialApp.router( - routeInformationProvider: goRouter.routeInformationProvider, - routeInformationParser: goRouter.routeInformationParser, - routerDelegate: goRouter.routerDelegate, - )); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('build')), findsNothing); expect(find.byKey(const Key('buildPage')), findsOneWidget); }, @@ -313,11 +279,7 @@ void main() { initialLocation: '/redirect-with-state', routes: _routes, ); - await tester.pumpWidget(MaterialApp.router( - routeInformationProvider: goRouter.routeInformationProvider, - routeInformationParser: goRouter.routeInformationParser, - routerDelegate: goRouter.routerDelegate, - )); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('build')), findsNothing); expect(find.byKey(const Key('buildPage')), findsNothing); }, From 4f60cd614676577e6de845de6a6abfcd6c0ec170 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Thu, 15 Jun 2023 18:19:11 -0700 Subject: [PATCH 08/28] 1 --- .../go_router_builder/example/lib/main.dart | 419 +++++------------- .../go_router_builder/example/lib/main.g.dart | 147 ++---- .../lib/stateful_shell_route_example.dart | 153 +++++++ .../lib/stateful_shell_route_example.g.dart | 83 ++++ .../go_router_builder/example/pubspec.yaml | 3 +- .../lib/src/go_router_generator.dart | 2 + .../lib/src/route_config.dart | 39 +- packages/go_router_builder/pubspec.yaml | 3 +- 8 files changed, 423 insertions(+), 426 deletions(-) create mode 100644 packages/go_router_builder/example/lib/stateful_shell_route_example.dart create mode 100644 packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart diff --git a/packages/go_router_builder/example/lib/main.dart b/packages/go_router_builder/example/lib/main.dart index 84b28c2ae56..49fa3b3cb0e 100644 --- a/packages/go_router_builder/example/lib/main.dart +++ b/packages/go_router_builder/example/lib/main.dart @@ -4,371 +4,192 @@ // ignore_for_file: public_member_api_docs -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; - -import 'shared/data.dart'; part 'main.g.dart'; +final GlobalKey _sectionANavigatorKey = + GlobalKey(debugLabel: 'sectionANav'); void main() => runApp(App()); class App extends StatelessWidget { App({super.key}); - final LoginInfo loginInfo = LoginInfo(); - static const String title = 'GoRouter Example: Named Routes'; - @override - Widget build(BuildContext context) => ChangeNotifierProvider.value( - value: loginInfo, - child: MaterialApp.router( - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - routeInformationProvider: _router.routeInformationProvider, - title: title, - debugShowCheckedModeBanner: false, - ), + Widget build(BuildContext context) => MaterialApp.router( + routerConfig: _router, ); - late final GoRouter _router = GoRouter( - debugLogDiagnostics: true, + final GoRouter _router = GoRouter( routes: $appRoutes, + initialLocation: '/detailsA', + ); +} - // redirect to the login page if the user is not logged in - redirect: (BuildContext context, GoRouterState state) { - final bool loggedIn = loginInfo.loggedIn; - - // check just the matchedLocation in case there are query parameters - final String loginLoc = const LoginRoute().location; - final bool goingToLogin = state.matchedLocation == loginLoc; - - // the user is not logged in and not headed to /login, they need to login - if (!loggedIn && !goingToLogin) { - return LoginRoute(fromPage: state.matchedLocation).location; - } - - // the user is logged in and headed to /login, no need to login again - if (loggedIn && goingToLogin) { - return const HomeRoute().location; - } - - // no need to redirect at all - return null; - }, +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); - // changes on the listenable will cause the router to refresh it's route - refreshListenable: loginInfo, - ); + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: const Text('foo')), + ); } -@TypedGoRoute( - path: '/', - routes: >[ - TypedGoRoute( - path: 'family/:fid', - routes: >[ - TypedGoRoute( - path: 'person/:pid', - routes: >[ - TypedGoRoute(path: 'details/:details'), - ], - ), +@TypedStatefulShellRoute( + routes: >[ + TypedStatefulShellBranch( + routes: >[ + TypedGoRoute(path: '/detailsA'), + ], + ), + TypedStatefulShellBranch( + routes: >[ + TypedGoRoute(path: '/detailsB'), ], ), - TypedGoRoute(path: 'family-count/:count'), ], ) -class HomeRoute extends GoRouteData { - const HomeRoute(); +class MyShellRouteData extends StatefulShellRouteData { + const MyShellRouteData(); @override - Widget build(BuildContext context, GoRouterState state) => const HomeScreen(); + Widget builder( + BuildContext context, + GoRouterState state, + StatefulNavigationShell navigationShell, + ) { + return ScaffoldWithNavBar(navigationShell: navigationShell); + } } -@TypedGoRoute( - path: '/login', -) -class LoginRoute extends GoRouteData { - const LoginRoute({this.fromPage}); - - final String? fromPage; - - @override - Widget build(BuildContext context, GoRouterState state) => - LoginScreen(from: fromPage); +class BranchAData extends StatefulShellBranchData { + const BranchAData(); } -class FamilyRoute extends GoRouteData { - const FamilyRoute(this.fid); - - final String fid; +class BranchBData extends StatefulShellBranchData { + const BranchBData(); - @override - Widget build(BuildContext context, GoRouterState state) => - FamilyScreen(family: familyById(fid)); + static final GlobalKey $navigatorKey = _sectionANavigatorKey; } -class PersonRoute extends GoRouteData { - const PersonRoute(this.fid, this.pid); - - final String fid; - final int pid; +class DetailsARouteData extends GoRouteData { + const DetailsARouteData(); @override Widget build(BuildContext context, GoRouterState state) { - final Family family = familyById(fid); - final Person person = family.person(pid); - return PersonScreen(family: family, person: person); + return const DetailsScreen(label: 'A'); } } -class PersonDetailsRoute extends GoRouteData { - const PersonDetailsRoute(this.fid, this.pid, this.details, {this.$extra}); - - final String fid; - final int pid; - final PersonDetails details; - final int? $extra; +class DetailsBRouteData extends GoRouteData { + const DetailsBRouteData(); @override - Page buildPage(BuildContext context, GoRouterState state) { - final Family family = familyById(fid); - final Person person = family.person(pid); - - return MaterialPage( - fullscreenDialog: true, - key: state.pageKey, - child: PersonDetailsPage( - family: family, - person: person, - detailsKey: details, - extra: $extra, - ), + Widget build(BuildContext context, GoRouterState state) { + return DetailsScreen( + label: 'B' ); } } -class FamilyCountRoute extends GoRouteData { - const FamilyCountRoute(this.count); - - final int count; - - @override - Widget build(BuildContext context, GoRouterState state) => FamilyCountScreen( - count: count, - ); -} +/// Builds the "shell" for the app by building a Scaffold with a +/// BottomNavigationBar, where [child] is placed in the body of the Scaffold. +class ScaffoldWithNavBar extends StatelessWidget { + /// Constructs an [ScaffoldWithNavBar]. + const ScaffoldWithNavBar({ + required this.navigationShell, + Key? key, + }) : super(key: key ?? const ValueKey('ScaffoldWithNavBar')); -class HomeScreen extends StatelessWidget { - const HomeScreen({super.key}); + /// The navigation shell and container for the branch Navigators. + final StatefulNavigationShell navigationShell; @override Widget build(BuildContext context) { - final LoginInfo info = context.read(); - return Scaffold( - appBar: AppBar( - title: const Text(App.title), - centerTitle: true, - actions: [ - PopupMenuButton( - itemBuilder: (BuildContext context) { - return >[ - PopupMenuItem( - value: '1', - child: const Text('Push w/o return value'), - onTap: () => const PersonRoute('f1', 1).push(context), - ), - PopupMenuItem( - value: '2', - child: const Text('Push w/ return value'), - onTap: () async { - unawaited(FamilyCountRoute(familyData.length) - .push(context) - .then((int? value) { - if (value != null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Age was: $value'), - ), - ); - } - })); - }, - ), - PopupMenuItem( - value: '3', - child: Text('Logout: ${info.userName}'), - onTap: () => info.logout(), - ), - ]; - }, - ), - ], - ), - body: ListView( - children: [ - for (final Family f in familyData) - ListTile( - title: Text(f.name), - onTap: () => FamilyRoute(f.id).go(context), - ) + body: navigationShell, + bottomNavigationBar: BottomNavigationBar( + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'), + BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'), ], + currentIndex: navigationShell.currentIndex, + onTap: (int index) => _onTap(context, index), ), ); } -} - -class FamilyScreen extends StatelessWidget { - const FamilyScreen({required this.family, super.key}); - final Family family; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(family.name)), - body: ListView( - children: [ - for (final Person p in family.people) - ListTile( - title: Text(p.name), - onTap: () => PersonRoute(family.id, p.id).go(context), - ), - ], - ), - ); -} - -class PersonScreen extends StatelessWidget { - const PersonScreen({required this.family, required this.person, super.key}); - final Family family; - final Person person; - - static int _extraClickCount = 0; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(person.name)), - body: ListView( - children: [ - ListTile( - title: Text( - '${person.name} ${family.name} is ${person.age} years old'), - ), - for (final MapEntry entry - in person.details.entries) - ListTile( - title: Text( - '${entry.key.name} - ${entry.value}', - ), - trailing: OutlinedButton( - onPressed: () => PersonDetailsRoute( - family.id, - person.id, - entry.key, - $extra: ++_extraClickCount, - ).go(context), - child: const Text('With extra...'), - ), - onTap: () => PersonDetailsRoute(family.id, person.id, entry.key) - .go(context), - ) - ], - ), - ); + void _onTap(BuildContext context, int index) { + navigationShell.goBranch( + index, + initialLocation: index == navigationShell.currentIndex, + ); + } } -class PersonDetailsPage extends StatelessWidget { - const PersonDetailsPage({ - required this.family, - required this.person, - required this.detailsKey, +/// The details screen for either the A or B screen. +class DetailsScreen extends StatefulWidget { + /// Constructs a [DetailsScreen]. + const DetailsScreen({ + required this.label, + this.param, this.extra, super.key, }); - final Family family; - final Person person; - final PersonDetails detailsKey; - final int? extra; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(person.name)), - body: ListView( - children: [ - ListTile( - title: Text( - '${person.name} ${family.name}: ' - '$detailsKey - ${person.details[detailsKey]}', - ), - ), - if (extra == null) const ListTile(title: Text('No extra click!')), - if (extra != null) - ListTile(title: Text('Extra click count: $extra')), - ], - ), - ); -} - -class FamilyCountScreen extends StatelessWidget { - const FamilyCountScreen({super.key, required this.count}); + /// The label to display in the center of the screen. + final String label; - final int count; + /// Optional param + final String? param; + /// Optional extra object + final Object? extra; @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text('Family Count')), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: Text( - 'There are $count families', - style: Theme.of(context).textTheme.headlineSmall, - ), - ), - ElevatedButton( - onPressed: () => context.pop(count), - child: Text('Pop with return value $count'), - ), - ], - ), - ), - ); + State createState() => DetailsScreenState(); } -class LoginScreen extends StatelessWidget { - const LoginScreen({this.from, super.key}); - final String? from; +/// The state for DetailsScreen +class DetailsScreenState extends State { + int _counter = 0; @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text(App.title)), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - // log a user in, letting all the listeners know - context.read().login('test-user'); + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Details Screen - ${widget.label}'), + ), + body: _build(context), + ); + } - // if there's a deep link, go there - if (from != null) { - context.go(from!); - } - }, - child: const Text('Login'), - ), - ], + Widget _build(BuildContext context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Details for ${widget.label} - Counter: $_counter', + style: Theme.of(context).textTheme.titleLarge), + const Padding(padding: EdgeInsets.all(4)), + TextButton( + onPressed: () { + setState(() { + _counter++; + }); + }, + child: const Text('Increment counter'), ), - ), - ); + const Padding(padding: EdgeInsets.all(8)), + if (widget.param != null) + Text('Parameter: ${widget.param!}', + style: Theme.of(context).textTheme.titleMedium), + const Padding(padding: EdgeInsets.all(8)), + if (widget.extra != null) + Text('Extra: ${widget.extra!}', + style: Theme.of(context).textTheme.titleMedium), + ], + ), + ); + } } diff --git a/packages/go_router_builder/example/lib/main.g.dart b/packages/go_router_builder/example/lib/main.g.dart index 031877e8890..208044c2bac 100644 --- a/packages/go_router_builder/example/lib/main.g.dart +++ b/packages/go_router_builder/example/lib/main.g.dart @@ -9,77 +9,49 @@ part of 'main.dart'; // ************************************************************************** List get $appRoutes => [ - $homeRoute, - $loginRoute, + $myShellRouteData, ]; -RouteBase get $homeRoute => GoRouteData.$route( - path: '/', - factory: $HomeRouteExtension._fromState, - routes: [ - GoRouteData.$route( - path: 'family/:fid', - factory: $FamilyRouteExtension._fromState, +RouteBase get $myShellRouteData => StatefulShellRouteData.$route( + factory: $MyShellRouteDataExtension._fromState, + branches: [ + StatefulShellBranchData.$route( + factory: $BranchADataExtension._fromState, routes: [ GoRouteData.$route( - path: 'person/:pid', - factory: $PersonRouteExtension._fromState, - routes: [ - GoRouteData.$route( - path: 'details/:details', - factory: $PersonDetailsRouteExtension._fromState, - ), - ], + path: '/detailsA', + factory: $DetailsARouteDataExtension._fromState, ), ], ), - GoRouteData.$route( - path: 'family-count/:count', - factory: $FamilyCountRouteExtension._fromState, + StatefulShellBranchData.$route( + factory: $BranchBDataExtension._fromState, + navigatorKey: BranchBData.$navigatorKey, + routes: [ + GoRouteData.$route( + path: '/detailsB', + factory: $DetailsBRouteDataExtension._fromState, + ), + ], ), ], ); -extension $HomeRouteExtension on HomeRoute { - static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); - - String get location => GoRouteData.$location( - '/', - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); +extension $MyShellRouteDataExtension on MyShellRouteData { + static MyShellRouteData _fromState(GoRouterState state) => + const MyShellRouteData(); } -extension $FamilyRouteExtension on FamilyRoute { - static FamilyRoute _fromState(GoRouterState state) => FamilyRoute( - state.pathParameters['fid']!, - ); - - String get location => GoRouteData.$location( - '/family/${Uri.encodeComponent(fid)}', - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); +extension $BranchADataExtension on BranchAData { + static BranchAData _fromState(GoRouterState state) => const BranchAData(); } -extension $PersonRouteExtension on PersonRoute { - static PersonRoute _fromState(GoRouterState state) => PersonRoute( - state.pathParameters['fid']!, - int.parse(state.pathParameters['pid']!), - ); +extension $DetailsARouteDataExtension on DetailsARouteData { + static DetailsARouteData _fromState(GoRouterState state) => + const DetailsARouteData(); String get location => GoRouteData.$location( - '/family/${Uri.encodeComponent(fid)}/person/${Uri.encodeComponent(pid.toString())}', + '/detailsA', ); void go(BuildContext context) => context.go(location); @@ -90,71 +62,16 @@ extension $PersonRouteExtension on PersonRoute { context.pushReplacement(location); } -extension $PersonDetailsRouteExtension on PersonDetailsRoute { - static PersonDetailsRoute _fromState(GoRouterState state) => - PersonDetailsRoute( - state.pathParameters['fid']!, - int.parse(state.pathParameters['pid']!), - _$PersonDetailsEnumMap._$fromName(state.pathParameters['details']!), - $extra: state.extra as int?, - ); - - String get location => GoRouteData.$location( - '/family/${Uri.encodeComponent(fid)}/person/${Uri.encodeComponent(pid.toString())}/details/${Uri.encodeComponent(_$PersonDetailsEnumMap[details]!)}', - ); - - void go(BuildContext context) => context.go(location, extra: $extra); - - Future push(BuildContext context) => - context.push(location, extra: $extra); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); -} - -extension $FamilyCountRouteExtension on FamilyCountRoute { - static FamilyCountRoute _fromState(GoRouterState state) => FamilyCountRoute( - int.parse(state.pathParameters['count']!), - ); - - String get location => GoRouteData.$location( - '/family-count/${Uri.encodeComponent(count.toString())}', - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); +extension $BranchBDataExtension on BranchBData { + static BranchBData _fromState(GoRouterState state) => const BranchBData(); } -const _$PersonDetailsEnumMap = { - PersonDetails.hobbies: 'hobbies', - PersonDetails.favoriteFood: 'favorite-food', - PersonDetails.favoriteSport: 'favorite-sport', -}; - -extension on Map { - T _$fromName(String value) => - entries.singleWhere((element) => element.value == value).key; -} - -RouteBase get $loginRoute => GoRouteData.$route( - path: '/login', - factory: $LoginRouteExtension._fromState, - ); - -extension $LoginRouteExtension on LoginRoute { - static LoginRoute _fromState(GoRouterState state) => LoginRoute( - fromPage: state.queryParameters['from-page'], - ); +extension $DetailsBRouteDataExtension on DetailsBRouteData { + static DetailsBRouteData _fromState(GoRouterState state) => + const DetailsBRouteData(); String get location => GoRouteData.$location( - '/login', - queryParams: { - if (fromPage != null) 'from-page': fromPage, - }, + '/detailsB', ); void go(BuildContext context) => context.go(location); diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart new file mode 100644 index 00000000000..ba0d63d771f --- /dev/null +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart @@ -0,0 +1,153 @@ +// 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. + +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +part 'stateful_shell_route_example.g.dart'; + +void main() => runApp(App()); + +class App extends StatelessWidget { + App({super.key}); + + @override + Widget build(BuildContext context) => MaterialApp.router( + routerConfig: _router, + ); + + final GoRouter _router = GoRouter( + routes: $appRoutes, + initialLocation: '/foo', + ); +} + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: const Text('foo')), + ); +} + +@TypedShellRoute( + routes: >[ + TypedGoRoute(path: '/foo'), + TypedGoRoute(path: '/bar'), + ], +) +class MyShellRouteData extends ShellRouteData { + const MyShellRouteData(); + + @override + Widget builder( + BuildContext context, + GoRouterState state, + Widget navigator, + ) { + return MyShellRouteScreen(child: navigator); + } +} + +class FooRouteData extends GoRouteData { + const FooRouteData(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const FooScreen(); + } +} + +class BarRouteData extends GoRouteData { + const BarRouteData(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const BarScreen(); + } +} + +class MyShellRouteScreen extends StatelessWidget { + const MyShellRouteScreen({required this.child, super.key}); + + final Widget child; + + int getCurrentIndex(BuildContext context) { + final String location = GoRouter.of(context).location; + if (location == '/bar') { + return 1; + } + return 0; + } + + @override + Widget build(BuildContext context) { + final int currentIndex = getCurrentIndex(context); + return Scaffold( + body: child, + bottomNavigationBar: BottomNavigationBar( + currentIndex: currentIndex, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home), + label: 'Foo', + ), + BottomNavigationBarItem( + icon: Icon(Icons.business), + label: 'Bar', + ), + ], + onTap: (int index) { + switch (index) { + case 0: + const FooRouteData().go(context); + break; + case 1: + const BarRouteData().go(context); + break; + } + }, + ), + ); + } +} + +class FooScreen extends StatelessWidget { + const FooScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Text('Foo'); + } +} + +class BarScreen extends StatelessWidget { + const BarScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Text('Bar'); + } +} + +@TypedGoRoute(path: '/login') +class LoginRoute extends GoRouteData { + const LoginRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const LoginScreen(); +} + +class LoginScreen extends StatelessWidget { + const LoginScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Text('Login'); + } +} diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart new file mode 100644 index 00000000000..94e9a5e81ba --- /dev/null +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart @@ -0,0 +1,83 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: always_specify_types, public_member_api_docs + +part of 'stateful_shell_route_example.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $loginRoute, + $myShellRouteData, + ]; + +RouteBase get $loginRoute => GoRouteData.$route( + path: '/login', + factory: $LoginRouteExtension._fromState, + ); + +extension $LoginRouteExtension on LoginRoute { + static LoginRoute _fromState(GoRouterState state) => const LoginRoute(); + + String get location => GoRouteData.$location( + '/login', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); +} + +RouteBase get $myShellRouteData => ShellRouteData.$route( + factory: $MyShellRouteDataExtension._fromState, + routes: [ + GoRouteData.$route( + path: '/foo', + factory: $FooRouteDataExtension._fromState, + ), + GoRouteData.$route( + path: '/bar', + factory: $BarRouteDataExtension._fromState, + ), + ], + ); + +extension $MyShellRouteDataExtension on MyShellRouteData { + static MyShellRouteData _fromState(GoRouterState state) => + const MyShellRouteData(); +} + +extension $FooRouteDataExtension on FooRouteData { + static FooRouteData _fromState(GoRouterState state) => const FooRouteData(); + + String get location => GoRouteData.$location( + '/foo', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); +} + +extension $BarRouteDataExtension on BarRouteData { + static BarRouteData _fromState(GoRouterState state) => const BarRouteData(); + + String get location => GoRouteData.$location( + '/bar', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); +} diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index 766952e4ca9..a7acb727abd 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -8,7 +8,8 @@ environment: dependencies: flutter: sdk: flutter - go_router: ^7.0.0 + go_router: + path: /Users/jinhangyu/Documents/GitHub/packages/packages/go_router provider: 6.0.5 dev_dependencies: diff --git a/packages/go_router_builder/lib/src/go_router_generator.dart b/packages/go_router_builder/lib/src/go_router_generator.dart index 5a1767022db..afba5a786a0 100644 --- a/packages/go_router_builder/lib/src/go_router_generator.dart +++ b/packages/go_router_builder/lib/src/go_router_generator.dart @@ -16,6 +16,8 @@ const String _routeDataUrl = 'package:go_router/src/route_data.dart'; const Map _annotations = { 'TypedGoRoute': 'GoRouteData', 'TypedShellRoute': 'ShellRouteData', + 'TypedStatefulShellBranch': 'StatefulShellBranchData', + 'TypedStatefulShellRoute': 'StatefulShellRouteData', }; /// A [Generator] for classes annotated with a typed go route annotation. diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 693eb4a42cf..089b786f745 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -41,7 +41,7 @@ class RouteConfig { this._routeDataClass, this._parent, this._key, - this._isShellRoute, + this._typeName, ); /// Creates a new [RouteConfig] represented the annotation data in [reader]. @@ -73,12 +73,13 @@ class RouteConfig { // TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to // 5.2+ (when Flutter 3.4+ is on stable). // ignore: deprecated_member_use - final bool isShellRoute = type.element.name == 'TypedShellRoute'; + + final String typeName = type.element.name; String? path; String? name; - if (!isShellRoute) { + if (!typeName.contains('Shell')) { final ConstantReader pathValue = reader.read('path'); if (pathValue.isNull) { throw InvalidGenerationSourceError( @@ -114,9 +115,9 @@ class RouteConfig { parent, _generateNavigatorKeyGetterCode( classElement, - keyName: isShellRoute ? r'$navigatorKey' : r'$parentNavigatorKey', + keyName: typeName.contains('Shell') ? r'$navigatorKey' : r'$parentNavigatorKey', ), - isShellRoute, + typeName, ); value._children.addAll(reader.read('routes').listValue.map((DartObject e) => @@ -131,7 +132,7 @@ class RouteConfig { final InterfaceElement _routeDataClass; final RouteConfig? _parent; final String? _key; - final bool _isShellRoute; + final String _typeName; static String? _generateNavigatorKeyGetterCode( InterfaceElement classElement, { @@ -196,7 +197,7 @@ class RouteConfig { /// Returns `extension` code. String _extensionDefinition() { - if (_isShellRoute) { + if (_typeName.contains('Shell')) { return ''' extension $_extensionName on $_className { static $_className _fromState(GoRouterState state) $_newFromState @@ -340,14 +341,32 @@ RouteBase get $_routeGetterName => ${_routeDefinition()}; final String routesBit = _children.isEmpty ? '' : ''' -routes: [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}], +${_typeName=='TypedStatefulShellRoute'?"branches:":"routes:"} [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}], '''; final String navigatorKeyParameterName = - _isShellRoute ? 'navigatorKey' : 'parentNavigatorKey'; + _typeName.contains('Shell') ? 'navigatorKey' : 'parentNavigatorKey'; final String navigatorKey = _key == null || _key!.isEmpty ? '' : '$navigatorKeyParameterName: $_key,'; - if (_isShellRoute) { + if (_typeName=='TypedStatefulShellRoute') { + return ''' + StatefulShellRouteData.\$route( + factory: $_extensionName._fromState, + $navigatorKey + $routesBit + ) +'''; + } + if (_typeName=='TypedStatefulShellBranch') { + return ''' + StatefulShellBranchData.\$route( + factory: $_extensionName._fromState, + $navigatorKey + $routesBit + ) +'''; + } + if (_typeName=='TypedShellRoute') { return ''' ShellRouteData.\$route( factory: $_extensionName._fromState, diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 9f213c1508d..000ddf2f854 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: dev_dependencies: build_runner: ^2.0.0 - go_router: ^7.0.0 + go_router: + path: /Users/jinhangyu/Documents/GitHub/packages/packages/go_router source_gen_test: ^1.0.0 test: ^1.20.0 From e4fc52669ae6d18bbee08fc61f6b3560ae964c42 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Fri, 16 Jun 2023 13:43:27 -0700 Subject: [PATCH 09/28] Revert "1" This reverts commit 68f99cb4ac66ca445b37bdc8619875f163b739f3. --- .../go_router_builder/example/lib/main.dart | 419 +++++++++++++----- .../go_router_builder/example/lib/main.g.dart | 147 ++++-- .../lib/stateful_shell_route_example.dart | 174 +++++--- .../lib/stateful_shell_route_example.g.dart | 70 +-- 4 files changed, 556 insertions(+), 254 deletions(-) diff --git a/packages/go_router_builder/example/lib/main.dart b/packages/go_router_builder/example/lib/main.dart index 49fa3b3cb0e..84b28c2ae56 100644 --- a/packages/go_router_builder/example/lib/main.dart +++ b/packages/go_router_builder/example/lib/main.dart @@ -4,192 +4,371 @@ // ignore_for_file: public_member_api_docs +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; + +import 'shared/data.dart'; part 'main.g.dart'; -final GlobalKey _sectionANavigatorKey = - GlobalKey(debugLabel: 'sectionANav'); void main() => runApp(App()); class App extends StatelessWidget { App({super.key}); + final LoginInfo loginInfo = LoginInfo(); + static const String title = 'GoRouter Example: Named Routes'; + @override - Widget build(BuildContext context) => MaterialApp.router( - routerConfig: _router, + Widget build(BuildContext context) => ChangeNotifierProvider.value( + value: loginInfo, + child: MaterialApp.router( + routeInformationParser: _router.routeInformationParser, + routerDelegate: _router.routerDelegate, + routeInformationProvider: _router.routeInformationProvider, + title: title, + debugShowCheckedModeBanner: false, + ), ); - final GoRouter _router = GoRouter( + late final GoRouter _router = GoRouter( + debugLogDiagnostics: true, routes: $appRoutes, - initialLocation: '/detailsA', - ); -} -class HomeScreen extends StatelessWidget { - const HomeScreen({super.key}); + // redirect to the login page if the user is not logged in + redirect: (BuildContext context, GoRouterState state) { + final bool loggedIn = loginInfo.loggedIn; - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text('foo')), - ); + // check just the matchedLocation in case there are query parameters + final String loginLoc = const LoginRoute().location; + final bool goingToLogin = state.matchedLocation == loginLoc; + + // the user is not logged in and not headed to /login, they need to login + if (!loggedIn && !goingToLogin) { + return LoginRoute(fromPage: state.matchedLocation).location; + } + + // the user is logged in and headed to /login, no need to login again + if (loggedIn && goingToLogin) { + return const HomeRoute().location; + } + + // no need to redirect at all + return null; + }, + + // changes on the listenable will cause the router to refresh it's route + refreshListenable: loginInfo, + ); } -@TypedStatefulShellRoute( - routes: >[ - TypedStatefulShellBranch( - routes: >[ - TypedGoRoute(path: '/detailsA'), - ], - ), - TypedStatefulShellBranch( - routes: >[ - TypedGoRoute(path: '/detailsB'), +@TypedGoRoute( + path: '/', + routes: >[ + TypedGoRoute( + path: 'family/:fid', + routes: >[ + TypedGoRoute( + path: 'person/:pid', + routes: >[ + TypedGoRoute(path: 'details/:details'), + ], + ), ], ), + TypedGoRoute(path: 'family-count/:count'), ], ) -class MyShellRouteData extends StatefulShellRouteData { - const MyShellRouteData(); +class HomeRoute extends GoRouteData { + const HomeRoute(); @override - Widget builder( - BuildContext context, - GoRouterState state, - StatefulNavigationShell navigationShell, - ) { - return ScaffoldWithNavBar(navigationShell: navigationShell); - } + Widget build(BuildContext context, GoRouterState state) => const HomeScreen(); } -class BranchAData extends StatefulShellBranchData { - const BranchAData(); +@TypedGoRoute( + path: '/login', +) +class LoginRoute extends GoRouteData { + const LoginRoute({this.fromPage}); + + final String? fromPage; + + @override + Widget build(BuildContext context, GoRouterState state) => + LoginScreen(from: fromPage); } -class BranchBData extends StatefulShellBranchData { - const BranchBData(); +class FamilyRoute extends GoRouteData { + const FamilyRoute(this.fid); + + final String fid; - static final GlobalKey $navigatorKey = _sectionANavigatorKey; + @override + Widget build(BuildContext context, GoRouterState state) => + FamilyScreen(family: familyById(fid)); } -class DetailsARouteData extends GoRouteData { - const DetailsARouteData(); +class PersonRoute extends GoRouteData { + const PersonRoute(this.fid, this.pid); + + final String fid; + final int pid; @override Widget build(BuildContext context, GoRouterState state) { - return const DetailsScreen(label: 'A'); + final Family family = familyById(fid); + final Person person = family.person(pid); + return PersonScreen(family: family, person: person); } } -class DetailsBRouteData extends GoRouteData { - const DetailsBRouteData(); +class PersonDetailsRoute extends GoRouteData { + const PersonDetailsRoute(this.fid, this.pid, this.details, {this.$extra}); + + final String fid; + final int pid; + final PersonDetails details; + final int? $extra; @override - Widget build(BuildContext context, GoRouterState state) { - return DetailsScreen( - label: 'B' + Page buildPage(BuildContext context, GoRouterState state) { + final Family family = familyById(fid); + final Person person = family.person(pid); + + return MaterialPage( + fullscreenDialog: true, + key: state.pageKey, + child: PersonDetailsPage( + family: family, + person: person, + detailsKey: details, + extra: $extra, + ), ); } } -/// Builds the "shell" for the app by building a Scaffold with a -/// BottomNavigationBar, where [child] is placed in the body of the Scaffold. -class ScaffoldWithNavBar extends StatelessWidget { - /// Constructs an [ScaffoldWithNavBar]. - const ScaffoldWithNavBar({ - required this.navigationShell, - Key? key, - }) : super(key: key ?? const ValueKey('ScaffoldWithNavBar')); +class FamilyCountRoute extends GoRouteData { + const FamilyCountRoute(this.count); + + final int count; + + @override + Widget build(BuildContext context, GoRouterState state) => FamilyCountScreen( + count: count, + ); +} - /// The navigation shell and container for the branch Navigators. - final StatefulNavigationShell navigationShell; +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); @override Widget build(BuildContext context) { + final LoginInfo info = context.read(); + return Scaffold( - body: navigationShell, - bottomNavigationBar: BottomNavigationBar( - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'), - BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'), + appBar: AppBar( + title: const Text(App.title), + centerTitle: true, + actions: [ + PopupMenuButton( + itemBuilder: (BuildContext context) { + return >[ + PopupMenuItem( + value: '1', + child: const Text('Push w/o return value'), + onTap: () => const PersonRoute('f1', 1).push(context), + ), + PopupMenuItem( + value: '2', + child: const Text('Push w/ return value'), + onTap: () async { + unawaited(FamilyCountRoute(familyData.length) + .push(context) + .then((int? value) { + if (value != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Age was: $value'), + ), + ); + } + })); + }, + ), + PopupMenuItem( + value: '3', + child: Text('Logout: ${info.userName}'), + onTap: () => info.logout(), + ), + ]; + }, + ), + ], + ), + body: ListView( + children: [ + for (final Family f in familyData) + ListTile( + title: Text(f.name), + onTap: () => FamilyRoute(f.id).go(context), + ) ], - currentIndex: navigationShell.currentIndex, - onTap: (int index) => _onTap(context, index), ), ); } +} - void _onTap(BuildContext context, int index) { - navigationShell.goBranch( - index, - initialLocation: index == navigationShell.currentIndex, - ); - } +class FamilyScreen extends StatelessWidget { + const FamilyScreen({required this.family, super.key}); + final Family family; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: Text(family.name)), + body: ListView( + children: [ + for (final Person p in family.people) + ListTile( + title: Text(p.name), + onTap: () => PersonRoute(family.id, p.id).go(context), + ), + ], + ), + ); +} + +class PersonScreen extends StatelessWidget { + const PersonScreen({required this.family, required this.person, super.key}); + + final Family family; + final Person person; + + static int _extraClickCount = 0; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: Text(person.name)), + body: ListView( + children: [ + ListTile( + title: Text( + '${person.name} ${family.name} is ${person.age} years old'), + ), + for (final MapEntry entry + in person.details.entries) + ListTile( + title: Text( + '${entry.key.name} - ${entry.value}', + ), + trailing: OutlinedButton( + onPressed: () => PersonDetailsRoute( + family.id, + person.id, + entry.key, + $extra: ++_extraClickCount, + ).go(context), + child: const Text('With extra...'), + ), + onTap: () => PersonDetailsRoute(family.id, person.id, entry.key) + .go(context), + ) + ], + ), + ); } -/// The details screen for either the A or B screen. -class DetailsScreen extends StatefulWidget { - /// Constructs a [DetailsScreen]. - const DetailsScreen({ - required this.label, - this.param, +class PersonDetailsPage extends StatelessWidget { + const PersonDetailsPage({ + required this.family, + required this.person, + required this.detailsKey, this.extra, super.key, }); - /// The label to display in the center of the screen. - final String label; + final Family family; + final Person person; + final PersonDetails detailsKey; + final int? extra; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: Text(person.name)), + body: ListView( + children: [ + ListTile( + title: Text( + '${person.name} ${family.name}: ' + '$detailsKey - ${person.details[detailsKey]}', + ), + ), + if (extra == null) const ListTile(title: Text('No extra click!')), + if (extra != null) + ListTile(title: Text('Extra click count: $extra')), + ], + ), + ); +} + +class FamilyCountScreen extends StatelessWidget { + const FamilyCountScreen({super.key, required this.count}); - /// Optional param - final String? param; + final int count; - /// Optional extra object - final Object? extra; @override - State createState() => DetailsScreenState(); + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: const Text('Family Count')), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Text( + 'There are $count families', + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + ElevatedButton( + onPressed: () => context.pop(count), + child: Text('Pop with return value $count'), + ), + ], + ), + ), + ); } -/// The state for DetailsScreen -class DetailsScreenState extends State { - int _counter = 0; +class LoginScreen extends StatelessWidget { + const LoginScreen({this.from, super.key}); + final String? from; @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Details Screen - ${widget.label}'), - ), - body: _build(context), - ); - } + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: const Text(App.title)), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + // log a user in, letting all the listeners know + context.read().login('test-user'); - Widget _build(BuildContext context) { - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text('Details for ${widget.label} - Counter: $_counter', - style: Theme.of(context).textTheme.titleLarge), - const Padding(padding: EdgeInsets.all(4)), - TextButton( - onPressed: () { - setState(() { - _counter++; - }); - }, - child: const Text('Increment counter'), + // if there's a deep link, go there + if (from != null) { + context.go(from!); + } + }, + child: const Text('Login'), + ), + ], ), - const Padding(padding: EdgeInsets.all(8)), - if (widget.param != null) - Text('Parameter: ${widget.param!}', - style: Theme.of(context).textTheme.titleMedium), - const Padding(padding: EdgeInsets.all(8)), - if (widget.extra != null) - Text('Extra: ${widget.extra!}', - style: Theme.of(context).textTheme.titleMedium), - ], - ), - ); - } + ), + ); } diff --git a/packages/go_router_builder/example/lib/main.g.dart b/packages/go_router_builder/example/lib/main.g.dart index 208044c2bac..031877e8890 100644 --- a/packages/go_router_builder/example/lib/main.g.dart +++ b/packages/go_router_builder/example/lib/main.g.dart @@ -9,49 +9,77 @@ part of 'main.dart'; // ************************************************************************** List get $appRoutes => [ - $myShellRouteData, + $homeRoute, + $loginRoute, ]; -RouteBase get $myShellRouteData => StatefulShellRouteData.$route( - factory: $MyShellRouteDataExtension._fromState, - branches: [ - StatefulShellBranchData.$route( - factory: $BranchADataExtension._fromState, +RouteBase get $homeRoute => GoRouteData.$route( + path: '/', + factory: $HomeRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: 'family/:fid', + factory: $FamilyRouteExtension._fromState, routes: [ GoRouteData.$route( - path: '/detailsA', - factory: $DetailsARouteDataExtension._fromState, + path: 'person/:pid', + factory: $PersonRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: 'details/:details', + factory: $PersonDetailsRouteExtension._fromState, + ), + ], ), ], ), - StatefulShellBranchData.$route( - factory: $BranchBDataExtension._fromState, - navigatorKey: BranchBData.$navigatorKey, - routes: [ - GoRouteData.$route( - path: '/detailsB', - factory: $DetailsBRouteDataExtension._fromState, - ), - ], + GoRouteData.$route( + path: 'family-count/:count', + factory: $FamilyCountRouteExtension._fromState, ), ], ); -extension $MyShellRouteDataExtension on MyShellRouteData { - static MyShellRouteData _fromState(GoRouterState state) => - const MyShellRouteData(); +extension $HomeRouteExtension on HomeRoute { + static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); + + String get location => GoRouteData.$location( + '/', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); } -extension $BranchADataExtension on BranchAData { - static BranchAData _fromState(GoRouterState state) => const BranchAData(); +extension $FamilyRouteExtension on FamilyRoute { + static FamilyRoute _fromState(GoRouterState state) => FamilyRoute( + state.pathParameters['fid']!, + ); + + String get location => GoRouteData.$location( + '/family/${Uri.encodeComponent(fid)}', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); } -extension $DetailsARouteDataExtension on DetailsARouteData { - static DetailsARouteData _fromState(GoRouterState state) => - const DetailsARouteData(); +extension $PersonRouteExtension on PersonRoute { + static PersonRoute _fromState(GoRouterState state) => PersonRoute( + state.pathParameters['fid']!, + int.parse(state.pathParameters['pid']!), + ); String get location => GoRouteData.$location( - '/detailsA', + '/family/${Uri.encodeComponent(fid)}/person/${Uri.encodeComponent(pid.toString())}', ); void go(BuildContext context) => context.go(location); @@ -62,16 +90,71 @@ extension $DetailsARouteDataExtension on DetailsARouteData { context.pushReplacement(location); } -extension $BranchBDataExtension on BranchBData { - static BranchBData _fromState(GoRouterState state) => const BranchBData(); +extension $PersonDetailsRouteExtension on PersonDetailsRoute { + static PersonDetailsRoute _fromState(GoRouterState state) => + PersonDetailsRoute( + state.pathParameters['fid']!, + int.parse(state.pathParameters['pid']!), + _$PersonDetailsEnumMap._$fromName(state.pathParameters['details']!), + $extra: state.extra as int?, + ); + + String get location => GoRouteData.$location( + '/family/${Uri.encodeComponent(fid)}/person/${Uri.encodeComponent(pid.toString())}/details/${Uri.encodeComponent(_$PersonDetailsEnumMap[details]!)}', + ); + + void go(BuildContext context) => context.go(location, extra: $extra); + + Future push(BuildContext context) => + context.push(location, extra: $extra); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: $extra); +} + +extension $FamilyCountRouteExtension on FamilyCountRoute { + static FamilyCountRoute _fromState(GoRouterState state) => FamilyCountRoute( + int.parse(state.pathParameters['count']!), + ); + + String get location => GoRouteData.$location( + '/family-count/${Uri.encodeComponent(count.toString())}', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); } -extension $DetailsBRouteDataExtension on DetailsBRouteData { - static DetailsBRouteData _fromState(GoRouterState state) => - const DetailsBRouteData(); +const _$PersonDetailsEnumMap = { + PersonDetails.hobbies: 'hobbies', + PersonDetails.favoriteFood: 'favorite-food', + PersonDetails.favoriteSport: 'favorite-sport', +}; + +extension on Map { + T _$fromName(String value) => + entries.singleWhere((element) => element.value == value).key; +} + +RouteBase get $loginRoute => GoRouteData.$route( + path: '/login', + factory: $LoginRouteExtension._fromState, + ); + +extension $LoginRouteExtension on LoginRoute { + static LoginRoute _fromState(GoRouterState state) => LoginRoute( + fromPage: state.queryParameters['from-page'], + ); String get location => GoRouteData.$location( - '/detailsB', + '/login', + queryParams: { + if (fromPage != null) 'from-page': fromPage, + }, ); void go(BuildContext context) => context.go(location); diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart index ba0d63d771f..564bc41903d 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart @@ -9,6 +9,8 @@ import 'package:go_router/go_router.dart'; part 'stateful_shell_route_example.g.dart'; +final GlobalKey _sectionANavigatorKey = + GlobalKey(debugLabel: 'sectionANav'); void main() => runApp(App()); class App extends StatelessWidget { @@ -21,7 +23,7 @@ class App extends StatelessWidget { final GoRouter _router = GoRouter( routes: $appRoutes, - initialLocation: '/foo', + initialLocation: '/detailsA', ); } @@ -34,120 +36,158 @@ class HomeScreen extends StatelessWidget { ); } -@TypedShellRoute( +@TypedStatefulShellRoute( routes: >[ - TypedGoRoute(path: '/foo'), - TypedGoRoute(path: '/bar'), + TypedStatefulShellBranch( + routes: >[ + TypedGoRoute(path: '/detailsA'), + ], + ), + TypedStatefulShellBranch( + routes: >[ + TypedGoRoute(path: '/detailsB'), + ], + ), ], ) -class MyShellRouteData extends ShellRouteData { +class MyShellRouteData extends StatefulShellRouteData { const MyShellRouteData(); @override Widget builder( BuildContext context, GoRouterState state, - Widget navigator, + StatefulNavigationShell navigationShell, ) { - return MyShellRouteScreen(child: navigator); + return ScaffoldWithNavBar(navigationShell: navigationShell); } } -class FooRouteData extends GoRouteData { - const FooRouteData(); +class BranchAData extends StatefulShellBranchData { + const BranchAData(); +} + +class BranchBData extends StatefulShellBranchData { + const BranchBData(); + + static final GlobalKey $navigatorKey = _sectionANavigatorKey; +} + +class DetailsARouteData extends GoRouteData { + const DetailsARouteData(); @override Widget build(BuildContext context, GoRouterState state) { - return const FooScreen(); + return const DetailsScreen(label: 'A'); } } -class BarRouteData extends GoRouteData { - const BarRouteData(); +class DetailsBRouteData extends GoRouteData { + const DetailsBRouteData(); @override Widget build(BuildContext context, GoRouterState state) { - return const BarScreen(); + return const DetailsScreen(label: 'B'); } } -class MyShellRouteScreen extends StatelessWidget { - const MyShellRouteScreen({required this.child, super.key}); - - final Widget child; +/// Builds the "shell" for the app by building a Scaffold with a +/// BottomNavigationBar, where [child] is placed in the body of the Scaffold. +class ScaffoldWithNavBar extends StatelessWidget { + /// Constructs an [ScaffoldWithNavBar]. + const ScaffoldWithNavBar({ + required this.navigationShell, + Key? key, + }) : super(key: key ?? const ValueKey('ScaffoldWithNavBar')); - int getCurrentIndex(BuildContext context) { - final String location = GoRouter.of(context).location; - if (location == '/bar') { - return 1; - } - return 0; - } + /// The navigation shell and container for the branch Navigators. + final StatefulNavigationShell navigationShell; @override Widget build(BuildContext context) { - final int currentIndex = getCurrentIndex(context); return Scaffold( - body: child, + body: navigationShell, bottomNavigationBar: BottomNavigationBar( - currentIndex: currentIndex, items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.home), - label: 'Foo', - ), - BottomNavigationBarItem( - icon: Icon(Icons.business), - label: 'Bar', - ), + BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'), + BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'), ], - onTap: (int index) { - switch (index) { - case 0: - const FooRouteData().go(context); - break; - case 1: - const BarRouteData().go(context); - break; - } - }, + currentIndex: navigationShell.currentIndex, + onTap: (int index) => _onTap(context, index), ), ); } -} - -class FooScreen extends StatelessWidget { - const FooScreen({super.key}); - @override - Widget build(BuildContext context) { - return const Text('Foo'); + void _onTap(BuildContext context, int index) { + navigationShell.goBranch( + index, + initialLocation: index == navigationShell.currentIndex, + ); } } -class BarScreen extends StatelessWidget { - const BarScreen({super.key}); +/// The details screen for either the A or B screen. +class DetailsScreen extends StatefulWidget { + /// Constructs a [DetailsScreen]. + const DetailsScreen({ + required this.label, + this.param, + this.extra, + super.key, + }); - @override - Widget build(BuildContext context) { - return const Text('Bar'); - } -} + /// The label to display in the center of the screen. + final String label; -@TypedGoRoute(path: '/login') -class LoginRoute extends GoRouteData { - const LoginRoute(); + /// Optional param + final String? param; + /// Optional extra object + final Object? extra; @override - Widget build(BuildContext context, GoRouterState state) => - const LoginScreen(); + State createState() => DetailsScreenState(); } -class LoginScreen extends StatelessWidget { - const LoginScreen({super.key}); +/// The state for DetailsScreen +class DetailsScreenState extends State { + int _counter = 0; @override Widget build(BuildContext context) { - return const Text('Login'); + return Scaffold( + appBar: AppBar( + title: Text('Details Screen - ${widget.label}'), + ), + body: _build(context), + ); + } + + Widget _build(BuildContext context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Details for ${widget.label} - Counter: $_counter', + style: Theme.of(context).textTheme.titleLarge), + const Padding(padding: EdgeInsets.all(4)), + TextButton( + onPressed: () { + setState(() { + _counter++; + }); + }, + child: const Text('Increment counter'), + ), + const Padding(padding: EdgeInsets.all(8)), + if (widget.param != null) + Text('Parameter: ${widget.param!}', + style: Theme.of(context).textTheme.titleMedium), + const Padding(padding: EdgeInsets.all(8)), + if (widget.extra != null) + Text('Extra: ${widget.extra!}', + style: Theme.of(context).textTheme.titleMedium), + ], + ), + ); } } diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart index 94e9a5e81ba..87e1e262a3d 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart @@ -9,40 +9,30 @@ part of 'stateful_shell_route_example.dart'; // ************************************************************************** List get $appRoutes => [ - $loginRoute, $myShellRouteData, ]; -RouteBase get $loginRoute => GoRouteData.$route( - path: '/login', - factory: $LoginRouteExtension._fromState, - ); - -extension $LoginRouteExtension on LoginRoute { - static LoginRoute _fromState(GoRouterState state) => const LoginRoute(); - - String get location => GoRouteData.$location( - '/login', - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); -} - -RouteBase get $myShellRouteData => ShellRouteData.$route( +RouteBase get $myShellRouteData => StatefulShellRouteData.$route( factory: $MyShellRouteDataExtension._fromState, - routes: [ - GoRouteData.$route( - path: '/foo', - factory: $FooRouteDataExtension._fromState, + branches: [ + StatefulShellBranchData.$route( + factory: $BranchADataExtension._fromState, + routes: [ + GoRouteData.$route( + path: '/detailsA', + factory: $DetailsARouteDataExtension._fromState, + ), + ], ), - GoRouteData.$route( - path: '/bar', - factory: $BarRouteDataExtension._fromState, + StatefulShellBranchData.$route( + factory: $BranchBDataExtension._fromState, + navigatorKey: BranchBData.$navigatorKey, + routes: [ + GoRouteData.$route( + path: '/detailsB', + factory: $DetailsBRouteDataExtension._fromState, + ), + ], ), ], ); @@ -52,11 +42,16 @@ extension $MyShellRouteDataExtension on MyShellRouteData { const MyShellRouteData(); } -extension $FooRouteDataExtension on FooRouteData { - static FooRouteData _fromState(GoRouterState state) => const FooRouteData(); +extension $BranchADataExtension on BranchAData { + static BranchAData _fromState(GoRouterState state) => const BranchAData(); +} + +extension $DetailsARouteDataExtension on DetailsARouteData { + static DetailsARouteData _fromState(GoRouterState state) => + const DetailsARouteData(); String get location => GoRouteData.$location( - '/foo', + '/detailsA', ); void go(BuildContext context) => context.go(location); @@ -67,11 +62,16 @@ extension $FooRouteDataExtension on FooRouteData { context.pushReplacement(location); } -extension $BarRouteDataExtension on BarRouteData { - static BarRouteData _fromState(GoRouterState state) => const BarRouteData(); +extension $BranchBDataExtension on BranchBData { + static BranchBData _fromState(GoRouterState state) => const BranchBData(); +} + +extension $DetailsBRouteDataExtension on DetailsBRouteData { + static DetailsBRouteData _fromState(GoRouterState state) => + const DetailsBRouteData(); String get location => GoRouteData.$location( - '/bar', + '/detailsB', ); void go(BuildContext context) => context.go(location); From d328021ccd565686b5438801ed32dfdc0cd01bea Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:50:17 -0700 Subject: [PATCH 10/28] update --- .../lib/stateful_shell_route_example.dart | 2 +- .../lib/stateful_shell_route_example.g.dart | 4 +-- .../lib/src/route_config.dart | 26 ++++++++++++------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart index 564bc41903d..c0361c79141 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart @@ -37,7 +37,7 @@ class HomeScreen extends StatelessWidget { } @TypedStatefulShellRoute( - routes: >[ + branches: >[ TypedStatefulShellBranch( routes: >[ TypedGoRoute(path: '/detailsA'), diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart index 87e1e262a3d..ac358c53ec8 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart @@ -15,7 +15,7 @@ List get $appRoutes => [ RouteBase get $myShellRouteData => StatefulShellRouteData.$route( factory: $MyShellRouteDataExtension._fromState, branches: [ - StatefulShellBranchData.$route( + StatefulShellBranchData.$branch( factory: $BranchADataExtension._fromState, routes: [ GoRouteData.$route( @@ -24,7 +24,7 @@ RouteBase get $myShellRouteData => StatefulShellRouteData.$route( ), ], ), - StatefulShellBranchData.$route( + StatefulShellBranchData.$branch( factory: $BranchBDataExtension._fromState, navigatorKey: BranchBData.$navigatorKey, routes: [ diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 089b786f745..c0381789a17 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -115,13 +115,21 @@ class RouteConfig { parent, _generateNavigatorKeyGetterCode( classElement, - keyName: typeName.contains('Shell') ? r'$navigatorKey' : r'$parentNavigatorKey', + keyName: typeName.contains('Shell') + ? r'$navigatorKey' + : r'$parentNavigatorKey', ), typeName, ); - - value._children.addAll(reader.read('routes').listValue.map((DartObject e) => - RouteConfig._fromAnnotation(ConstantReader(e), element, value))); + if (typeName == 'TypedStatefulShellRoute') { + value._children.addAll(reader.read('branches').listValue.map( + (DartObject e) => + RouteConfig._fromAnnotation(ConstantReader(e), element, value))); + } else { + value._children.addAll(reader.read('routes').listValue.map( + (DartObject e) => + RouteConfig._fromAnnotation(ConstantReader(e), element, value))); + } return value; } @@ -341,14 +349,14 @@ RouteBase get $_routeGetterName => ${_routeDefinition()}; final String routesBit = _children.isEmpty ? '' : ''' -${_typeName=='TypedStatefulShellRoute'?"branches:":"routes:"} [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}], +${_typeName == 'TypedStatefulShellRoute' ? "branches:" : "routes:"} [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}], '''; final String navigatorKeyParameterName = _typeName.contains('Shell') ? 'navigatorKey' : 'parentNavigatorKey'; final String navigatorKey = _key == null || _key!.isEmpty ? '' : '$navigatorKeyParameterName: $_key,'; - if (_typeName=='TypedStatefulShellRoute') { + if (_typeName == 'TypedStatefulShellRoute') { return ''' StatefulShellRouteData.\$route( factory: $_extensionName._fromState, @@ -357,16 +365,16 @@ ${_typeName=='TypedStatefulShellRoute'?"branches:":"routes:"} [${_children.map(( ) '''; } - if (_typeName=='TypedStatefulShellBranch') { + if (_typeName == 'TypedStatefulShellBranch') { return ''' - StatefulShellBranchData.\$route( + StatefulShellBranchData.\$branch( factory: $_extensionName._fromState, $navigatorKey $routesBit ) '''; } - if (_typeName=='TypedShellRoute') { + if (_typeName == 'TypedShellRoute') { return ''' ShellRouteData.\$route( factory: $_extensionName._fromState, From bc19f53cd7a6ee0463974189296bbb374068029f Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:37:40 -0700 Subject: [PATCH 11/28] update tests --- .../lib/stateful_shell_route_example.g.dart | 2 -- .../test/stateful_shell_route_test.dart | 25 +++++++++++++++++++ .../lib/src/route_config.dart | 1 - 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 packages/go_router_builder/example/test/stateful_shell_route_test.dart diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart index ac358c53ec8..55e8bf695f4 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart @@ -16,7 +16,6 @@ RouteBase get $myShellRouteData => StatefulShellRouteData.$route( factory: $MyShellRouteDataExtension._fromState, branches: [ StatefulShellBranchData.$branch( - factory: $BranchADataExtension._fromState, routes: [ GoRouteData.$route( path: '/detailsA', @@ -25,7 +24,6 @@ RouteBase get $myShellRouteData => StatefulShellRouteData.$route( ], ), StatefulShellBranchData.$branch( - factory: $BranchBDataExtension._fromState, navigatorKey: BranchBData.$navigatorKey, routes: [ GoRouteData.$route( diff --git a/packages/go_router_builder/example/test/stateful_shell_route_test.dart b/packages/go_router_builder/example/test/stateful_shell_route_test.dart new file mode 100644 index 00000000000..de997887415 --- /dev/null +++ b/packages/go_router_builder/example/test/stateful_shell_route_test.dart @@ -0,0 +1,25 @@ +// 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_builder_example/stateful_shell_route_example.dart'; + +void main() { + testWidgets('Navigate between section A and section B', (WidgetTester tester) async { + await tester.pumpWidget(App()); + expect(find.text('Details for A - Counter: 0'), findsOneWidget); + + await tester.tap(find.text('Increment counter')); + await tester.pumpAndSettle(); + expect(find.text('Details for A - Counter: 1'), findsOneWidget); + + await tester.tap(find.text('Section B')); + await tester.pumpAndSettle(); + expect(find.text('Details for B - Counter: 0'), findsOneWidget); + + await tester.tap(find.text('Section A')); + await tester.pumpAndSettle(); + expect(find.text('Details for A - Counter: 1'), findsOneWidget); + }); +} diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index c0381789a17..6c170ed1c5f 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -368,7 +368,6 @@ ${_typeName == 'TypedStatefulShellRoute' ? "branches:" : "routes:"} [${_children if (_typeName == 'TypedStatefulShellBranch') { return ''' StatefulShellBranchData.\$branch( - factory: $_extensionName._fromState, $navigatorKey $routesBit ) From 6a09020524313000e29affbdaf45e6ac61fb1229 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:37:29 -0700 Subject: [PATCH 12/28] update --- packages/go_router_builder/CHANGELOG.md | 4 ++++ .../example/lib/stateful_shell_route_example.g.dart | 4 ++++ packages/go_router_builder/example/pubspec.yaml | 3 +-- packages/go_router_builder/pubspec.yaml | 3 +-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index ac41ce6cdad..3204f74d415 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.0 + +* Adds Support for StatefulShellRoute + ## 2.2.0 * Adds replace methods to the generated routes. diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart index 55e8bf695f4..f16b0411b7c 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart @@ -58,6 +58,8 @@ extension $DetailsARouteDataExtension on DetailsARouteData { void pushReplacement(BuildContext context) => context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); } extension $BranchBDataExtension on BranchBData { @@ -78,4 +80,6 @@ extension $DetailsBRouteDataExtension on DetailsBRouteData { void pushReplacement(BuildContext context) => context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index a7acb727abd..4f83343fe56 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -8,8 +8,7 @@ environment: dependencies: flutter: sdk: flutter - go_router: - path: /Users/jinhangyu/Documents/GitHub/packages/packages/go_router + go_router: ^9.0.3 provider: 6.0.5 dev_dependencies: diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index c6a78b7bebb..b55fb64d991 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -23,7 +23,6 @@ dependencies: dev_dependencies: build_runner: ^2.0.0 - go_router: - path: /Users/jinhangyu/Documents/GitHub/packages/packages/go_router + go_router: ^9.0.3 source_gen_test: ^1.0.0 test: ^1.20.0 From bb6064b6e8b73282e46ea252c7371c246f629fd6 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Sun, 9 Jul 2023 18:02:14 -0700 Subject: [PATCH 13/28] bump version --- packages/go_router_builder/CHANGELOG.md | 2 ++ packages/go_router_builder/README.md | 4 +-- .../example/lib/all_types.dart | 26 +++++++++---------- .../example/lib/shell_route_example.dart | 2 +- .../lib/shell_route_with_keys_example.dart | 2 +- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index 3204f74d415..f32935026a5 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,6 +1,8 @@ ## 2.3.0 * Adds Support for StatefulShellRoute +* Updates the documentation to go_router v9.0.3 +* Bumps go_router version in example folder to v9.0.3 ## 2.2.0 diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index 0eff1f07c6a..1bd93ea1916 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -8,12 +8,12 @@ To use `go_router_builder`, you need to have the following dependencies in ```yaml dependencies: # ...along with your other dependencies - go_router: ^7.0.0 + go_router: ^9.0.3 dev_dependencies: # ...along with your other dev-dependencies build_runner: ^2.0.0 - go_router_builder: ^2.0.0 + go_router_builder: ^2.3.0 ``` ### Source code diff --git a/packages/go_router_builder/example/lib/all_types.dart b/packages/go_router_builder/example/lib/all_types.dart index ce86905afc0..9849b7ae523 100644 --- a/packages/go_router_builder/example/lib/all_types.dart +++ b/packages/go_router_builder/example/lib/all_types.dart @@ -58,7 +58,7 @@ class BigIntRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('BigIntRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -84,7 +84,7 @@ class BoolRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('BoolRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -107,7 +107,7 @@ class DateTimeRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('DateTimeRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -133,7 +133,7 @@ class DoubleRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('DoubleRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -159,7 +159,7 @@ class IntRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('IntRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -185,7 +185,7 @@ class NumRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('NumRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -212,7 +212,7 @@ class EnumRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('EnumRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -239,7 +239,7 @@ class EnhancedEnumRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('EnhancedEnumRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -265,7 +265,7 @@ class StringRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('StringRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -288,7 +288,7 @@ class UriRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('UriRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -361,7 +361,7 @@ class IterableRoute extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('IterableRoute'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -430,7 +430,7 @@ class IterableRouteWithDefaultValues extends GoRouteData { Widget drawerTile(BuildContext context) => ListTile( title: const Text('IterableRouteWithDefaultValues'), onTap: () => go(context), - selected: GoRouter.of(context).location == location, + selected: GoRouterState.of(context).location == location, ); } @@ -536,7 +536,7 @@ class BasePage extends StatelessWidget { Text( 'Query param with default value: $queryParamWithDefaultValue', ), - SelectableText(GoRouter.of(context).location), + SelectableText(GoRouterState.of(context).location), ], ), ), diff --git a/packages/go_router_builder/example/lib/shell_route_example.dart b/packages/go_router_builder/example/lib/shell_route_example.dart index 2662ba83d37..979a31e728a 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.dart @@ -77,7 +77,7 @@ class MyShellRouteScreen extends StatelessWidget { final Widget child; int getCurrentIndex(BuildContext context) { - final String location = GoRouter.of(context).location; + final String location = GoRouterState.of(context).location; if (location == '/bar') { return 1; } diff --git a/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart b/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart index bc6521e05fe..b97ed47fc90 100644 --- a/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart +++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart @@ -57,7 +57,7 @@ class MyShellRouteScreen extends StatelessWidget { final Widget child; int getCurrentIndex(BuildContext context) { - final String location = GoRouter.of(context).location; + final String location = GoRouterState.of(context).location; if (location.startsWith('/users')) { return 1; } From 441eb7861e8bfc47c057b30a18723c36f40d47d3 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Sun, 9 Jul 2023 18:25:26 -0700 Subject: [PATCH 14/28] Update route_config.dart --- packages/go_router_builder/lib/src/route_config.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index f9513ec9b75..84b833da81e 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -73,7 +73,6 @@ class RouteConfig { // TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to // 5.2+ (when Flutter 3.4+ is on stable). // ignore: deprecated_member_use - final String typeName = type.element.name; String? path; From 8b4e2de682a9991c8a6fb8c8a0268ed4cb028e51 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Sun, 9 Jul 2023 18:51:09 -0700 Subject: [PATCH 15/28] Update stateful_shell_route_test.dart --- .../example/test/stateful_shell_route_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/go_router_builder/example/test/stateful_shell_route_test.dart b/packages/go_router_builder/example/test/stateful_shell_route_test.dart index de997887415..f885e54c531 100644 --- a/packages/go_router_builder/example/test/stateful_shell_route_test.dart +++ b/packages/go_router_builder/example/test/stateful_shell_route_test.dart @@ -6,7 +6,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:go_router_builder_example/stateful_shell_route_example.dart'; void main() { - testWidgets('Navigate between section A and section B', (WidgetTester tester) async { + testWidgets('Navigate between section A and section B', + (WidgetTester tester) async { await tester.pumpWidget(App()); expect(find.text('Details for A - Counter: 0'), findsOneWidget); From 13865ac2a1a43980603942af2bb079b43c6afb9e Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:22:01 -0700 Subject: [PATCH 16/28] update route_config --- .../lib/stateful_shell_route_example.g.dart | 8 - .../lib/src/route_config.dart | 152 ++++++++++++------ 2 files changed, 101 insertions(+), 59 deletions(-) diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart index f16b0411b7c..baf28b22b65 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart @@ -40,10 +40,6 @@ extension $MyShellRouteDataExtension on MyShellRouteData { const MyShellRouteData(); } -extension $BranchADataExtension on BranchAData { - static BranchAData _fromState(GoRouterState state) => const BranchAData(); -} - extension $DetailsARouteDataExtension on DetailsARouteData { static DetailsARouteData _fromState(GoRouterState state) => const DetailsARouteData(); @@ -62,10 +58,6 @@ extension $DetailsARouteDataExtension on DetailsARouteData { void replace(BuildContext context) => context.replace(location); } -extension $BranchBDataExtension on BranchBData { - static BranchBData _fromState(GoRouterState state) => const BranchBData(); -} - extension $DetailsBRouteDataExtension on DetailsBRouteData { static DetailsBRouteData _fromState(GoRouterState state) => const DetailsBRouteData(); diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 78043a2bc34..85ac70740a7 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -63,7 +63,7 @@ extension $_extensionName on $_className { String get routeDataClassName => 'ShellRouteData'; } -/// The configuration to generate class declarations for a ShellRouteData. +/// The configuration to generate class declarations for a StatefulShellRouteData. class StatefulShellRouteConfig extends RouteBaseConfig { StatefulShellRouteConfig._({ required this.navigatorKey, @@ -92,6 +92,29 @@ extension $_extensionName on $_className { String get routeDataClassName => 'StatefulShellRouteData'; } +/// The configuration to generate class declarations for a StatefulShellBranchData. +class StatefulShellBranchConfig extends RouteBaseConfig { + StatefulShellBranchConfig._({ + required this.navigatorKey, + required super.routeDataClass, + required super.parent, + required super.parentNavigatorKey, + }) : super._(); + + /// The command for calling the navigator key getter from the ShellRouteData. + final String? navigatorKey; + + @override + Iterable classDeclarations() => []; + + @override + String get routeConstructorParameters => + navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'; + + @override + String get routeDataClassName => 'StatefulShellBranchData'; +} + /// The configuration to generate class declarations for a GoRouteData. class GoRouteConfig extends RouteBaseConfig { GoRouteConfig._({ @@ -385,58 +408,75 @@ abstract class RouteBaseConfig { final InterfaceElement classElement = typeParamType.element; final RouteBaseConfig value; - if (typeName =='TypedShellRoute') { - value = ShellRouteConfig._( - routeDataClass: classElement, - parent: parent, - navigatorKey: _generateNavigatorKeyGetterCode( - classElement, - keyName: r'$navigatorKey', - ), - parentNavigatorKey: _generateNavigatorKeyGetterCode( - classElement, - keyName: r'$parentNavigatorKey', - ), - ); - } - else if(typeName=='TypedStatefulShellRoute'){ - value = StatefulShellRouteConfig._( - routeDataClass: classElement, - parent: parent, - navigatorKey: _generateNavigatorKeyGetterCode( - classElement, - keyName: r'$navigatorKey', - ), - parentNavigatorKey: _generateNavigatorKeyGetterCode( - classElement, - keyName: r'$parentNavigatorKey', - ), - ); - } - else { - final ConstantReader pathValue = reader.read('path'); - if (pathValue.isNull) { - throw InvalidGenerationSourceError( - 'Missing `path` value on annotation.', - element: element, + switch (typeName) { + case 'TypedShellRoute': + value = ShellRouteConfig._( + routeDataClass: classElement, + parent: parent, + navigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$navigatorKey', + ), + parentNavigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$parentNavigatorKey', + ), ); - } + break; + case 'TypedStatefulShellRoute': + value = StatefulShellRouteConfig._( + routeDataClass: classElement, + parent: parent, + navigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$navigatorKey', + ), + parentNavigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$parentNavigatorKey', + ), + ); + break; + case 'TypedStatefulShellBranch': + value = StatefulShellBranchConfig._( + routeDataClass: classElement, + parent: parent, + navigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$navigatorKey', + ), + parentNavigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$parentNavigatorKey', + ), + ); + break; + default: + final ConstantReader pathValue = reader.read('path'); + if (pathValue.isNull) { + throw InvalidGenerationSourceError( + 'Missing `path` value on annotation.', + element: element, + ); + } - final ConstantReader nameValue = reader.read('name'); - value = GoRouteConfig._( - path: pathValue.stringValue, - name: nameValue.isNull ? null : nameValue.stringValue, - routeDataClass: classElement, - parent: parent, - parentNavigatorKey: _generateNavigatorKeyGetterCode( - classElement, - keyName: r'$parentNavigatorKey', - ), - ); + final ConstantReader nameValue = reader.read('name'); + value = GoRouteConfig._( + path: pathValue.stringValue, + name: nameValue.isNull ? null : nameValue.stringValue, + routeDataClass: classElement, + parent: parent, + parentNavigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$parentNavigatorKey', + ), + ); } - value._children.addAll(reader.read('routes').listValue.map( - (DartObject e) => RouteBaseConfig._fromAnnotation( + value._children.addAll(reader + .read(typeName == 'TypedStatefulShellRoute' ? 'branches' : 'routes') + .listValue + .map((DartObject e) => RouteBaseConfig._fromAnnotation( ConstantReader(e), element, value))); return value; @@ -537,11 +577,21 @@ RouteBase get $_routeGetterName => ${_invokesRouteConstructor()}; final String routesBit = _children.isEmpty ? '' : ''' -routes: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}], +${routeDataClassName == 'StatefulShellRouteData' ? 'branches' : 'routes'}: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}], '''; final String parentNavigatorKeyParameter = parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'; + + if (routeDataClassName == 'StatefulShellBranchData') { + return ''' +$routeDataClassName.\$branch( + $routeConstructorParameters + $routesBit + ) +'''; + } + return ''' $routeDataClassName.\$route( $routeConstructorParameters @@ -628,4 +678,4 @@ const String _enumConverterHelper = ''' extension on Map { T $enumExtensionHelperName(String value) => entries.singleWhere((element) => element.value == value).key; -}'''; \ No newline at end of file +}'''; From e3d109d15ead23b0db114346c5049585c5f051f8 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Wed, 19 Jul 2023 15:28:45 -0700 Subject: [PATCH 17/28] Update route_config.dart --- .../lib/src/route_config.dart | 81 +++++++++++-------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 85ac70740a7..5e650613dfd 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -14,6 +14,7 @@ import 'package:path/path.dart' as p; import 'package:path_to_regexp/path_to_regexp.dart'; import 'package:source_gen/source_gen.dart'; import 'package:source_helper/source_helper.dart'; +import 'package:go_router/go_router.dart'; import 'type_helpers.dart'; @@ -61,19 +62,25 @@ extension $_extensionName on $_className { @override String get routeDataClassName => 'ShellRouteData'; + + @override + String get childrenGetterName => 'routes'; + + @override + String get dataConvertionFunctionName => '\$route'; } /// The configuration to generate class declarations for a StatefulShellRouteData. class StatefulShellRouteConfig extends RouteBaseConfig { StatefulShellRouteConfig._({ - required this.navigatorKey, required super.routeDataClass, required super.parent, - required super.parentNavigatorKey, + this.navigatorContainerBuilder, + this.restorationScopeId, }) : super._(); - /// The command for calling the navigator key getter from the ShellRouteData. - final String? navigatorKey; + final ShellNavigationContainerBuilder? navigatorContainerBuilder; + final String? restorationScopeId; @override Iterable classDeclarations() => [ @@ -86,10 +93,17 @@ extension $_extensionName on $_className { @override String get routeConstructorParameters => - navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'; + '${restorationScopeId == null ? '' : 'restorationScopeId: $restorationScopeId,'}' + '${restorationScopeId == null ? '' : 'restorationScopeId: $restorationScopeId,'}'; @override String get routeDataClassName => 'StatefulShellRouteData'; + + @override + String get childrenGetterName => 'branches'; + + @override + String get dataConvertionFunctionName => '\$route'; } /// The configuration to generate class declarations for a StatefulShellBranchData. @@ -98,7 +112,6 @@ class StatefulShellBranchConfig extends RouteBaseConfig { required this.navigatorKey, required super.routeDataClass, required super.parent, - required super.parentNavigatorKey, }) : super._(); /// The command for calling the navigator key getter from the ShellRouteData. @@ -113,6 +126,12 @@ class StatefulShellBranchConfig extends RouteBaseConfig { @override String get routeDataClassName => 'StatefulShellBranchData'; + + @override + String get childrenGetterName => 'routes'; + + @override + String get dataConvertionFunctionName => '\$branch'; } /// The configuration to generate class declarations for a GoRouteData. @@ -352,6 +371,11 @@ extension $_extensionName on $_className { @override String get routeDataClassName => 'GoRouteData'; + + @override + String get childrenGetterName => 'routes'; + @override + String get dataConvertionFunctionName => '\$route'; } /// Represents a `TypedGoRoute` annotation to the builder. @@ -359,7 +383,7 @@ abstract class RouteBaseConfig { RouteBaseConfig._({ required this.routeDataClass, required this.parent, - required this.parentNavigatorKey, + this.parentNavigatorKey, }); /// Creates a new [RouteBaseConfig] represented the annotation data in [reader]. @@ -427,14 +451,6 @@ abstract class RouteBaseConfig { value = StatefulShellRouteConfig._( routeDataClass: classElement, parent: parent, - navigatorKey: _generateNavigatorKeyGetterCode( - classElement, - keyName: r'$navigatorKey', - ), - parentNavigatorKey: _generateNavigatorKeyGetterCode( - classElement, - keyName: r'$parentNavigatorKey', - ), ); break; case 'TypedStatefulShellBranch': @@ -445,13 +461,9 @@ abstract class RouteBaseConfig { classElement, keyName: r'$navigatorKey', ), - parentNavigatorKey: _generateNavigatorKeyGetterCode( - classElement, - keyName: r'$parentNavigatorKey', - ), ); break; - default: + default://case 'TypedGoRoute': final ConstantReader pathValue = reader.read('path'); if (pathValue.isNull) { throw InvalidGenerationSourceError( @@ -459,7 +471,6 @@ abstract class RouteBaseConfig { element: element, ); } - final ConstantReader nameValue = reader.read('name'); value = GoRouteConfig._( path: pathValue.stringValue, @@ -474,7 +485,7 @@ abstract class RouteBaseConfig { } value._children.addAll(reader - .read(typeName == 'TypedStatefulShellRoute' ? 'branches' : 'routes') + .read(_generateChildrenGetterName(typeName)) .listValue .map((DartObject e) => RouteBaseConfig._fromAnnotation( ConstantReader(e), element, value))); @@ -494,6 +505,10 @@ abstract class RouteBaseConfig { /// `RouteBase` class this config generates. final String? parentNavigatorKey; + static String _generateChildrenGetterName(String name) { + return name == 'TypedStatefulShellRoute' ? 'branches' : 'routes'; + } + static String? _generateNavigatorKeyGetterCode( InterfaceElement classElement, { required String keyName, @@ -577,23 +592,15 @@ RouteBase get $_routeGetterName => ${_invokesRouteConstructor()}; final String routesBit = _children.isEmpty ? '' : ''' -${routeDataClassName == 'StatefulShellRouteData' ? 'branches' : 'routes'}: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}], +${childrenGetterName}: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}], '''; + final String parentNavigatorKeyParameter = parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'; - if (routeDataClassName == 'StatefulShellBranchData') { - return ''' -$routeDataClassName.\$branch( - $routeConstructorParameters - $routesBit - ) -'''; - } - return ''' -$routeDataClassName.\$route( +$routeDataClassName.$dataConvertionFunctionName( $routeConstructorParameters factory: $_extensionName._fromState, $parentNavigatorKeyParameter @@ -609,6 +616,14 @@ $routeDataClassName.\$route( @protected String get routeDataClassName; + /// The name to get children. + @protected + String get childrenGetterName; + + /// The function name of `RouteData` to get Routes or branches. + @protected + String get dataConvertionFunctionName; + /// Additional constructor parameter for invoking route constructor. @protected String get routeConstructorParameters; From bfd36ef4d0a15a50755d93cd8bc5a4a8af92ff10 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Thu, 20 Jul 2023 11:09:11 -0700 Subject: [PATCH 18/28] merge --- packages/go_router_builder/pubspec.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 09a426bb4df..381961ee2d4 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -22,9 +22,7 @@ dependencies: source_helper: ^1.3.0 dev_dependencies: - build_runner: ^2.0.0 - go_router: ^9.0.3 - source_gen_test: ^1.0.0 build_test: ^2.1.7 dart_style: 2.3.2 + go_router: ^9.0.0 test: ^1.20.0 From 03c069b9016de83b654d3db29985108f4fe72c3a Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Tue, 25 Jul 2023 11:28:20 -0700 Subject: [PATCH 19/28] resolve comments --- .../lib/stateful_shell_route_example.dart | 6 ++ .../lib/stateful_shell_route_example.g.dart | 3 + .../lib/src/route_config.dart | 86 +++++++++++++------ 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart index c0361c79141..88ec3d435e4 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart @@ -61,6 +61,11 @@ class MyShellRouteData extends StatefulShellRouteData { ) { return ScaffoldWithNavBar(navigationShell: navigationShell); } + + static final String $restorationScopeId = 'restorationScopeId'; + + static final ShellNavigationContainerBuilder $navigatorContainerBuilder = + ( (_, __, List children) => ListView(children: children)); } class BranchAData extends StatefulShellBranchData { @@ -71,6 +76,7 @@ class BranchBData extends StatefulShellBranchData { const BranchBData(); static final GlobalKey $navigatorKey = _sectionANavigatorKey; + static final String $restorationScopeId = 'restorationScopeId'; } class DetailsARouteData extends GoRouteData { diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart index baf28b22b65..9be15d9859a 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart @@ -13,6 +13,8 @@ List get $appRoutes => [ ]; RouteBase get $myShellRouteData => StatefulShellRouteData.$route( + restorationScopeId: MyShellRouteData.$restorationScopeId, + navigatorContainerBuilder: MyShellRouteData.$navigatorContainerBuilder, factory: $MyShellRouteDataExtension._fromState, branches: [ StatefulShellBranchData.$branch( @@ -25,6 +27,7 @@ RouteBase get $myShellRouteData => StatefulShellRouteData.$route( ), StatefulShellBranchData.$branch( navigatorKey: BranchBData.$navigatorKey, + restorationScopeId: BranchBData.$restorationScopeId, routes: [ GoRouteData.$route( path: '/detailsB', diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 5e650613dfd..c063384818e 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -14,7 +14,6 @@ import 'package:path/path.dart' as p; import 'package:path_to_regexp/path_to_regexp.dart'; import 'package:source_gen/source_gen.dart'; import 'package:source_helper/source_helper.dart'; -import 'package:go_router/go_router.dart'; import 'type_helpers.dart'; @@ -60,6 +59,10 @@ extension $_extensionName on $_className { String get routeConstructorParameters => navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'; + @override + String get factorConstructorParameters => + 'factory: $_extensionName._fromState,'; + @override String get routeDataClassName => 'ShellRouteData'; @@ -79,7 +82,7 @@ class StatefulShellRouteConfig extends RouteBaseConfig { this.restorationScopeId, }) : super._(); - final ShellNavigationContainerBuilder? navigatorContainerBuilder; + final String? navigatorContainerBuilder; final String? restorationScopeId; @override @@ -94,7 +97,11 @@ extension $_extensionName on $_className { @override String get routeConstructorParameters => '${restorationScopeId == null ? '' : 'restorationScopeId: $restorationScopeId,'}' - '${restorationScopeId == null ? '' : 'restorationScopeId: $restorationScopeId,'}'; + '${navigatorContainerBuilder == null ? '' : 'navigatorContainerBuilder: $navigatorContainerBuilder,'}'; + + @override + String get factorConstructorParameters => + 'factory: $_extensionName._fromState,'; @override String get routeDataClassName => 'StatefulShellRouteData'; @@ -112,17 +119,23 @@ class StatefulShellBranchConfig extends RouteBaseConfig { required this.navigatorKey, required super.routeDataClass, required super.parent, + this.restorationScopeId, }) : super._(); /// The command for calling the navigator key getter from the ShellRouteData. final String? navigatorKey; + final String? restorationScopeId; + @override Iterable classDeclarations() => []; + @override + String get factorConstructorParameters => ''; @override String get routeConstructorParameters => - navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'; + '${navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'}' + '${restorationScopeId == null ? '' : 'restorationScopeId: $restorationScopeId,'}'; @override String get routeDataClassName => 'StatefulShellBranchData'; @@ -363,6 +376,10 @@ extension $_extensionName on $_className { return enumParamTypes.map(_enumMapConst); } + @override + String get factorConstructorParameters => + 'factory: $_extensionName._fromState,'; + @override String get routeConstructorParameters => ''' path: ${escapeDartString(path)}, @@ -437,11 +454,11 @@ abstract class RouteBaseConfig { value = ShellRouteConfig._( routeDataClass: classElement, parent: parent, - navigatorKey: _generateNavigatorKeyGetterCode( + navigatorKey: _generateParameterGetterCode( classElement, keyName: r'$navigatorKey', ), - parentNavigatorKey: _generateNavigatorKeyGetterCode( + parentNavigatorKey: _generateParameterGetterCode( classElement, keyName: r'$parentNavigatorKey', ), @@ -451,19 +468,31 @@ abstract class RouteBaseConfig { value = StatefulShellRouteConfig._( routeDataClass: classElement, parent: parent, + restorationScopeId: _generateParameterGetterCode( + classElement, + parameterName: r'$restorationScopeId', + ), + navigatorContainerBuilder: _generateParameterGetterCode( + classElement, + parameterName: r'$navigatorContainerBuilder', + ), ); break; case 'TypedStatefulShellBranch': value = StatefulShellBranchConfig._( routeDataClass: classElement, parent: parent, - navigatorKey: _generateNavigatorKeyGetterCode( + navigatorKey: _generateParameterGetterCode( classElement, keyName: r'$navigatorKey', ), + restorationScopeId: _generateParameterGetterCode( + classElement, + parameterName: r'$restorationScopeId', + ), ); break; - default://case 'TypedGoRoute': + default: //case 'TypedGoRoute': final ConstantReader pathValue = reader.read('path'); if (pathValue.isNull) { throw InvalidGenerationSourceError( @@ -477,7 +506,7 @@ abstract class RouteBaseConfig { name: nameValue.isNull ? null : nameValue.stringValue, routeDataClass: classElement, parent: parent, - parentNavigatorKey: _generateNavigatorKeyGetterCode( + parentNavigatorKey: _generateParameterGetterCode( classElement, keyName: r'$parentNavigatorKey', ), @@ -509,28 +538,31 @@ abstract class RouteBaseConfig { return name == 'TypedStatefulShellRoute' ? 'branches' : 'routes'; } - static String? _generateNavigatorKeyGetterCode( + static String? _generateParameterGetterCode( InterfaceElement classElement, { - required String keyName, + required String parameterName, }) { final String? fieldDisplayName = classElement.fields .where((FieldElement element) { - final DartType type = element.type; - if (!element.isStatic || - element.name != keyName || - type is! ParameterizedType) { - return false; - } - final List typeArguments = type.typeArguments; - if (typeArguments.length != 1) { + if (!element.isStatic || element.name != parameterName) { return false; } - final DartType typeArgument = typeArguments.single; - if (typeArgument.getDisplayString(withNullability: false) == - 'NavigatorState') { - return true; + if (parameterName.toLowerCase().contains('navigatorkey')) { + final DartType type = element.type; + if (type is! ParameterizedType) { + return false; + } + final List typeArguments = type.typeArguments; + if (typeArguments.length != 1) { + return false; + } + final DartType typeArgument = typeArguments.single; + if (typeArgument.getDisplayString(withNullability: false) != + 'NavigatorState') { + return false; + } } - return false; + return true; }) .map((FieldElement e) => e.displayName) .firstOrNull; @@ -602,7 +634,7 @@ ${childrenGetterName}: [${_children.map((RouteBaseConfig e) => '${e._invokesRout return ''' $routeDataClassName.$dataConvertionFunctionName( $routeConstructorParameters - factory: $_extensionName._fromState, + $factorConstructorParameters $parentNavigatorKeyParameter $routesBit ) @@ -624,6 +656,10 @@ $routeDataClassName.$dataConvertionFunctionName( @protected String get dataConvertionFunctionName; + /// Additional factory constructor. + @protected + String get factorConstructorParameters; + /// Additional constructor parameter for invoking route constructor. @protected String get routeConstructorParameters; From 0da2339c6cb2055e9100a4fcca041aa36d2637ae Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Tue, 25 Jul 2023 13:02:17 -0700 Subject: [PATCH 20/28] update --- .../lib/shell_route_with_keys_example.g.dart | 2 +- .../lib/src/route_config.dart | 62 +++++++------------ 2 files changed, 23 insertions(+), 41 deletions(-) diff --git a/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart b/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart index 31f9d4bca6d..6151889582d 100644 --- a/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart +++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart @@ -26,8 +26,8 @@ RouteBase get $myShellRouteData => ShellRouteData.$route( routes: [ GoRouteData.$route( path: ':id', - factory: $UserRouteDataExtension._fromState, parentNavigatorKey: UserRouteData.$parentNavigatorKey, + factory: $UserRouteDataExtension._fromState, ), ], ), diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index c063384818e..fb511f3531c 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -38,14 +38,16 @@ class InfoIterable extends IterableBase { class ShellRouteConfig extends RouteBaseConfig { ShellRouteConfig._({ required this.navigatorKey, + required this.parentNavigatorKey, required super.routeDataClass, required super.parent, - required super.parentNavigatorKey, }) : super._(); /// The command for calling the navigator key getter from the ShellRouteData. final String? navigatorKey; + final String? parentNavigatorKey; + @override Iterable classDeclarations() => [ ''' @@ -57,7 +59,8 @@ extension $_extensionName on $_className { @override String get routeConstructorParameters => - navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'; + '${navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'}' + '${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}'; @override String get factorConstructorParameters => @@ -66,9 +69,6 @@ extension $_extensionName on $_className { @override String get routeDataClassName => 'ShellRouteData'; - @override - String get childrenGetterName => 'routes'; - @override String get dataConvertionFunctionName => '\$route'; } @@ -78,8 +78,8 @@ class StatefulShellRouteConfig extends RouteBaseConfig { StatefulShellRouteConfig._({ required super.routeDataClass, required super.parent, - this.navigatorContainerBuilder, - this.restorationScopeId, + required this.navigatorContainerBuilder, + required this.restorationScopeId, }) : super._(); final String? navigatorContainerBuilder; @@ -106,9 +106,6 @@ extension $_extensionName on $_className { @override String get routeDataClassName => 'StatefulShellRouteData'; - @override - String get childrenGetterName => 'branches'; - @override String get dataConvertionFunctionName => '\$route'; } @@ -140,9 +137,6 @@ class StatefulShellBranchConfig extends RouteBaseConfig { @override String get routeDataClassName => 'StatefulShellBranchData'; - @override - String get childrenGetterName => 'routes'; - @override String get dataConvertionFunctionName => '\$branch'; } @@ -152,9 +146,9 @@ class GoRouteConfig extends RouteBaseConfig { GoRouteConfig._({ required this.path, required this.name, + required this.parentNavigatorKey, required super.routeDataClass, required super.parent, - required super.parentNavigatorKey, }) : super._(); /// The path of the GoRoute to be created by this configuration. @@ -163,6 +157,8 @@ class GoRouteConfig extends RouteBaseConfig { /// The name of the GoRoute to be created by this configuration. final String? name; + final String? parentNavigatorKey; + late final Set _pathParams = Set.unmodifiable(_parsedPath .whereType() .map((ParameterToken e) => e.name)); @@ -384,13 +380,12 @@ extension $_extensionName on $_className { String get routeConstructorParameters => ''' path: ${escapeDartString(path)}, ${name != null ? 'name: ${escapeDartString(name!)},' : ''} + ${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'} '''; @override String get routeDataClassName => 'GoRouteData'; - @override - String get childrenGetterName => 'routes'; @override String get dataConvertionFunctionName => '\$route'; } @@ -400,7 +395,6 @@ abstract class RouteBaseConfig { RouteBaseConfig._({ required this.routeDataClass, required this.parent, - this.parentNavigatorKey, }); /// Creates a new [RouteBaseConfig] represented the annotation data in [reader]. @@ -456,11 +450,11 @@ abstract class RouteBaseConfig { parent: parent, navigatorKey: _generateParameterGetterCode( classElement, - keyName: r'$navigatorKey', + parameterName: r'$navigatorKey', ), parentNavigatorKey: _generateParameterGetterCode( classElement, - keyName: r'$parentNavigatorKey', + parameterName: r'$parentNavigatorKey', ), ); break; @@ -484,7 +478,7 @@ abstract class RouteBaseConfig { parent: parent, navigatorKey: _generateParameterGetterCode( classElement, - keyName: r'$navigatorKey', + parameterName: r'$navigatorKey', ), restorationScopeId: _generateParameterGetterCode( classElement, @@ -508,7 +502,7 @@ abstract class RouteBaseConfig { parent: parent, parentNavigatorKey: _generateParameterGetterCode( classElement, - keyName: r'$parentNavigatorKey', + parameterName: r'$parentNavigatorKey', ), ); } @@ -530,18 +524,15 @@ abstract class RouteBaseConfig { /// The parent of this route config. final RouteBaseConfig? parent; - /// The parent navigator key string that is used for initialize the - /// `RouteBase` class this config generates. - final String? parentNavigatorKey; - static String _generateChildrenGetterName(String name) { - return name == 'TypedStatefulShellRoute' ? 'branches' : 'routes'; + return (name == 'TypedStatefulShellRoute' || + name == 'StatefulShellRouteData') + ? 'branches' + : 'routes'; } - static String? _generateParameterGetterCode( - InterfaceElement classElement, { - required String parameterName, - }) { + static String? _generateParameterGetterCode(InterfaceElement classElement, + {required String parameterName}) { final String? fieldDisplayName = classElement.fields .where((FieldElement element) { if (!element.isStatic || element.name != parameterName) { @@ -624,18 +615,13 @@ RouteBase get $_routeGetterName => ${_invokesRouteConstructor()}; final String routesBit = _children.isEmpty ? '' : ''' -${childrenGetterName}: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}], +${_generateChildrenGetterName(routeDataClassName)}: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}], '''; - final String parentNavigatorKeyParameter = parentNavigatorKey == null - ? '' - : 'parentNavigatorKey: $parentNavigatorKey,'; - return ''' $routeDataClassName.$dataConvertionFunctionName( $routeConstructorParameters $factorConstructorParameters - $parentNavigatorKeyParameter $routesBit ) '''; @@ -648,10 +634,6 @@ $routeDataClassName.$dataConvertionFunctionName( @protected String get routeDataClassName; - /// The name to get children. - @protected - String get childrenGetterName; - /// The function name of `RouteData` to get Routes or branches. @protected String get dataConvertionFunctionName; From 06c7e67fa7f9abdae2c6f91032ad646ef8b4ac87 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Tue, 25 Jul 2023 13:04:13 -0700 Subject: [PATCH 21/28] Update route_config.dart --- packages/go_router_builder/lib/src/route_config.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index fb511f3531c..2e782cc3814 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -442,7 +442,7 @@ abstract class RouteBaseConfig { // ignore: deprecated_member_use final InterfaceElement classElement = typeParamType.element; - final RouteBaseConfig value; + RouteBaseConfig? value; switch (typeName) { case 'TypedShellRoute': value = ShellRouteConfig._( @@ -486,7 +486,7 @@ abstract class RouteBaseConfig { ), ); break; - default: //case 'TypedGoRoute': + case 'TypedGoRoute': final ConstantReader pathValue = reader.read('path'); if (pathValue.isNull) { throw InvalidGenerationSourceError( @@ -507,7 +507,7 @@ abstract class RouteBaseConfig { ); } - value._children.addAll(reader + value!._children.addAll(reader .read(_generateChildrenGetterName(typeName)) .listValue .map((DartObject e) => RouteBaseConfig._fromAnnotation( From ecbc5f5b03d7e6e8f5934599fb8766d4e830d0d4 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:33:11 -0700 Subject: [PATCH 22/28] update --- .../lib/src/route_config.dart | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 2e782cc3814..33a00b2ac10 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -70,18 +70,21 @@ extension $_extensionName on $_className { String get routeDataClassName => 'ShellRouteData'; @override - String get dataConvertionFunctionName => '\$route'; + String get dataConvertionFunctionName => r'$route'; } /// The configuration to generate class declarations for a StatefulShellRouteData. class StatefulShellRouteConfig extends RouteBaseConfig { StatefulShellRouteConfig._({ + required this.parentNavigatorKey, required super.routeDataClass, required super.parent, required this.navigatorContainerBuilder, required this.restorationScopeId, }) : super._(); + + final String? parentNavigatorKey; final String? navigatorContainerBuilder; final String? restorationScopeId; @@ -96,6 +99,7 @@ extension $_extensionName on $_className { @override String get routeConstructorParameters => + '${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}' '${restorationScopeId == null ? '' : 'restorationScopeId: $restorationScopeId,'}' '${navigatorContainerBuilder == null ? '' : 'navigatorContainerBuilder: $navigatorContainerBuilder,'}'; @@ -107,7 +111,7 @@ extension $_extensionName on $_className { String get routeDataClassName => 'StatefulShellRouteData'; @override - String get dataConvertionFunctionName => '\$route'; + String get dataConvertionFunctionName => r'$route'; } /// The configuration to generate class declarations for a StatefulShellBranchData. @@ -138,7 +142,7 @@ class StatefulShellBranchConfig extends RouteBaseConfig { String get routeDataClassName => 'StatefulShellBranchData'; @override - String get dataConvertionFunctionName => '\$branch'; + String get dataConvertionFunctionName => r'$branch'; } /// The configuration to generate class declarations for a GoRouteData. @@ -387,7 +391,7 @@ extension $_extensionName on $_className { String get routeDataClassName => 'GoRouteData'; @override - String get dataConvertionFunctionName => '\$route'; + String get dataConvertionFunctionName => r'$route'; } /// Represents a `TypedGoRoute` annotation to the builder. @@ -442,7 +446,7 @@ abstract class RouteBaseConfig { // ignore: deprecated_member_use final InterfaceElement classElement = typeParamType.element; - RouteBaseConfig? value; + final RouteBaseConfig value; switch (typeName) { case 'TypedShellRoute': value = ShellRouteConfig._( @@ -462,6 +466,10 @@ abstract class RouteBaseConfig { value = StatefulShellRouteConfig._( routeDataClass: classElement, parent: parent, + parentNavigatorKey: _generateParameterGetterCode( + classElement, + parameterName: r'$parentNavigatorKey', + ), restorationScopeId: _generateParameterGetterCode( classElement, parameterName: r'$restorationScopeId', @@ -505,9 +513,12 @@ abstract class RouteBaseConfig { parameterName: r'$parentNavigatorKey', ), ); + break; + default: + throw UnsupportedError('Unrecognized type $typeName'); } - value!._children.addAll(reader + value._children.addAll(reader .read(_generateChildrenGetterName(typeName)) .listValue .map((DartObject e) => RouteBaseConfig._fromAnnotation( From 6044e3229699d60819a39a67ae5e2582f4fdb1e3 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:37:07 -0700 Subject: [PATCH 23/28] Update pubspec.yaml --- packages/go_router_builder/pubspec.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index ce5d5015e9b..c3beffd473c 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,9 +2,7 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router - version: 2.3.0 - repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22 From 3c2ed3bf41962f3f988a7588c7320d55e6dd28b8 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:52:36 -0700 Subject: [PATCH 24/28] lint --- .../lib/stateful_shell_route_example.dart | 8 +++---- .../lib/src/route_config.dart | 24 ++++++++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart index 88ec3d435e4..37ea1867522 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart @@ -62,10 +62,10 @@ class MyShellRouteData extends StatefulShellRouteData { return ScaffoldWithNavBar(navigationShell: navigationShell); } - static final String $restorationScopeId = 'restorationScopeId'; + static const String $restorationScopeId = 'restorationScopeId'; - static final ShellNavigationContainerBuilder $navigatorContainerBuilder = - ( (_, __, List children) => ListView(children: children)); + static Widget $navigatorContainerBuilder(_, __, List children) => + ListView(children: children); } class BranchAData extends StatefulShellBranchData { @@ -76,7 +76,7 @@ class BranchBData extends StatefulShellBranchData { const BranchBData(); static final GlobalKey $navigatorKey = _sectionANavigatorKey; - static final String $restorationScopeId = 'restorationScopeId'; + static const String $restorationScopeId = 'restorationScopeId'; } class DetailsARouteData extends GoRouteData { diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 99d925d56b2..f9206c750ef 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -84,11 +84,12 @@ class StatefulShellRouteConfig extends RouteBaseConfig { required this.restorationScopeId, }) : super._(); - /// The parent navigator key. final String? parentNavigatorKey; + /// The navigator container builder. final String? navigatorContainerBuilder; + /// The restoration scope id. final String? restorationScopeId; @@ -129,6 +130,7 @@ class StatefulShellBranchConfig extends RouteBaseConfig { /// The command for calling the navigator key getter from the ShellRouteData. final String? navigatorKey; + /// The restoration scope id. final String? restorationScopeId; @@ -171,7 +173,6 @@ class GoRouteConfig extends RouteBaseConfig { late final Set _pathParams = pathParametersFromPattern(_rawJoinedPath); - String get _rawJoinedPath { final List pathSegments = []; @@ -513,7 +514,7 @@ abstract class RouteBaseConfig { ); break; default: - throw UnsupportedError('Unrecognized type $typeName'); + throw UnsupportedError('Unrecognized type $typeName'); } value._children.addAll(reader @@ -567,10 +568,21 @@ abstract class RouteBaseConfig { .map((FieldElement e) => e.displayName) .firstOrNull; - if (fieldDisplayName == null) { - return null; + if (fieldDisplayName != null) { + return '${classElement.name}.$fieldDisplayName'; + } + + final String? methodDisplayName = classElement.methods + .where((MethodElement element) { + return element.isStatic && element.name == parameterName; + }) + .map((MethodElement e) => e.displayName) + .firstOrNull; + + if (methodDisplayName != null) { + return '${classElement.name}.$methodDisplayName'; } - return '${classElement.name}.$fieldDisplayName'; + return null; } /// Generates all of the members that correspond to `this`. From 1e78defecf6fdd7a196c04c6b88a8c936191d1c1 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:40:29 -0700 Subject: [PATCH 25/28] Update stateful_shell_route_example.dart --- .../lib/stateful_shell_route_example.dart | 77 ++++++++++++++++++- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart index 37ea1867522..c098b52e898 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart @@ -4,6 +4,7 @@ // ignore_for_file: public_member_api_docs +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -59,13 +60,18 @@ class MyShellRouteData extends StatefulShellRouteData { GoRouterState state, StatefulNavigationShell navigationShell, ) { - return ScaffoldWithNavBar(navigationShell: navigationShell); + return navigationShell; } static const String $restorationScopeId = 'restorationScopeId'; - static Widget $navigatorContainerBuilder(_, __, List children) => - ListView(children: children); + static Widget $navigatorContainerBuilder(BuildContext context, + StatefulNavigationShell navigationShell, List children) { + return ScaffoldWithNavBar( + navigationShell: navigationShell, + children: children, + ); + } } class BranchAData extends StatefulShellBranchData { @@ -103,17 +109,31 @@ class ScaffoldWithNavBar extends StatelessWidget { /// Constructs an [ScaffoldWithNavBar]. const ScaffoldWithNavBar({ required this.navigationShell, + required this.children, Key? key, }) : super(key: key ?? const ValueKey('ScaffoldWithNavBar')); /// The navigation shell and container for the branch Navigators. final StatefulNavigationShell navigationShell; + /// The children (branch Navigators) to display in a custom container + /// ([AnimatedBranchContainer]). + final List children; + @override Widget build(BuildContext context) { return Scaffold( - body: navigationShell, + body: + + AnimatedBranchContainer( + currentIndex: navigationShell.currentIndex, + children: children, + ), bottomNavigationBar: BottomNavigationBar( + // Here, the items of BottomNavigationBar are hard coded. In a real + // world scenario, the items would most likely be generated from the + // branches of the shell route, which can be fetched using + // `navigationShell.route.branches`. items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'), BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'), @@ -124,14 +144,63 @@ class ScaffoldWithNavBar extends StatelessWidget { ); } + /// Navigate to the current location of the branch at the provided index when + /// tapping an item in the BottomNavigationBar. void _onTap(BuildContext context, int index) { + // When navigating to a new branch, it's recommended to use the goBranch + // method, as doing so makes sure the last navigation state of the + // Navigator for the branch is restored. navigationShell.goBranch( index, + // A common pattern when using bottom navigation bars is to support + // navigating to the initial location when tapping the item that is + // already active. This example demonstrates how to support this behavior, + // using the initialLocation parameter of goBranch. initialLocation: index == navigationShell.currentIndex, ); } } +/// Custom branch Navigator container that provides animated transitions +/// when switching branches. +class AnimatedBranchContainer extends StatelessWidget { + /// Creates a AnimatedBranchContainer + const AnimatedBranchContainer( + {super.key, required this.currentIndex, required this.children}); + + /// The index (in [children]) of the branch Navigator to display. + final int currentIndex; + + /// The children (branch Navigators) to display in this container. + final List children; + + @override + Widget build(BuildContext context) { + return Stack( + children: children.mapIndexed( + (int index, Widget navigator) { + return AnimatedScale( + scale: index == currentIndex ? 1 : 1.5, + duration: const Duration(milliseconds: 400), + child: AnimatedOpacity( + opacity: index == currentIndex ? 1 : 0, + duration: const Duration(milliseconds: 400), + child: _branchNavigatorWrapper(index, navigator), + ), + ); + }, + ).toList()); + } + + Widget _branchNavigatorWrapper(int index, Widget navigator) => IgnorePointer( + ignoring: index != currentIndex, + child: TickerMode( + enabled: index == currentIndex, + child: navigator, + ), + ); +} + /// The details screen for either the A or B screen. class DetailsScreen extends StatefulWidget { /// Constructs a [DetailsScreen]. From eecb67ece8e42ebb2b0e31734c72dbb687a816ca Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:49:01 -0700 Subject: [PATCH 26/28] Update pubspec.yaml --- packages/go_router_builder/example/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index 3dea2475de7..b5fde7d2bf9 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: sdk: flutter go_router: ^10.0.0 provider: 6.0.5 + collection: ^1.15.0 dev_dependencies: build_runner: ^2.3.0 From 2e63519f43bd3c45c217c09ec4c30fa12ea9af91 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Mon, 31 Jul 2023 15:08:57 -0700 Subject: [PATCH 27/28] Update pubspec.yaml --- packages/go_router_builder/example/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index b5fde7d2bf9..1cc07ea3430 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -6,11 +6,11 @@ environment: sdk: ">=2.18.0 <4.0.0" dependencies: + collection: ^1.15.0 flutter: sdk: flutter go_router: ^10.0.0 provider: 6.0.5 - collection: ^1.15.0 dev_dependencies: build_runner: ^2.3.0 From cfb1de87b1b3779d687402909162424f2e8d62b4 Mon Sep 17 00:00:00 2001 From: hangyu <108393416+hangyujin@users.noreply.github.com> Date: Mon, 31 Jul 2023 15:16:28 -0700 Subject: [PATCH 28/28] Update stateful_shell_route_example.dart --- .../example/lib/stateful_shell_route_example.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart index c098b52e898..9e9748e870d 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart @@ -60,7 +60,7 @@ class MyShellRouteData extends StatefulShellRouteData { GoRouterState state, StatefulNavigationShell navigationShell, ) { - return navigationShell; + return navigationShell; } static const String $restorationScopeId = 'restorationScopeId'; @@ -123,9 +123,7 @@ class ScaffoldWithNavBar extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: - - AnimatedBranchContainer( + body: AnimatedBranchContainer( currentIndex: navigationShell.currentIndex, children: children, ),