[go_router_builder] Add ShellRoute support to go_router_builder (#3439)
[go_router_builder] Add ShellRoute support to go_router_builder
diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md
index b1f15fb..3fcea0b 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.7
* Supports default values for `Set`, `List` and `Iterable` route parameters.
diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md
index e7d3a96..e911d33 100644
--- a/packages/go_router_builder/README.md
+++ b/packages/go_router_builder/README.md
@@ -327,3 +327,44 @@
),
}
```
+
+## 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<NavigatorState> shellNavigatorKey = GlobalKey<NavigatorState>();
+
+class MyShellRouteData extends ShellRouteData {
+ const MyShellRouteData();
+
+ static final GlobalKey<NavigatorState> $navigatorKey = shellNavigatorKey;
+
+ @override
+ Widget builder(BuildContext context, GoRouterState state, Widget navigator) {
+ // ...
+ }
+}
+
+// For GoRoutes:
+class MyGoRouteData extends GoRouteData {
+ const MyGoRouteData();
+
+ static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
+
+ @override
+ Widget build(BuildContext context, GoRouterState state) {
+ // ...
+ }
+}
+```
+
+An example is available [here](https://github.com/flutter/packages/blob/main/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart).
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 f38dadc..0dc37d5 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 @@
// GoRouterGenerator
// **************************************************************************
-List<GoRoute> get $appRoutes => [
+List<RouteBase> 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.g.dart b/packages/go_router_builder/example/lib/main.g.dart
index ff7a236..ba15ccd 100644
--- a/packages/go_router_builder/example/lib/main.g.dart
+++ b/packages/go_router_builder/example/lib/main.g.dart
@@ -8,12 +8,12 @@
// GoRouterGenerator
// **************************************************************************
-List<GoRoute> get $appRoutes => [
+List<RouteBase> get $appRoutes => [
$homeRoute,
$loginRoute,
];
-GoRoute get $homeRoute => GoRouteData.$route(
+RouteBase get $homeRoute => GoRouteData.$route(
path: '/',
factory: $HomeRouteExtension._fromState,
routes: [
@@ -118,7 +118,7 @@
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/shell_route_example.dart b/packages/go_router_builder/example/lib/shell_route_example.dart
new file mode 100644
index 0000000..2662ba8
--- /dev/null
+++ b/packages/go_router_builder/example/lib/shell_route_example.dart
@@ -0,0 +1,153 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+part '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<MyShellRouteData>(
+ routes: <TypedRoute<RouteData>>[
+ TypedGoRoute<FooRouteData>(path: '/foo'),
+ TypedGoRoute<BarRouteData>(path: '/bar'),
+ ],
+)
+class MyShellRouteData extends ShellRouteData {
+ const MyShellRouteData();
+
+ @override
+ Widget builder(
+ BuildContext context,
+ GoRouterState state,
+ Widget navigator,
+ ) {
+ return MyShellRouteScreen(child: navigator);
+ }
+}
+
+class FooRouteData extends GoRouteData {
+ const FooRouteData();
+
+ @override
+ Widget build(BuildContext context, GoRouterState state) {
+ return const FooScreen();
+ }
+}
+
+class BarRouteData extends GoRouteData {
+ const BarRouteData();
+
+ @override
+ Widget build(BuildContext context, GoRouterState state) {
+ return const BarScreen();
+ }
+}
+
+class MyShellRouteScreen extends StatelessWidget {
+ const MyShellRouteScreen({required this.child, super.key});
+
+ final Widget child;
+
+ int getCurrentIndex(BuildContext context) {
+ final String location = GoRouter.of(context).location;
+ if (location == '/bar') {
+ return 1;
+ }
+ return 0;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final int currentIndex = getCurrentIndex(context);
+ return Scaffold(
+ body: child,
+ bottomNavigationBar: BottomNavigationBar(
+ currentIndex: currentIndex,
+ items: const <BottomNavigationBarItem>[
+ BottomNavigationBarItem(
+ icon: Icon(Icons.home),
+ label: 'Foo',
+ ),
+ BottomNavigationBarItem(
+ icon: Icon(Icons.business),
+ label: 'Bar',
+ ),
+ ],
+ onTap: (int index) {
+ switch (index) {
+ case 0:
+ const FooRouteData().go(context);
+ break;
+ case 1:
+ const BarRouteData().go(context);
+ break;
+ }
+ },
+ ),
+ );
+ }
+}
+
+class FooScreen extends StatelessWidget {
+ const FooScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const Text('Foo');
+ }
+}
+
+class BarScreen extends StatelessWidget {
+ const BarScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const Text('Bar');
+ }
+}
+
+@TypedGoRoute<LoginRoute>(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
new file mode 100644
index 0000000..5315025
--- /dev/null
+++ b/packages/go_router_builder/example/lib/shell_route_example.g.dart
@@ -0,0 +1,83 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// ignore_for_file: always_specify_types, public_member_api_docs
+
+part of 'shell_route_example.dart';
+
+// **************************************************************************
+// GoRouterGenerator
+// **************************************************************************
+
+List<RouteBase> 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: [
+ 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);
+
+ void push(BuildContext context) => context.push(location);
+
+ void pushReplacement(BuildContext context) =>
+ context.pushReplacement(location);
+}
+
+extension $BarRouteDataExtension on BarRouteData {
+ static BarRouteData _fromState(GoRouterState state) => const BarRouteData();
+
+ String get location => GoRouteData.$location(
+ '/bar',
+ );
+
+ void go(BuildContext context) => context.go(location);
+
+ void push(BuildContext context) => context.push(location);
+
+ void pushReplacement(BuildContext context) =>
+ context.pushReplacement(location);
+}
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 0000000..bc6521e
--- /dev/null
+++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart
@@ -0,0 +1,169 @@
+// 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<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
+final GlobalKey<NavigatorState> shellNavigatorKey = GlobalKey<NavigatorState>();
+
+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<MyShellRouteData>(
+ routes: <TypedRoute<RouteData>>[
+ TypedGoRoute<HomeRouteData>(path: '/home'),
+ TypedGoRoute<UsersRouteData>(
+ path: '/users',
+ routes: <TypedGoRoute<UserRouteData>>[
+ TypedGoRoute<UserRouteData>(path: ':id'),
+ ],
+ ),
+ ],
+)
+class MyShellRouteData extends ShellRouteData {
+ const MyShellRouteData();
+
+ static final GlobalKey<NavigatorState> $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: <Widget>[
+ NavigationRail(
+ destinations: const <NavigationRailDestination>[
+ 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;
+ }
+ },
+ ),
+ const 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: <Widget>[
+ for (int userID = 1; userID <= 3; userID++)
+ ListTile(
+ title: Text('User $userID'),
+ onTap: () => UserRouteData(id: userID).go(context),
+ ),
+ ],
+ );
+ }
+}
+
+class DialogPage extends Page<void> {
+ /// 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<void> createRoute(BuildContext context) {
+ return DialogRoute<void>(
+ context: context,
+ settings: this,
+ builder: (BuildContext 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<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
+
+ final int id;
+
+ @override
+ Page<void> 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 0000000..523af4e
--- /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<RouteBase> 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);
+}
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 6f9eb6a..4edd92a 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 @@
// GoRouterGenerator
// **************************************************************************
-List<GoRoute> get $appRoutes => [
+List<RouteBase> 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 4fd36fc..b8b7c9f 100644
--- a/packages/go_router_builder/example/pubspec.yaml
+++ b/packages/go_router_builder/example/pubspec.yaml
@@ -12,7 +12,7 @@
provider: 6.0.5
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/example/test/all_types_test.dart b/packages/go_router_builder/example/test/all_types_test.dart
index ec25b3d..99a3824 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';
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 0000000..3919970
--- /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);
+ });
+}
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 0000000..2c7bd59
--- /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));
+ });
+}
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 42ef4ca..5a17670 100644
--- a/packages/go_router_builder/lib/src/go_router_generator.dart
+++ b/packages/go_router_builder/lib/src/go_router_generator.dart
@@ -11,22 +11,56 @@
import 'route_config.dart';
-/// A [Generator] for classes annotated with `TypedGoRoute`.
+const String _routeDataUrl = 'package:go_router/src/route_data.dart';
+
+const Map<String, String> _annotations = <String, String>{
+ 'TypedGoRoute': 'GoRouteData',
+ 'TypedShellRoute': 'ShellRouteData',
+};
+
+/// A [Generator] for classes annotated with a typed go route annotation.
class GoRouterGenerator extends GeneratorForAnnotation<void> {
/// Creates a new instance of [GoRouterGenerator].
const GoRouterGenerator();
@override
- TypeChecker get typeChecker => const TypeChecker.fromUrl(
- 'package:go_router/src/route_data.dart#TypedGoRoute',
+ TypeChecker get typeChecker => TypeChecker.any(
+ _annotations.keys.map((String annotation) =>
+ TypeChecker.fromUrl('$_routeDataUrl#$annotation')),
);
@override
FutureOr<String> generate(LibraryReader library, BuildStep buildStep) async {
final Set<String> values = <String>{};
-
final Set<String> getters = <String>{};
+ for (final String annotation in _annotations.keys) {
+ final TypeChecker typeChecker =
+ TypeChecker.fromUrl('$_routeDataUrl#$annotation');
+ _generateForAnnotation(library, typeChecker, buildStep, values, getters);
+ }
+
+ if (values.isEmpty) {
+ return '';
+ }
+
+ return <String>[
+ '''
+List<RouteBase> get \$appRoutes => [
+${getters.map((String e) => "$e,").join('\n')}
+ ];
+''',
+ ...values,
+ ].join('\n\n');
+ }
+
+ void _generateForAnnotation(
+ LibraryReader library,
+ TypeChecker typeChecker,
+ BuildStep buildStep,
+ Set<String> values,
+ Set<String> getters,
+ ) {
for (final AnnotatedElement annotatedElement
in library.annotatedWith(typeChecker)) {
final InfoIterable generatedValue = generateForAnnotatedElement(
@@ -40,19 +74,6 @@
values.add(value);
}
}
-
- if (values.isEmpty) {
- return '';
- }
-
- return <String>[
- '''
-List<GoRoute> get \$appRoutes => [
-${getters.map((String e) => "$e,").join('\n')}
- ];
-''',
- ...values,
- ].join('\n\n');
}
@override
@@ -61,18 +82,25 @@
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 @TypedGoRoute 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 @TypedGoRoute 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,
);
}
@@ -80,7 +108,3 @@
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/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart
index cfc4863..9d7c1d9 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 @@
this._path,
this._routeDataClass,
this._parent,
+ this._key,
+ this._isShellRoute,
);
/// Creates a new [RouteConfig] represented the annotation data in [reader].
@@ -66,19 +68,26 @@
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;
+ // 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;
+
+ if (!isShellRoute) {
+ final ConstantReader pathValue = reader.read('path');
+ if (pathValue.isNull) {
+ throw InvalidGenerationSourceError(
+ 'Missing `path` value on annotation.',
+ element: element,
+ );
+ }
+ path = pathValue.stringValue;
}
- final String path = pathValue.stringValue;
-
- final InterfaceType type = reader.objectValue.type! as InterfaceType;
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,16 @@
// ignore: deprecated_member_use
final InterfaceElement classElement = typeParamType.element;
- final RouteConfig value = RouteConfig._(path, classElement, parent);
+ final RouteConfig value = RouteConfig._(
+ path ?? '',
+ classElement,
+ parent,
+ _generateNavigatorKeyGetterCode(
+ classElement,
+ keyName: isShellRoute ? r'$navigatorKey' : r'$parentNavigatorKey',
+ ),
+ isShellRoute,
+ );
value._children.addAll(reader.read('routes').listValue.map((DartObject e) =>
RouteConfig._fromAnnotation(ConstantReader(e), element, value)));
@@ -105,6 +123,42 @@
final String _path;
final InterfaceElement _routeDataClass;
final RouteConfig? _parent;
+ final String? _key;
+ final bool _isShellRoute;
+
+ static String? _generateNavigatorKeyGetterCode(
+ InterfaceElement classElement, {
+ required String keyName,
+ }) {
+ bool whereStatic(FieldElement element) => element.isStatic;
+ bool whereKeyName(FieldElement element) => element.name == keyName;
+ 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<DartType> 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<String>((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 +190,15 @@
}
/// 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
@@ -152,6 +214,7 @@
context.pushReplacement(location${_extraParam != null ? ', extra: $extraFieldName' : ''});
}
''';
+ }
/// Returns this [RouteConfig] and all child [RouteConfig] instances.
Iterable<RouteConfig> _flatten() sync* {
@@ -166,7 +229,7 @@
/// 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
@@ -271,11 +334,25 @@
: '''
routes: [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}],
''';
-
+ final String navigatorKeyParameterName =
+ _isShellRoute ? 'navigatorKey' : 'parentNavigatorKey';
+ final String navigatorKey = _key == null || _key!.isEmpty
+ ? ''
+ : '$navigatorKeyParameterName: $_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 5bc629a..99d3ba3 100644
--- a/packages/go_router_builder/pubspec.yaml
+++ b/packages/go_router_builder/pubspec.yaml
@@ -2,7 +2,7 @@
description: >-
A builder that supports generated strongly-typed route helpers for
package:go_router
-version: 1.1.7
+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 @@
sdk: ">=2.18.0 <4.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
@@ -23,6 +23,6 @@
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 9875d2e..2b843c1 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 @@
}
@ShouldGenerate(r'''
-GoRoute get $enumParam => GoRouteData.$route(
+RouteBase get $enumParam => GoRouteData.$route(
path: '/:y',
factory: $EnumParamExtension._fromState,
);
@@ -121,7 +121,7 @@
}
@ShouldGenerate(r'''
-GoRoute get $defaultValueRoute => GoRouteData.$route(
+RouteBase get $defaultValueRoute => GoRouteData.$route(
path: '/default-value-route',
factory: $DefaultValueRouteExtension._fromState,
);
@@ -162,7 +162,7 @@
}
@ShouldGenerate(r'''
-GoRoute get $extraValueRoute => GoRouteData.$route(
+RouteBase get $extraValueRoute => GoRouteData.$route(
path: '/default-value-route',
factory: $ExtraValueRouteExtension._fromState,
);
@@ -215,7 +215,7 @@
}
@ShouldGenerate(r'''
-GoRoute get $iterableWithEnumRoute => GoRouteData.$route(
+RouteBase get $iterableWithEnumRoute => GoRouteData.$route(
path: '/iterable-with-enum',
factory: $IterableWithEnumRouteExtension._fromState,
);
@@ -269,7 +269,7 @@
}
@ShouldGenerate(r'''
-GoRoute get $iterableDefaultValueRoute => GoRouteData.$route(
+RouteBase get $iterableDefaultValueRoute => GoRouteData.$route(
path: '/iterable-default-value-route',
factory: $IterableDefaultValueRouteExtension._fromState,
);