diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index 2b9e741d8c6..70d841bc06b 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.1 * Support for the generation of the pushReplacement method has been added. 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 84f652da062..1d48b9e4e81 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..6bc3194e622 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 GlobalObjectKey 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 navigator) { + return FamilyScreen(child: navigator); + } +} + @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 @@ -279,6 +314,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 74b89bc933a..0650a13f210 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,11 @@ extension $HomeRouteExtension on HomeRoute { } extension $FamilyRouteExtension on FamilyRoute { - static FamilyRoute _fromState(GoRouterState state) => FamilyRoute( + static FamilyRoute _fromState(GoRouterState state) => const FamilyRoute(); +} + +extension $FamilyIdRouteExtension on FamilyIdRoute { + static FamilyIdRoute _fromState(GoRouterState state) => FamilyIdRoute( state.params['fid']!, ); @@ -118,7 +128,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 a71180d1e1d..e6285bf5360 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 38f7a8c2ceb..9ae033da6c7 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,26 @@ 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; + // Ignore the deprected `element2` so that the "downgraded_analyze" CI step + // passes. + //ignore: deprecated_member_use + 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,7 +102,13 @@ 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))); @@ -105,6 +120,39 @@ class RouteConfig { 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._( @@ -136,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 @@ -150,6 +206,7 @@ extension $_extensionName on $_className { context.pushReplacement(location, extra: this); } '''; + } /// Returns this [RouteConfig] and all child [RouteConfig] instances. Iterable _flatten() sync* { @@ -164,7 +221,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 @@ -257,11 +314,22 @@ 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 b4ae5b16982..2ca8bdc4b1d 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.1 +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 @@ -24,6 +24,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 c85e8e34867..2cc266febe4 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, ); @@ -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, );