From 54e1576b91d19b869cc6959e97962cc554294fe4 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Wed, 22 Feb 2023 12:40:47 -0800 Subject: [PATCH 01/29] Add ShellRoute support to go_router_builder --- .../example/lib/all_types.g.dart | 4 +- .../go_router_builder/example/lib/main.dart | 67 +++++++++++---- .../go_router_builder/example/lib/main.g.dart | 41 +++++++--- .../example/lib/simple_example.g.dart | 4 +- .../go_router_builder/example/pubspec.yaml | 2 +- .../lib/src/go_router_generator.dart | 2 +- .../lib/src/route_config.dart | 82 +++++++++++++++---- packages/go_router_builder/pubspec.yaml | 2 +- .../_go_router_builder_test_input.dart | 2 +- 9 files changed, 159 insertions(+), 47 deletions(-) diff --git a/packages/go_router_builder/example/lib/all_types.g.dart b/packages/go_router_builder/example/lib/all_types.g.dart index 61f38b7aee9..d04f026eaf4 100644 --- a/packages/go_router_builder/example/lib/all_types.g.dart +++ b/packages/go_router_builder/example/lib/all_types.g.dart @@ -8,11 +8,11 @@ part of 'all_types.dart'; // GoRouterGenerator // ************************************************************************** -List get $appRoutes => [ +List get $appRoutes => [ $allTypesBaseRoute, ]; -GoRoute get $allTypesBaseRoute => GoRouteData.$route( +RouteBase get $allTypesBaseRoute => GoRouteData.$route( path: '/', factory: $AllTypesBaseRouteExtension._fromState, routes: [ diff --git a/packages/go_router_builder/example/lib/main.dart b/packages/go_router_builder/example/lib/main.dart index f6a64edf184..f1e5fab8b61 100644 --- a/packages/go_router_builder/example/lib/main.dart +++ b/packages/go_router_builder/example/lib/main.dart @@ -35,6 +35,7 @@ class App extends StatelessWidget { late final GoRouter _router = GoRouter( debugLogDiagnostics: true, routes: $appRoutes, + navigatorKey: key, // redirect to the login page if the user is not logged in redirect: (BuildContext context, GoRouterState state) { @@ -63,20 +64,26 @@ class App extends StatelessWidget { ); } +const key = GlobalObjectKey('navigator_key'); + @TypedGoRoute( path: '/', - routes: >[ - TypedGoRoute( - path: 'family/:fid', + routes: [ + TypedShellRoute( routes: >[ - TypedGoRoute( - path: 'person/:pid', - routes: >[ - TypedGoRoute(path: 'details/:details'), + TypedGoRoute( + path: 'family/:fid', + routes: [ + TypedGoRoute( + path: 'person/:pid', + routes: >[ + TypedGoRoute(path: 'details/:details'), + ], + ), ], ), ], - ) + ), ], ) class HomeRoute extends GoRouteData { @@ -86,6 +93,17 @@ class HomeRoute extends GoRouteData { Widget build(BuildContext context, GoRouterState state) => const HomeScreen(); } +class FamilyRoute extends ShellRouteData { + const FamilyRoute(); + + static final GlobalKey $navigatorKey = GlobalKey(); + + @override + Widget builder(BuildContext context, GoRouterState state, Widget child) { + return FamilyScreen(child: child); + } +} + @TypedGoRoute( path: '/login', ) @@ -99,14 +117,15 @@ class LoginRoute extends GoRouteData { LoginScreen(from: fromPage); } -class FamilyRoute extends GoRouteData { - const FamilyRoute(this.fid); +class FamilyIdRoute extends GoRouteData { + const FamilyIdRoute(this.fid); final String fid; @override - Widget build(BuildContext context, GoRouterState state) => - FamilyScreen(family: familyById(fid)); + Widget build(BuildContext context, GoRouterState state) => FamilyIdScreen( + family: familyById(fid), + ); } class PersonRoute extends GoRouteData { @@ -177,7 +196,7 @@ class HomeScreen extends StatelessWidget { for (final Family f in familyData) ListTile( title: Text(f.name), - onTap: () => FamilyRoute(f.id).go(context), + onTap: () => FamilyIdRoute(f.id).go(context), ) ], ), @@ -186,7 +205,23 @@ class HomeScreen extends StatelessWidget { } class FamilyScreen extends StatelessWidget { - const FamilyScreen({required this.family, super.key}); + const FamilyScreen({ + required this.child, + super.key, + }); + + final Widget child; + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.all(20), + child: child, + ); +} + +class FamilyIdScreen extends StatelessWidget { + const FamilyIdScreen({required this.family, super.key}); + final Family family; @override @@ -225,7 +260,8 @@ class PersonScreen extends StatelessWidget { in person.details.entries) ListTile( title: Text( - '${entry.key.name} - ${entry.value}', + // TODO(kevmoo): replace `split` with `name` when min SDK is 2.15 + '${entry.key.toString().split('.').last} - ${entry.value}', ), trailing: OutlinedButton( onPressed: () => PersonDetailsRoute( @@ -279,6 +315,7 @@ class PersonDetailsPage extends StatelessWidget { class LoginScreen extends StatelessWidget { const LoginScreen({this.from, super.key}); + final String? from; @override diff --git a/packages/go_router_builder/example/lib/main.g.dart b/packages/go_router_builder/example/lib/main.g.dart index ff7a23627c9..e13aebcb19d 100644 --- a/packages/go_router_builder/example/lib/main.g.dart +++ b/packages/go_router_builder/example/lib/main.g.dart @@ -8,26 +8,32 @@ part of 'main.dart'; // GoRouterGenerator // ************************************************************************** -List get $appRoutes => [ +List get $appRoutes => [ $homeRoute, $loginRoute, ]; -GoRoute get $homeRoute => GoRouteData.$route( +RouteBase get $homeRoute => GoRouteData.$route( path: '/', factory: $HomeRouteExtension._fromState, routes: [ - GoRouteData.$route( - path: 'family/:fid', + ShellRouteData.$route( factory: $FamilyRouteExtension._fromState, + navigatorKey: FamilyRoute.$navigatorKey, routes: [ GoRouteData.$route( - path: 'person/:pid', - factory: $PersonRouteExtension._fromState, + path: 'family/:fid', + factory: $FamilyIdRouteExtension._fromState, routes: [ GoRouteData.$route( - path: 'details/:details', - factory: $PersonDetailsRouteExtension._fromState, + path: 'person/:pid', + factory: $PersonRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: 'details/:details', + factory: $PersonDetailsRouteExtension._fromState, + ), + ], ), ], ), @@ -52,7 +58,22 @@ extension $HomeRouteExtension on HomeRoute { } extension $FamilyRouteExtension on FamilyRoute { - static FamilyRoute _fromState(GoRouterState state) => FamilyRoute( + static FamilyRoute _fromState(GoRouterState state) => const FamilyRoute(); + + String get location => GoRouteData.$location( + '/', + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: this); +} + +extension $FamilyIdRouteExtension on FamilyIdRoute { + static FamilyIdRoute _fromState(GoRouterState state) => FamilyIdRoute( state.params['fid']!, ); @@ -118,7 +139,7 @@ extension on Map { entries.singleWhere((element) => element.value == value).key; } -GoRoute get $loginRoute => GoRouteData.$route( +RouteBase get $loginRoute => GoRouteData.$route( path: '/login', factory: $LoginRouteExtension._fromState, ); diff --git a/packages/go_router_builder/example/lib/simple_example.g.dart b/packages/go_router_builder/example/lib/simple_example.g.dart index 6f9eb6a5eef..4edd92ae6e1 100644 --- a/packages/go_router_builder/example/lib/simple_example.g.dart +++ b/packages/go_router_builder/example/lib/simple_example.g.dart @@ -8,11 +8,11 @@ part of 'simple_example.dart'; // GoRouterGenerator // ************************************************************************** -List get $appRoutes => [ +List get $appRoutes => [ $homeRoute, ]; -GoRoute get $homeRoute => GoRouteData.$route( +RouteBase get $homeRoute => GoRouteData.$route( path: '/', factory: $HomeRouteExtension._fromState, routes: [ diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index 0b7ffcbff9d..63fa416f679 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: provider: ^6.0.0 dev_dependencies: - build_runner: ^2.0.0 + build_runner: ^2.3.0 build_verify: ^3.1.0 flutter_test: sdk: flutter 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 42ef4ca343c..583e7ba5ae8 100644 --- a/packages/go_router_builder/lib/src/go_router_generator.dart +++ b/packages/go_router_builder/lib/src/go_router_generator.dart @@ -47,7 +47,7 @@ class GoRouterGenerator extends GeneratorForAnnotation { return [ ''' -List get \$appRoutes => [ +List get \$appRoutes => [ ${getters.map((String e) => "$e,").join('\n')} ]; ''', diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 87087a368fe..a65a6a676a9 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -39,6 +39,8 @@ class RouteConfig { this._path, this._routeDataClass, this._parent, + this._key, + this._isShellRoute, ); /// Creates a new [RouteConfig] represented the annotation data in [reader]. @@ -66,19 +68,23 @@ class RouteConfig { RouteConfig? parent, ) { assert(!reader.isNull, 'reader should not be null'); - final ConstantReader pathValue = reader.read('path'); - if (pathValue.isNull) { - throw InvalidGenerationSourceError( - 'Missing `path` value on annotation.', - element: element, - ); - } + final InterfaceType type = reader.objectValue.type! as InterfaceType; + final bool isShellRoute = type.element2.name == 'TypedShellRoute'; - final String path = pathValue.stringValue; + String? path; - final InterfaceType type = reader.objectValue.type! as InterfaceType; - final DartType typeParamType = type.typeArguments.single; + if (!isShellRoute) { + final ConstantReader pathValue = reader.read('path'); + if (pathValue.isNull) { + throw InvalidGenerationSourceError( + 'Missing `path` value on annotation.', + element: element, + ); + } + path = pathValue.stringValue; + } + final DartType typeParamType = type.typeArguments.single; if (typeParamType is! InterfaceType) { throw InvalidGenerationSourceError( 'The type parameter on one of the @TypedGoRoute declarations could not ' @@ -93,18 +99,56 @@ class RouteConfig { // ignore: deprecated_member_use final InterfaceElement classElement = typeParamType.element; - final RouteConfig value = RouteConfig._(path, classElement, parent); + final RouteConfig value = RouteConfig._( + path ?? '', + classElement, + parent, + _decodeKey(classElement), + isShellRoute, + ); value._children.addAll(reader.read('routes').listValue.map((DartObject e) => RouteConfig._fromAnnotation(ConstantReader(e), element, value))); return value; } - final List _children = []; final String _path; final InterfaceElement _routeDataClass; final RouteConfig? _parent; + final String? _key; + final bool _isShellRoute; + + static String? _decodeKey(InterfaceElement classElement) { + bool whereStatic(FieldElement element) => element.isStatic; + bool whereKeyName(FieldElement element) => element.name == r'$navigatorKey'; + final String? fieldDisplayName = classElement.fields + .where(whereStatic) + .where(whereKeyName) + .where((FieldElement element) { + 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 true; + } + return false; + }) + .map((FieldElement e) => e.displayName) + .firstOrNull; + + if (fieldDisplayName == null) { + return null; + } + return '${classElement.name}.$fieldDisplayName'; + } /// Generates all of the members that correspond to `this`. InfoIterable generateMembers() => InfoIterable._( @@ -166,7 +210,7 @@ extension $_extensionName on $_className { /// Returns the `GoRoute` code for the annotated class. String _rootDefinition() => ''' -GoRoute get $_routeGetterName => ${_routeDefinition()}; +RouteBase get $_routeGetterName => ${_routeDefinition()}; '''; /// Returns code representing the constant maps that contain the `enum` to @@ -264,11 +308,21 @@ GoRoute get $_routeGetterName => ${_routeDefinition()}; : ''' routes: [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}], '''; - + final String navigatorKey = _key == null || _key!.isEmpty ? '' : 'navigatorKey: $_key,'; + if (_isShellRoute) { + return ''' + ShellRouteData.\$route( + factory: $_extensionName._fromState, + $navigatorKey + $routesBit + ) +'''; + } return ''' GoRouteData.\$route( path: ${escapeDartString(_path)}, factory: $_extensionName._fromState, + $navigatorKey $routesBit ) '''; diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 999c4a06673..505c6d4581f 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -23,6 +23,6 @@ dependencies: dev_dependencies: build_runner: ^2.0.0 - go_router: ^5.0.0 + go_router: ^6.0.10 source_gen_test: ^1.0.0 test: ^1.20.0 diff --git a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart index abb47ad5b7d..572ed2e3f87 100644 --- a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart +++ b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart @@ -72,7 +72,7 @@ class MissingPathParam extends GoRouteData { } @ShouldGenerate(r''' -GoRoute get $enumParam => GoRouteData.$route( +RouteBase get $enumParam => GoRouteData.$route( path: '/:y', factory: $EnumParamExtension._fromState, ); From a52bad2d2cdf2c4f6f55bd0a90d4c68335ee00f8 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Thu, 23 Feb 2023 09:42:58 -0800 Subject: [PATCH 02/29] Fix analysis issues --- packages/go_router_builder/example/lib/main.dart | 10 +++++----- packages/go_router_builder/lib/src/route_config.dart | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/go_router_builder/example/lib/main.dart b/packages/go_router_builder/example/lib/main.dart index f1e5fab8b61..026ef404a49 100644 --- a/packages/go_router_builder/example/lib/main.dart +++ b/packages/go_router_builder/example/lib/main.dart @@ -64,16 +64,16 @@ class App extends StatelessWidget { ); } -const key = GlobalObjectKey('navigator_key'); +const GlobalObjectKey key = GlobalObjectKey('navigator_key'); @TypedGoRoute( path: '/', - routes: [ + routes: >[ TypedShellRoute( routes: >[ TypedGoRoute( path: 'family/:fid', - routes: [ + routes: >[ TypedGoRoute( path: 'person/:pid', routes: >[ @@ -99,8 +99,8 @@ class FamilyRoute extends ShellRouteData { static final GlobalKey $navigatorKey = GlobalKey(); @override - Widget builder(BuildContext context, GoRouterState state, Widget child) { - return FamilyScreen(child: child); + Widget builder(BuildContext context, GoRouterState state, Widget navigator) { + return FamilyScreen(child: navigator); } } diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index a65a6a676a9..56ec9eff141 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -69,7 +69,7 @@ class RouteConfig { ) { assert(!reader.isNull, 'reader should not be null'); final InterfaceType type = reader.objectValue.type! as InterfaceType; - final bool isShellRoute = type.element2.name == 'TypedShellRoute'; + final bool isShellRoute = type.element.name == 'TypedShellRoute'; String? path; From 9797546a33536c0da141de4f10c0575b393e8709 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Thu, 23 Feb 2023 10:10:59 -0800 Subject: [PATCH 03/29] Fix go_router_builder test --- .../test/test_inputs/_go_router_builder_test_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart index 572ed2e3f87..61f5cdb38de 100644 --- a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart +++ b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart @@ -121,7 +121,7 @@ enum EnumTest { } @ShouldGenerate(r''' -GoRoute get $defaultValueRoute => GoRouteData.$route( +RouteBase get $defaultValueRoute => GoRouteData.$route( path: '/default-value-route', factory: $DefaultValueRouteExtension._fromState, ); From dadb98fd3b45c08e01d7a34dc4ee9d38f0b9f8dd Mon Sep 17 00:00:00 2001 From: John Ryan Date: Thu, 23 Feb 2023 10:20:40 -0800 Subject: [PATCH 04/29] format --- packages/go_router_builder/lib/src/route_config.dart | 3 ++- 1 file changed, 2 insertions(+), 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 56ec9eff141..4aabcb4f8a8 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -308,7 +308,8 @@ RouteBase get $_routeGetterName => ${_routeDefinition()}; : ''' routes: [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}], '''; - final String navigatorKey = _key == null || _key!.isEmpty ? '' : 'navigatorKey: $_key,'; + final String navigatorKey = + _key == null || _key!.isEmpty ? '' : 'navigatorKey: $_key,'; if (_isShellRoute) { return ''' ShellRouteData.\$route( From 2df6775e4212d8e668eb9a860aaa2d2ca7ec6299 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Thu, 23 Feb 2023 10:28:50 -0800 Subject: [PATCH 05/29] Increment minor version --- packages/go_router_builder/CHANGELOG.md | 4 ++++ packages/go_router_builder/pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index 57fd07f2dc3..ba4d8846ebd 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.2.0 + +* Adds Support for ShellRoute + ## 1.1.5 * Replaces unnecessary Flutter SDK constraint with corresponding Dart diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 505c6d4581f..bc440a7187a 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,7 +2,7 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router -version: 1.1.5 +version: 1.2.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 @@ -10,7 +10,7 @@ environment: sdk: ">=2.18.0 <3.0.0" dependencies: - analyzer: '>=4.4.0 <6.0.0' + analyzer: ">=4.4.0 <6.0.0" async: ^2.8.0 build: ^2.0.0 build_config: ^1.0.0 From b7441edf85620afb784c8f16fbe97807137ab74b Mon Sep 17 00:00:00 2001 From: John Ryan Date: Thu, 23 Feb 2023 10:37:28 -0800 Subject: [PATCH 06/29] ignore deprected member use so downgraded_analyze step passes --- packages/go_router_builder/lib/src/route_config.dart | 5 ++++- 1 file changed, 4 insertions(+), 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 4aabcb4f8a8..acf5dab1df9 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -69,7 +69,10 @@ class RouteConfig { ) { assert(!reader.isNull, 'reader should not be null'); final InterfaceType type = reader.objectValue.type! as InterfaceType; - final bool isShellRoute = type.element.name == 'TypedShellRoute'; + // Ignore the deprected `element2` so that the "downgraded_analyze" CI step + // passes. + //ignore: deprecated_member_use + final bool isShellRoute = type.element2.name == 'TypedShellRoute'; String? path; From 5633d150c54235f61fec7f6a7f3a77920601b74b Mon Sep 17 00:00:00 2001 From: John Ryan Date: Thu, 23 Feb 2023 12:16:12 -0800 Subject: [PATCH 07/29] Revert old TODO --- packages/go_router_builder/example/lib/main.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/go_router_builder/example/lib/main.dart b/packages/go_router_builder/example/lib/main.dart index 026ef404a49..6bc3194e622 100644 --- a/packages/go_router_builder/example/lib/main.dart +++ b/packages/go_router_builder/example/lib/main.dart @@ -260,8 +260,7 @@ class PersonScreen extends StatelessWidget { in person.details.entries) ListTile( title: Text( - // TODO(kevmoo): replace `split` with `name` when min SDK is 2.15 - '${entry.key.toString().split('.').last} - ${entry.value}', + '${entry.key.name} - ${entry.value}', ), trailing: OutlinedButton( onPressed: () => PersonDetailsRoute( From be0a39f79941eee7e37179aca45d525ee05f4c8b Mon Sep 17 00:00:00 2001 From: John Ryan Date: Thu, 23 Feb 2023 13:25:32 -0800 Subject: [PATCH 08/29] Skip extensions for ShellRoute --- packages/go_router_builder/example/lib/main.g.dart | 11 ----------- packages/go_router_builder/lib/src/route_config.dart | 12 +++++++++++- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/go_router_builder/example/lib/main.g.dart b/packages/go_router_builder/example/lib/main.g.dart index e13aebcb19d..d82fe541dbf 100644 --- a/packages/go_router_builder/example/lib/main.g.dart +++ b/packages/go_router_builder/example/lib/main.g.dart @@ -59,17 +59,6 @@ extension $HomeRouteExtension on HomeRoute { extension $FamilyRouteExtension on FamilyRoute { static FamilyRoute _fromState(GoRouterState state) => const FamilyRoute(); - - String get location => GoRouteData.$location( - '/', - ); - - void go(BuildContext context) => context.go(location, extra: this); - - void push(BuildContext context) => context.push(location, extra: this); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: this); } extension $FamilyIdRouteExtension on FamilyIdRoute { diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index acf5dab1df9..2848341ca1d 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -115,6 +115,7 @@ class RouteConfig { return value; } + final List _children = []; final String _path; final InterfaceElement _routeDataClass; @@ -183,7 +184,15 @@ class RouteConfig { } /// Returns `extension` code. - String _extensionDefinition() => ''' + String _extensionDefinition() { + if (_isShellRoute) { + return ''' +extension $_extensionName on $_className { + static $_className _fromState(GoRouterState state) $_newFromState +} +'''; + } + return ''' extension $_extensionName on $_className { static $_className _fromState(GoRouterState state) $_newFromState @@ -199,6 +208,7 @@ extension $_extensionName on $_className { context.pushReplacement(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); } '''; + } /// Returns this [RouteConfig] and all child [RouteConfig] instances. Iterable _flatten() sync* { From 6ed5535a8c0b70c01a31eb408c16dab0eb6582e7 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Fri, 10 Mar 2023 10:53:16 +0800 Subject: [PATCH 09/29] Add GoRouterShellGenerator --- .../lib/go_router_builder.dart | 6 +- .../lib/src/go_router_shell_generator.dart | 86 +++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 packages/go_router_builder/lib/src/go_router_shell_generator.dart diff --git a/packages/go_router_builder/lib/go_router_builder.dart b/packages/go_router_builder/lib/go_router_builder.dart index 2a4cc6bd541..919046bb4e0 100644 --- a/packages/go_router_builder/lib/go_router_builder.dart +++ b/packages/go_router_builder/lib/go_router_builder.dart @@ -16,12 +16,16 @@ import 'package:build/build.dart'; import 'package:source_gen/source_gen.dart'; import 'src/go_router_generator.dart'; +import 'src/go_router_shell_generator.dart'; /// Supports `package:build_runner` creation and configuration of /// `go_router`. /// /// Not meant to be invoked by hand-authored code. Builder goRouterBuilder(BuilderOptions options) => SharedPartBuilder( - const [GoRouterGenerator()], + const [ + GoRouterGenerator(), + GoRouterShellGenerator(), + ], 'go_router', ); diff --git a/packages/go_router_builder/lib/src/go_router_shell_generator.dart b/packages/go_router_builder/lib/src/go_router_shell_generator.dart new file mode 100644 index 00000000000..8a3c7a659b3 --- /dev/null +++ b/packages/go_router_builder/lib/src/go_router_shell_generator.dart @@ -0,0 +1,86 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:build/build.dart'; +import 'package:source_gen/source_gen.dart'; + +import 'route_config.dart'; + +/// A [Generator] for classes annotated with `TypedShellRoute`. +class GoRouterShellGenerator extends GeneratorForAnnotation { + /// Creates a new instance of [GoRouterShellGenerator]. + const GoRouterShellGenerator(); + + @override + TypeChecker get typeChecker => const TypeChecker.fromUrl( + 'package:go_router/src/route_data.dart#TypedShellRoute', + ); + + @override + FutureOr generate(LibraryReader library, BuildStep buildStep) async { + final Set values = {}; + + final Set getters = {}; + + for (final AnnotatedElement annotatedElement + in library.annotatedWith(typeChecker)) { + final InfoIterable generatedValue = generateForAnnotatedElement( + annotatedElement.element, + annotatedElement.annotation, + buildStep, + ); + getters.add(generatedValue.routeGetterName); + for (final String value in generatedValue) { + assert(value.length == value.trim().length); + values.add(value); + } + } + + if (values.isEmpty) { + return ''; + } + + return [ + ''' +List get \$appRoutes => [ +${getters.map((String e) => "$e,").join('\n')} + ]; +''', + ...values, + ].join('\n\n'); + } + + @override + InfoIterable generateForAnnotatedElement( + Element element, + ConstantReader annotation, + BuildStep buildStep, + ) { + if (element is! ClassElement) { + throw InvalidGenerationSourceError( + 'The @TypedShellRoute annotation can only be applied to classes.', + element: element, + ); + } + + if (!element.allSupertypes.any((InterfaceType element) => + _shellRouteDataChecker.isExactlyType(element))) { + throw InvalidGenerationSourceError( + 'The @TypedShellRoute annotation can only be applied to classes that ' + 'extend or implement `ShellRouteData`.', + element: element, + ); + } + + return RouteConfig.fromAnnotation(annotation, element).generateMembers(); + } +} + +const TypeChecker _shellRouteDataChecker = TypeChecker.fromUrl( + 'package:go_router/src/route_data.dart#ShellRouteData', +); From ce95e76d1720c4173c7eea5e9c5cd797c4d95d95 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Fri, 10 Mar 2023 10:53:21 +0800 Subject: [PATCH 10/29] Add example --- .../example/lib/shell_route_example.dart | 132 ++++++++++++++++++ .../example/lib/shell_route_example.g.dart | 62 ++++++++ 2 files changed, 194 insertions(+) create mode 100644 packages/go_router_builder/example/lib/shell_route_example.dart create mode 100644 packages/go_router_builder/example/lib/shell_route_example.g.dart diff --git a/packages/go_router_builder/example/lib/shell_route_example.dart b/packages/go_router_builder/example/lib/shell_route_example.dart new file mode 100644 index 00000000000..37ae3f5926d --- /dev/null +++ b/packages/go_router_builder/example/lib/shell_route_example.dart @@ -0,0 +1,132 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +part '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 location = GoRouter.of(context).location; + if (location == '/bar') return 1; + return 0; + } + + @override + Widget build(BuildContext context) { + final currentIndex = getCurrentIndex(context); + return Scaffold( + 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'); + } +} diff --git a/packages/go_router_builder/example/lib/shell_route_example.g.dart b/packages/go_router_builder/example/lib/shell_route_example.g.dart new file mode 100644 index 00000000000..4c569504369 --- /dev/null +++ b/packages/go_router_builder/example/lib/shell_route_example.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: always_specify_types, public_member_api_docs + +part of 'shell_route_example.dart'; + +// ************************************************************************** +// GoRouterShellGenerator +// ************************************************************************** + +List get $appRoutes => [ + $myShellRouteData, + ]; + +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, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: this); +} + +extension $BarRouteDataExtension on BarRouteData { + static BarRouteData _fromState(GoRouterState state) => const BarRouteData(); + + String get location => GoRouteData.$location( + '/bar', + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: this); +} From 1e2606f14be05998973e4aa26fe3170f22bd58c1 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Sat, 11 Mar 2023 01:04:47 +0800 Subject: [PATCH 11/29] Lint shell example --- .../example/lib/shell_route_example.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 37ae3f5926d..e29ea71633c 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.dart @@ -77,14 +77,16 @@ class MyShellRouteScreen extends StatelessWidget { final Widget child; int getCurrentIndex(BuildContext context) { - final location = GoRouter.of(context).location; - if (location == '/bar') return 1; + final String location = GoRouter.of(context).location; + if (location == '/bar') { + return 1; + } return 0; } @override Widget build(BuildContext context) { - final currentIndex = getCurrentIndex(context); + final int currentIndex = getCurrentIndex(context); return Scaffold( bottomNavigationBar: BottomNavigationBar( currentIndex: currentIndex, From 548882ac28c7196aa286f05384bec1c4c34edfcf Mon Sep 17 00:00:00 2001 From: GP4cK Date: Sat, 11 Mar 2023 09:11:10 +0800 Subject: [PATCH 12/29] Revert changes on example/main.dart --- .../go_router_builder/example/lib/main.dart | 64 ++++--------------- .../go_router_builder/example/lib/main.g.dart | 24 ++----- 2 files changed, 21 insertions(+), 67 deletions(-) diff --git a/packages/go_router_builder/example/lib/main.dart b/packages/go_router_builder/example/lib/main.dart index 6bc3194e622..f6a64edf184 100644 --- a/packages/go_router_builder/example/lib/main.dart +++ b/packages/go_router_builder/example/lib/main.dart @@ -35,7 +35,6 @@ class App extends StatelessWidget { late final GoRouter _router = GoRouter( debugLogDiagnostics: true, routes: $appRoutes, - navigatorKey: key, // redirect to the login page if the user is not logged in redirect: (BuildContext context, GoRouterState state) { @@ -64,26 +63,20 @@ class App extends StatelessWidget { ); } -const GlobalObjectKey key = GlobalObjectKey('navigator_key'); - @TypedGoRoute( path: '/', - routes: >[ - TypedShellRoute( + routes: >[ + TypedGoRoute( + path: 'family/:fid', routes: >[ - TypedGoRoute( - path: 'family/:fid', - routes: >[ - TypedGoRoute( - path: 'person/:pid', - routes: >[ - TypedGoRoute(path: 'details/:details'), - ], - ), + TypedGoRoute( + path: 'person/:pid', + routes: >[ + TypedGoRoute(path: 'details/:details'), ], ), ], - ), + ) ], ) class HomeRoute extends GoRouteData { @@ -93,17 +86,6 @@ class HomeRoute extends GoRouteData { Widget build(BuildContext context, GoRouterState state) => const HomeScreen(); } -class FamilyRoute extends ShellRouteData { - const FamilyRoute(); - - static final GlobalKey $navigatorKey = GlobalKey(); - - @override - Widget builder(BuildContext context, GoRouterState state, Widget navigator) { - return FamilyScreen(child: navigator); - } -} - @TypedGoRoute( path: '/login', ) @@ -117,15 +99,14 @@ class LoginRoute extends GoRouteData { LoginScreen(from: fromPage); } -class FamilyIdRoute extends GoRouteData { - const FamilyIdRoute(this.fid); +class FamilyRoute extends GoRouteData { + const FamilyRoute(this.fid); final String fid; @override - Widget build(BuildContext context, GoRouterState state) => FamilyIdScreen( - family: familyById(fid), - ); + Widget build(BuildContext context, GoRouterState state) => + FamilyScreen(family: familyById(fid)); } class PersonRoute extends GoRouteData { @@ -196,7 +177,7 @@ class HomeScreen extends StatelessWidget { for (final Family f in familyData) ListTile( title: Text(f.name), - onTap: () => FamilyIdRoute(f.id).go(context), + onTap: () => FamilyRoute(f.id).go(context), ) ], ), @@ -205,23 +186,7 @@ class HomeScreen extends StatelessWidget { } class FamilyScreen extends StatelessWidget { - const FamilyScreen({ - required this.child, - super.key, - }); - - final Widget child; - - @override - Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.all(20), - child: child, - ); -} - -class FamilyIdScreen extends StatelessWidget { - const FamilyIdScreen({required this.family, super.key}); - + const FamilyScreen({required this.family, super.key}); final Family family; @override @@ -314,7 +279,6 @@ class PersonDetailsPage extends StatelessWidget { class LoginScreen extends StatelessWidget { const LoginScreen({this.from, super.key}); - final String? from; @override diff --git a/packages/go_router_builder/example/lib/main.g.dart b/packages/go_router_builder/example/lib/main.g.dart index d82fe541dbf..ba15ccd83f7 100644 --- a/packages/go_router_builder/example/lib/main.g.dart +++ b/packages/go_router_builder/example/lib/main.g.dart @@ -17,23 +17,17 @@ RouteBase get $homeRoute => GoRouteData.$route( path: '/', factory: $HomeRouteExtension._fromState, routes: [ - ShellRouteData.$route( + GoRouteData.$route( + path: 'family/:fid', factory: $FamilyRouteExtension._fromState, - navigatorKey: FamilyRoute.$navigatorKey, routes: [ GoRouteData.$route( - path: 'family/:fid', - factory: $FamilyIdRouteExtension._fromState, + path: 'person/:pid', + factory: $PersonRouteExtension._fromState, routes: [ GoRouteData.$route( - path: 'person/:pid', - factory: $PersonRouteExtension._fromState, - routes: [ - GoRouteData.$route( - path: 'details/:details', - factory: $PersonDetailsRouteExtension._fromState, - ), - ], + path: 'details/:details', + factory: $PersonDetailsRouteExtension._fromState, ), ], ), @@ -58,11 +52,7 @@ extension $HomeRouteExtension on HomeRoute { } extension $FamilyRouteExtension on FamilyRoute { - static FamilyRoute _fromState(GoRouterState state) => const FamilyRoute(); -} - -extension $FamilyIdRouteExtension on FamilyIdRoute { - static FamilyIdRoute _fromState(GoRouterState state) => FamilyIdRoute( + static FamilyRoute _fromState(GoRouterState state) => FamilyRoute( state.params['fid']!, ); From 7b1305bcad194d153212d5340d355e3c5f056362 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Sat, 11 Mar 2023 09:55:49 +0800 Subject: [PATCH 13/29] Re-run build_runner after merge --- .../example/lib/shell_route_example.g.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/go_router_builder/example/lib/shell_route_example.g.dart b/packages/go_router_builder/example/lib/shell_route_example.g.dart index 4c569504369..d941ea8d7b0 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.g.dart @@ -38,12 +38,12 @@ extension $FooRouteDataExtension on FooRouteData { '/foo', ); - void go(BuildContext context) => context.go(location, extra: this); + void go(BuildContext context) => context.go(location); - void push(BuildContext context) => context.push(location, extra: this); + void push(BuildContext context) => context.push(location); void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: this); + context.pushReplacement(location); } extension $BarRouteDataExtension on BarRouteData { @@ -53,10 +53,10 @@ extension $BarRouteDataExtension on BarRouteData { '/bar', ); - void go(BuildContext context) => context.go(location, extra: this); + void go(BuildContext context) => context.go(location); - void push(BuildContext context) => context.push(location, extra: this); + void push(BuildContext context) => context.push(location); void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: this); + context.pushReplacement(location); } From 75bff0927c02ea323a066fbbaabc18726a37d7f2 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Sat, 11 Mar 2023 14:51:58 +0800 Subject: [PATCH 14/29] Fix go_router_builder test --- .../test/test_inputs/_go_router_builder_test_input.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart index 2a1f2b17c99..515ba16a521 100644 --- a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart +++ b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart @@ -162,7 +162,7 @@ class DefaultValueRoute extends GoRouteData { } @ShouldGenerate(r''' -GoRoute get $extraValueRoute => GoRouteData.$route( +RouteBase get $extraValueRoute => GoRouteData.$route( path: '/default-value-route', factory: $ExtraValueRouteExtension._fromState, ); @@ -215,7 +215,7 @@ class NullableDefaultValueRoute extends GoRouteData { } @ShouldGenerate(r''' -GoRoute get $iterableWithEnumRoute => GoRouteData.$route( +RouteBase get $iterableWithEnumRoute => GoRouteData.$route( path: '/iterable-with-enum', factory: $IterableWithEnumRouteExtension._fromState, ); From 76b9b81ec95f3a85739a30b5c797edee66a593bf Mon Sep 17 00:00:00 2001 From: GP4cK Date: Sat, 11 Mar 2023 14:55:24 +0800 Subject: [PATCH 15/29] Combine generators --- .../example/lib/shell_route_example.g.dart | 2 +- .../lib/go_router_builder.dart | 11 ++- .../lib/src/go_router_generator.dart | 29 ++++--- .../lib/src/go_router_shell_generator.dart | 86 ------------------- .../go_router_builder/test/builder_test.dart | 5 +- 5 files changed, 32 insertions(+), 101 deletions(-) delete mode 100644 packages/go_router_builder/lib/src/go_router_shell_generator.dart diff --git a/packages/go_router_builder/example/lib/shell_route_example.g.dart b/packages/go_router_builder/example/lib/shell_route_example.g.dart index d941ea8d7b0..578aae876f1 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.g.dart @@ -5,7 +5,7 @@ part of 'shell_route_example.dart'; // ************************************************************************** -// GoRouterShellGenerator +// GoRouterGenerator // ************************************************************************** List get $appRoutes => [ diff --git a/packages/go_router_builder/lib/go_router_builder.dart b/packages/go_router_builder/lib/go_router_builder.dart index 919046bb4e0..a989c07fb58 100644 --- a/packages/go_router_builder/lib/go_router_builder.dart +++ b/packages/go_router_builder/lib/go_router_builder.dart @@ -16,7 +16,6 @@ import 'package:build/build.dart'; import 'package:source_gen/source_gen.dart'; import 'src/go_router_generator.dart'; -import 'src/go_router_shell_generator.dart'; /// Supports `package:build_runner` creation and configuration of /// `go_router`. @@ -24,8 +23,14 @@ import 'src/go_router_shell_generator.dart'; /// Not meant to be invoked by hand-authored code. Builder goRouterBuilder(BuilderOptions options) => SharedPartBuilder( const [ - GoRouterGenerator(), - GoRouterShellGenerator(), + GoRouterGenerator( + annotation: 'TypedGoRoute', + routeClass: 'GoRouteData', + ), + GoRouterGenerator( + annotation: 'TypedShellRoute', + routeClass: 'ShellRouteData', + ), ], 'go_router', ); 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 583e7ba5ae8..e2b6a437c74 100644 --- a/packages/go_router_builder/lib/src/go_router_generator.dart +++ b/packages/go_router_builder/lib/src/go_router_generator.dart @@ -11,14 +11,27 @@ import 'package:source_gen/source_gen.dart'; import 'route_config.dart'; -/// A [Generator] for classes annotated with `TypedGoRoute`. +/// A [Generator] for classes annotated with a typed go route annotation. class GoRouterGenerator extends GeneratorForAnnotation { /// Creates a new instance of [GoRouterGenerator]. - const GoRouterGenerator(); + const GoRouterGenerator({ + required this.annotation, + required this.routeClass, + }); + + /// The typed go route annotation. + final String annotation; + + /// The route data class. + final String routeClass; @override - TypeChecker get typeChecker => const TypeChecker.fromUrl( - 'package:go_router/src/route_data.dart#TypedGoRoute', + TypeChecker get typeChecker => TypeChecker.fromUrl( + 'package:go_router/src/route_data.dart#$annotation', + ); + + TypeChecker get _goRouteDataChecker => TypeChecker.fromUrl( + 'package:go_router/src/route_data.dart#$routeClass', ); @override @@ -63,7 +76,7 @@ ${getters.map((String e) => "$e,").join('\n')} ) { if (element is! ClassElement) { throw InvalidGenerationSourceError( - 'The @TypedGoRoute annotation can only be applied to classes.', + 'The @${this.annotation} annotation can only be applied to classes.', element: element, ); } @@ -71,7 +84,7 @@ ${getters.map((String e) => "$e,").join('\n')} if (!element.allSupertypes.any((InterfaceType element) => _goRouteDataChecker.isExactlyType(element))) { throw InvalidGenerationSourceError( - 'The @TypedGoRoute annotation can only be applied to classes that ' + 'The @${this.annotation} annotation can only be applied to classes that ' 'extend or implement `GoRouteData`.', element: element, ); @@ -80,7 +93,3 @@ ${getters.map((String e) => "$e,").join('\n')} return RouteConfig.fromAnnotation(annotation, element).generateMembers(); } } - -const TypeChecker _goRouteDataChecker = TypeChecker.fromUrl( - 'package:go_router/src/route_data.dart#GoRouteData', -); diff --git a/packages/go_router_builder/lib/src/go_router_shell_generator.dart b/packages/go_router_builder/lib/src/go_router_shell_generator.dart deleted file mode 100644 index 8a3c7a659b3..00000000000 --- a/packages/go_router_builder/lib/src/go_router_shell_generator.dart +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.dart'; -import 'package:build/build.dart'; -import 'package:source_gen/source_gen.dart'; - -import 'route_config.dart'; - -/// A [Generator] for classes annotated with `TypedShellRoute`. -class GoRouterShellGenerator extends GeneratorForAnnotation { - /// Creates a new instance of [GoRouterShellGenerator]. - const GoRouterShellGenerator(); - - @override - TypeChecker get typeChecker => const TypeChecker.fromUrl( - 'package:go_router/src/route_data.dart#TypedShellRoute', - ); - - @override - FutureOr generate(LibraryReader library, BuildStep buildStep) async { - final Set values = {}; - - final Set getters = {}; - - for (final AnnotatedElement annotatedElement - in library.annotatedWith(typeChecker)) { - final InfoIterable generatedValue = generateForAnnotatedElement( - annotatedElement.element, - annotatedElement.annotation, - buildStep, - ); - getters.add(generatedValue.routeGetterName); - for (final String value in generatedValue) { - assert(value.length == value.trim().length); - values.add(value); - } - } - - if (values.isEmpty) { - return ''; - } - - return [ - ''' -List get \$appRoutes => [ -${getters.map((String e) => "$e,").join('\n')} - ]; -''', - ...values, - ].join('\n\n'); - } - - @override - InfoIterable generateForAnnotatedElement( - Element element, - ConstantReader annotation, - BuildStep buildStep, - ) { - if (element is! ClassElement) { - throw InvalidGenerationSourceError( - 'The @TypedShellRoute annotation can only be applied to classes.', - element: element, - ); - } - - if (!element.allSupertypes.any((InterfaceType element) => - _shellRouteDataChecker.isExactlyType(element))) { - throw InvalidGenerationSourceError( - 'The @TypedShellRoute annotation can only be applied to classes that ' - 'extend or implement `ShellRouteData`.', - element: element, - ); - } - - return RouteConfig.fromAnnotation(annotation, element).generateMembers(); - } -} - -const TypeChecker _shellRouteDataChecker = TypeChecker.fromUrl( - 'package:go_router/src/route_data.dart#ShellRouteData', -); diff --git a/packages/go_router_builder/test/builder_test.dart b/packages/go_router_builder/test/builder_test.dart index f9124ed4520..7fd6ba370de 100644 --- a/packages/go_router_builder/test/builder_test.dart +++ b/packages/go_router_builder/test/builder_test.dart @@ -16,7 +16,10 @@ Future main() async { testAnnotatedElements( testReader, - const GoRouterGenerator(), + const GoRouterGenerator( + annotation: 'TypedGoRoute', + routeClass: 'GoRouteData', + ), expectedAnnotatedTests: _expectedAnnotatedTests, ); } From 4600f3795b58653eff4618e96fdf0c8fbafcdb31 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Tue, 14 Mar 2023 09:08:11 +0800 Subject: [PATCH 16/29] Use element instead of element2 --- packages/go_router_builder/lib/src/route_config.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 24728d6a8ce..26a16226a68 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -69,10 +69,10 @@ class RouteConfig { ) { assert(!reader.isNull, 'reader should not be null'); final InterfaceType type = reader.objectValue.type! as InterfaceType; - // Ignore the deprected `element2` so that the "downgraded_analyze" CI step + // Ignore the deprecated `element2` so that the "downgraded_analyze" CI step // passes. //ignore: deprecated_member_use - final bool isShellRoute = type.element2.name == 'TypedShellRoute'; + final bool isShellRoute = type.element.name == 'TypedShellRoute'; String? path; From 3efeecd1c3823bf2abfc7d067ce20a992fa8a2f5 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Tue, 14 Mar 2023 09:57:27 +0800 Subject: [PATCH 17/29] Removed commented import --- packages/go_router_builder/example/test/all_types_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/go_router_builder/example/test/all_types_test.dart b/packages/go_router_builder/example/test/all_types_test.dart index 4b0cb3fb979..83174847fe1 100644 --- a/packages/go_router_builder/example/test/all_types_test.dart +++ b/packages/go_router_builder/example/test/all_types_test.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// import 'package:flutter/material.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router_builder_example/all_types.dart'; From fb449ff275a80230fc6656298eca347e744f82e6 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Tue, 14 Mar 2023 10:09:52 +0800 Subject: [PATCH 18/29] Add test on shell_route_example --- .../example/lib/shell_route_example.dart | 1 + .../example/test/shell_route_test.dart | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 packages/go_router_builder/example/test/shell_route_test.dart 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 e29ea71633c..79ee4878d87 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.dart @@ -88,6 +88,7 @@ class MyShellRouteScreen extends StatelessWidget { Widget build(BuildContext context) { final int currentIndex = getCurrentIndex(context); return Scaffold( + body: child, bottomNavigationBar: BottomNavigationBar( currentIndex: currentIndex, items: const [ diff --git a/packages/go_router_builder/example/test/shell_route_test.dart b/packages/go_router_builder/example/test/shell_route_test.dart new file mode 100644 index 00000000000..3919970209d --- /dev/null +++ b/packages/go_router_builder/example/test/shell_route_test.dart @@ -0,0 +1,17 @@ +// 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/shell_route_example.dart'; + +void main() { + testWidgets('Navigate from /foo to /bar', (WidgetTester tester) async { + await tester.pumpWidget(App()); + expect(find.byType(FooScreen), findsOneWidget); + + await tester.tap(find.text('Bar')); + await tester.pumpAndSettle(); + expect(find.byType(BarScreen), findsOneWidget); + }); +} From f0924edc8da7090855bcd33a646c3e91f5d2bad6 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Tue, 14 Mar 2023 10:40:33 +0800 Subject: [PATCH 19/29] Remove useless comment --- packages/go_router_builder/lib/src/route_config.dart | 3 --- 1 file changed, 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 26a16226a68..77a4c732d2d 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -69,9 +69,6 @@ class RouteConfig { ) { assert(!reader.isNull, 'reader should not be null'); final InterfaceType type = reader.objectValue.type! as InterfaceType; - // Ignore the deprecated `element2` so that the "downgraded_analyze" CI step - // passes. - //ignore: deprecated_member_use final bool isShellRoute = type.element.name == 'TypedShellRoute'; String? path; From 6eab71755acacc3e13c73902cced93ca780d9aa1 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Tue, 14 Mar 2023 11:33:13 +0800 Subject: [PATCH 20/29] Fix navigatorKey vs parentNavigatorKey --- packages/go_router_builder/lib/src/route_config.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 77a4c732d2d..68dbd52cbc9 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -325,8 +325,11 @@ RouteBase get $_routeGetterName => ${_routeDefinition()}; : ''' routes: [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}], '''; - final String navigatorKey = - _key == null || _key!.isEmpty ? '' : 'navigatorKey: $_key,'; + final String navigatorKeyParameterName = + _isShellRoute ? 'navigatorKey' : 'parentNavigatorKey'; + final String navigatorKey = _key == null || _key!.isEmpty + ? '' + : '$navigatorKeyParameterName: $_key,'; if (_isShellRoute) { return ''' ShellRouteData.\$route( From 9f8b0670b911abe83ad8bc00a13ae1ebab0bea2b Mon Sep 17 00:00:00 2001 From: GP4cK Date: Wed, 15 Mar 2023 09:40:03 +0800 Subject: [PATCH 21/29] Ignore deprecation warning --- packages/go_router_builder/lib/src/route_config.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 68dbd52cbc9..7e8500b425e 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -69,6 +69,9 @@ class RouteConfig { ) { assert(!reader.isNull, 'reader should not be null'); final InterfaceType type = reader.objectValue.type! as InterfaceType; + // 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'; String? path; From 7f4c6e0963bb6a89184193a533e33fc1c1023302 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Thu, 16 Mar 2023 16:21:10 +0800 Subject: [PATCH 22/29] Combine GoRouteGenerator to generate a single $appRoutes --- .../example/lib/shell_route_example.dart | 18 ++++ .../example/lib/shell_route_example.g.dart | 21 +++++ .../lib/go_router_builder.dart | 11 +-- .../lib/src/go_router_generator.dart | 83 +++++++++++-------- .../go_router_builder/test/builder_test.dart | 5 +- 5 files changed, 90 insertions(+), 48 deletions(-) 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 79ee4878d87..2662ba83d37 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.dart @@ -133,3 +133,21 @@ class BarScreen extends StatelessWidget { 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/shell_route_example.g.dart b/packages/go_router_builder/example/lib/shell_route_example.g.dart index 578aae876f1..531502515a0 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.g.dart @@ -9,9 +9,30 @@ part of '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); + + void push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); +} + RouteBase get $myShellRouteData => ShellRouteData.$route( factory: $MyShellRouteDataExtension._fromState, routes: [ diff --git a/packages/go_router_builder/lib/go_router_builder.dart b/packages/go_router_builder/lib/go_router_builder.dart index a989c07fb58..2a4cc6bd541 100644 --- a/packages/go_router_builder/lib/go_router_builder.dart +++ b/packages/go_router_builder/lib/go_router_builder.dart @@ -22,15 +22,6 @@ import 'src/go_router_generator.dart'; /// /// Not meant to be invoked by hand-authored code. Builder goRouterBuilder(BuilderOptions options) => SharedPartBuilder( - const [ - GoRouterGenerator( - annotation: 'TypedGoRoute', - routeClass: 'GoRouteData', - ), - GoRouterGenerator( - annotation: 'TypedShellRoute', - routeClass: 'ShellRouteData', - ), - ], + const [GoRouterGenerator()], 'go_router', ); 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 e2b6a437c74..5a1767022db 100644 --- a/packages/go_router_builder/lib/src/go_router_generator.dart +++ b/packages/go_router_builder/lib/src/go_router_generator.dart @@ -11,47 +11,33 @@ import 'package:source_gen/source_gen.dart'; import 'route_config.dart'; +const String _routeDataUrl = 'package:go_router/src/route_data.dart'; + +const Map _annotations = { + 'TypedGoRoute': 'GoRouteData', + 'TypedShellRoute': 'ShellRouteData', +}; + /// A [Generator] for classes annotated with a typed go route annotation. class GoRouterGenerator extends GeneratorForAnnotation { /// Creates a new instance of [GoRouterGenerator]. - const GoRouterGenerator({ - required this.annotation, - required this.routeClass, - }); - - /// The typed go route annotation. - final String annotation; - - /// The route data class. - final String routeClass; + const GoRouterGenerator(); @override - TypeChecker get typeChecker => TypeChecker.fromUrl( - 'package:go_router/src/route_data.dart#$annotation', - ); - - TypeChecker get _goRouteDataChecker => TypeChecker.fromUrl( - 'package:go_router/src/route_data.dart#$routeClass', + TypeChecker get typeChecker => TypeChecker.any( + _annotations.keys.map((String annotation) => + TypeChecker.fromUrl('$_routeDataUrl#$annotation')), ); @override FutureOr generate(LibraryReader library, BuildStep buildStep) async { final Set values = {}; - final Set getters = {}; - for (final AnnotatedElement annotatedElement - in library.annotatedWith(typeChecker)) { - final InfoIterable generatedValue = generateForAnnotatedElement( - annotatedElement.element, - annotatedElement.annotation, - buildStep, - ); - getters.add(generatedValue.routeGetterName); - for (final String value in generatedValue) { - assert(value.length == value.trim().length); - values.add(value); - } + for (final String annotation in _annotations.keys) { + final TypeChecker typeChecker = + TypeChecker.fromUrl('$_routeDataUrl#$annotation'); + _generateForAnnotation(library, typeChecker, buildStep, values, getters); } if (values.isEmpty) { @@ -68,24 +54,53 @@ ${getters.map((String e) => "$e,").join('\n')} ].join('\n\n'); } + void _generateForAnnotation( + LibraryReader library, + TypeChecker typeChecker, + BuildStep buildStep, + Set values, + Set getters, + ) { + for (final AnnotatedElement annotatedElement + in library.annotatedWith(typeChecker)) { + final InfoIterable generatedValue = generateForAnnotatedElement( + annotatedElement.element, + annotatedElement.annotation, + buildStep, + ); + getters.add(generatedValue.routeGetterName); + for (final String value in generatedValue) { + assert(value.length == value.trim().length); + values.add(value); + } + } + } + @override InfoIterable generateForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep, ) { + final String typedAnnotation = + annotation.objectValue.type!.getDisplayString(withNullability: false); + final String type = + typedAnnotation.substring(0, typedAnnotation.indexOf('<')); + final String routeData = _annotations[type]!; if (element is! ClassElement) { throw InvalidGenerationSourceError( - 'The @${this.annotation} annotation can only be applied to classes.', + 'The @$type annotation can only be applied to classes.', element: element, ); } - if (!element.allSupertypes.any((InterfaceType element) => - _goRouteDataChecker.isExactlyType(element))) { + final TypeChecker dataChecker = + TypeChecker.fromUrl('$_routeDataUrl#$routeData'); + if (!element.allSupertypes + .any((InterfaceType element) => dataChecker.isExactlyType(element))) { throw InvalidGenerationSourceError( - 'The @${this.annotation} annotation can only be applied to classes that ' - 'extend or implement `GoRouteData`.', + 'The @$type annotation can only be applied to classes that ' + 'extend or implement `$routeData`.', element: element, ); } diff --git a/packages/go_router_builder/test/builder_test.dart b/packages/go_router_builder/test/builder_test.dart index 7fd6ba370de..f9124ed4520 100644 --- a/packages/go_router_builder/test/builder_test.dart +++ b/packages/go_router_builder/test/builder_test.dart @@ -16,10 +16,7 @@ Future main() async { testAnnotatedElements( testReader, - const GoRouterGenerator( - annotation: 'TypedGoRoute', - routeClass: 'GoRouteData', - ), + const GoRouterGenerator(), expectedAnnotatedTests: _expectedAnnotatedTests, ); } From b08bc6df726056bc40b04f8a0c585776710bef7c Mon Sep 17 00:00:00 2001 From: GP4cK Date: Fri, 17 Mar 2023 13:41:35 +0800 Subject: [PATCH 23/29] Fix builder test after merge --- .../test/test_inputs/_go_router_builder_test_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart index f7867a909a1..2b843c1e8d7 100644 --- a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart +++ b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart @@ -269,7 +269,7 @@ enum EnumOnlyUsedInIterable { } @ShouldGenerate(r''' -GoRoute get $iterableDefaultValueRoute => GoRouteData.$route( +RouteBase get $iterableDefaultValueRoute => GoRouteData.$route( path: '/iterable-default-value-route', factory: $IterableDefaultValueRouteExtension._fromState, ); From 61c3d9d077012650e0ce82ffb51f25c0892bac20 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Tue, 4 Apr 2023 11:25:30 +0800 Subject: [PATCH 24/29] Use $navigatorKey / $parentNavigatorKey --- packages/go_router_builder/lib/src/route_config.dart | 12 +++++++++--- 1 file changed, 9 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 7e8500b425e..9d7c1d97b16 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -106,7 +106,10 @@ class RouteConfig { path ?? '', classElement, parent, - _decodeKey(classElement), + _generateNavigatorKeyGetterCode( + classElement, + keyName: isShellRoute ? r'$navigatorKey' : r'$parentNavigatorKey', + ), isShellRoute, ); @@ -123,9 +126,12 @@ class RouteConfig { final String? _key; final bool _isShellRoute; - static String? _decodeKey(InterfaceElement classElement) { + static String? _generateNavigatorKeyGetterCode( + InterfaceElement classElement, { + required String keyName, + }) { bool whereStatic(FieldElement element) => element.isStatic; - bool whereKeyName(FieldElement element) => element.name == r'$navigatorKey'; + bool whereKeyName(FieldElement element) => element.name == keyName; final String? fieldDisplayName = classElement.fields .where(whereStatic) .where(whereKeyName) From 3013a05f73a178e8926b3a7f3f4e7158a373ac0b Mon Sep 17 00:00:00 2001 From: GP4cK Date: Tue, 4 Apr 2023 11:26:20 +0800 Subject: [PATCH 25/29] Add example of navigation with keys --- .../lib/shell_route_with_keys_example.dart | 167 ++++++++++++++++++ .../lib/shell_route_with_keys_example.g.dart | 88 +++++++++ 2 files changed, 255 insertions(+) create mode 100644 packages/go_router_builder/example/lib/shell_route_with_keys_example.dart create mode 100644 packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart 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 new file mode 100644 index 00000000000..66a270e1d31 --- /dev/null +++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart @@ -0,0 +1,167 @@ +// 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 'shell_route_with_keys_example.g.dart'; + +void main() => runApp(App()); + +final GlobalKey rootNavigatorKey = GlobalKey(); +final GlobalKey shellNavigatorKey = GlobalKey(); + +class App extends StatelessWidget { + App({super.key}); + + @override + Widget build(BuildContext context) => MaterialApp.router( + routerConfig: _router, + ); + + final GoRouter _router = GoRouter( + routes: $appRoutes, + initialLocation: '/home', + navigatorKey: rootNavigatorKey, + ); +} + +@TypedShellRoute( + routes: >[ + TypedGoRoute(path: '/home'), + TypedGoRoute( + path: '/users', + routes: [TypedGoRoute(path: ':id')], + ), + ], +) +class MyShellRouteData extends ShellRouteData { + const MyShellRouteData(); + + static final GlobalKey $navigatorKey = shellNavigatorKey; + + @override + Widget builder(BuildContext context, GoRouterState state, Widget navigator) { + return MyShellRouteScreen(child: navigator); + } +} + +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.startsWith('/users')) { + return 1; + } + return 0; + } + + @override + Widget build(BuildContext context) { + final int selectedIndex = getCurrentIndex(context); + + return Scaffold( + body: Row( + children: [ + NavigationRail( + destinations: const [ + NavigationRailDestination( + icon: Icon(Icons.home), + label: Text('Home'), + ), + NavigationRailDestination( + icon: Icon(Icons.group), + label: Text('Users'), + ), + ], + selectedIndex: selectedIndex, + onDestinationSelected: (int index) { + switch (index) { + case 0: + const HomeRouteData().go(context); + break; + case 1: + const UsersRouteData().go(context); + break; + } + }, + ), + VerticalDivider(thickness: 1, width: 1), + Expanded(child: child), + ], + ), + ); + } +} + +class HomeRouteData extends GoRouteData { + const HomeRouteData(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const Center(child: Text('The home page')); + } +} + +class UsersRouteData extends GoRouteData { + const UsersRouteData(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return ListView( + children: [ + for (int userID = 1; userID <= 3; userID++) + ListTile( + title: Text('User $userID'), + onTap: () => UserRouteData(id: userID).go(context), + ), + ], + ); + } +} + +class DialogPage extends Page { + /// A page to display a dialog. + const DialogPage({required this.child, super.key}); + + /// The widget to be displayed which is usually a [Dialog] widget. + final Widget child; + + @override + Route createRoute(BuildContext context) { + return DialogRoute( + context: context, + settings: this, + builder: (context) => child, + ); + } +} + +class UserRouteData extends GoRouteData { + const UserRouteData({required this.id}); + + // Without this static key, the dialog will not cover the navigation rail. + static final GlobalKey $parentNavigatorKey = rootNavigatorKey; + + final int id; + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return DialogPage( + key: state.pageKey, + child: Center( + child: SizedBox( + width: 300, + height: 300, + child: Card(child: Center(child: Text('User $id'))), + ), + ), + ); + } +} 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 new file mode 100644 index 00000000000..523af4e9893 --- /dev/null +++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart @@ -0,0 +1,88 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: always_specify_types, public_member_api_docs + +part of 'shell_route_with_keys_example.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $myShellRouteData, + ]; + +RouteBase get $myShellRouteData => ShellRouteData.$route( + factory: $MyShellRouteDataExtension._fromState, + navigatorKey: MyShellRouteData.$navigatorKey, + routes: [ + GoRouteData.$route( + path: '/home', + factory: $HomeRouteDataExtension._fromState, + ), + GoRouteData.$route( + path: '/users', + factory: $UsersRouteDataExtension._fromState, + routes: [ + GoRouteData.$route( + path: ':id', + factory: $UserRouteDataExtension._fromState, + parentNavigatorKey: UserRouteData.$parentNavigatorKey, + ), + ], + ), + ], + ); + +extension $MyShellRouteDataExtension on MyShellRouteData { + static MyShellRouteData _fromState(GoRouterState state) => + const MyShellRouteData(); +} + +extension $HomeRouteDataExtension on HomeRouteData { + static HomeRouteData _fromState(GoRouterState state) => const HomeRouteData(); + + String get location => GoRouteData.$location( + '/home', + ); + + void go(BuildContext context) => context.go(location); + + void push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); +} + +extension $UsersRouteDataExtension on UsersRouteData { + static UsersRouteData _fromState(GoRouterState state) => + const UsersRouteData(); + + String get location => GoRouteData.$location( + '/users', + ); + + void go(BuildContext context) => context.go(location); + + void push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); +} + +extension $UserRouteDataExtension on UserRouteData { + static UserRouteData _fromState(GoRouterState state) => UserRouteData( + id: int.parse(state.params['id']!), + ); + + String get location => GoRouteData.$location( + '/users/${Uri.encodeComponent(id.toString())}', + ); + + void go(BuildContext context) => context.go(location); + + void push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); +} From 3376c7367804892dbe9cc1745ff2966778107f94 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Tue, 4 Apr 2023 11:38:20 +0800 Subject: [PATCH 26/29] Add section about navigator keys in README --- packages/go_router_builder/README.md | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index e7d3a96a617..8dec43113d7 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -327,3 +327,42 @@ class FancyRoute extends GoRouteData { ), } ``` + +## TypedShellRoute and navigator keys + +There may be situations were a child route of a shell needs to be displayed on a +different navigator. This kind of scenarios can be achieved by declaring a +**static** navigator key named: + +- `$navigatorKey` for ShellRoutes +- `$parentNavigatorKey` for GoRoutes + +Example: + +```dart +// For ShellRoutes: +final GlobalKey shellNavigatorKey = GlobalKey(); + +class MyShellRouteData extends ShellRouteData { + const MyShellRouteData(); + + static final GlobalKey $navigatorKey = shellNavigatorKey; + + @override + Widget builder(BuildContext context, GoRouterState state, Widget navigator) { + // ... + } +} + +// For GoRoutes: +class MyGoRouteData extends GoRouteData { + const MyGoRouteData(); + + static final GlobalKey $parentNavigatorKey = rootNavigatorKey; + + @override + Widget build(BuildContext context, GoRouterState state) { + // ... + } +} +``` From 183275376797cd2701ae3385624228ce031034ae Mon Sep 17 00:00:00 2001 From: GP4cK Date: Tue, 4 Apr 2023 11:46:59 +0800 Subject: [PATCH 27/29] Fix linter errors --- .../example/lib/shell_route_with_keys_example.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 66a270e1d31..bc6521e05fe 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 @@ -34,7 +34,9 @@ class App extends StatelessWidget { TypedGoRoute(path: '/home'), TypedGoRoute( path: '/users', - routes: [TypedGoRoute(path: ':id')], + routes: >[ + TypedGoRoute(path: ':id'), + ], ), ], ) @@ -92,7 +94,7 @@ class MyShellRouteScreen extends StatelessWidget { } }, ), - VerticalDivider(thickness: 1, width: 1), + const VerticalDivider(thickness: 1, width: 1), Expanded(child: child), ], ), @@ -126,7 +128,7 @@ class UsersRouteData extends GoRouteData { } } -class DialogPage extends Page { +class DialogPage extends Page { /// A page to display a dialog. const DialogPage({required this.child, super.key}); @@ -134,11 +136,11 @@ class DialogPage extends Page { final Widget child; @override - Route createRoute(BuildContext context) { - return DialogRoute( + Route createRoute(BuildContext context) { + return DialogRoute( context: context, settings: this, - builder: (context) => child, + builder: (BuildContext context) => child, ); } } From d7752fd6d2151d60b8391b7bdc3381d6ea806810 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Wed, 5 Apr 2023 08:50:28 +0800 Subject: [PATCH 28/29] Add link to example --- packages/go_router_builder/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index 8dec43113d7..e911d33be5c 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -366,3 +366,5 @@ class MyGoRouteData extends GoRouteData { } } ``` + +An example is available [here](https://github.com/flutter/packages/blob/main/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart). From 28e593f917696da42a0e0169b247796a464f6b30 Mon Sep 17 00:00:00 2001 From: GP4cK Date: Wed, 5 Apr 2023 09:15:22 +0800 Subject: [PATCH 29/29] Test shell_route_with_keys_example --- .../test/shell_route_with_keys_test.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 packages/go_router_builder/example/test/shell_route_with_keys_test.dart diff --git a/packages/go_router_builder/example/test/shell_route_with_keys_test.dart b/packages/go_router_builder/example/test/shell_route_with_keys_test.dart new file mode 100644 index 00000000000..2c7bd59aa89 --- /dev/null +++ b/packages/go_router_builder/example/test/shell_route_with_keys_test.dart @@ -0,0 +1,18 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router_builder_example/shell_route_with_keys_example.dart'; + +void main() { + testWidgets('Navigate from /home to /users', (WidgetTester tester) async { + await tester.pumpWidget(App()); + expect(find.text('The home page'), findsOneWidget); + + await tester.tap(find.byIcon(Icons.group)); + await tester.pumpAndSettle(); + expect(find.byType(ListTile), findsNWidgets(3)); + }); +}