[go_router_builder] Add go_router StatefulShellRoute support to go_router_builder (#4238)
fixes: https://github.com/flutter/flutter/issues/127371
diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md
index 11aeb62..3cd8359 100644
--- a/packages/go_router_builder/CHANGELOG.md
+++ b/packages/go_router_builder/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.3.0
+
+* Adds Support for StatefulShellRoute
+
## 2.2.5
* Fixes a bug where shell routes without const constructor were not generated correctly.
diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md
index 797231c..73faf97 100644
--- a/packages/go_router_builder/README.md
+++ b/packages/go_router_builder/README.md
@@ -8,12 +8,12 @@
```yaml
dependencies:
# ...along with your other dependencies
- go_router: ^7.0.0
+ go_router: ^9.0.3
dev_dependencies:
# ...along with your other dev-dependencies
build_runner: ^2.0.0
- go_router_builder: ^2.0.0
+ go_router_builder: ^2.3.0
```
### Source code
diff --git a/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart b/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart
index 31f9d4b..6151889 100644
--- a/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart
+++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart
@@ -26,8 +26,8 @@
routes: [
GoRouteData.$route(
path: ':id',
- factory: $UserRouteDataExtension._fromState,
parentNavigatorKey: UserRouteData.$parentNavigatorKey,
+ factory: $UserRouteDataExtension._fromState,
),
],
),
diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart
new file mode 100644
index 0000000..9e9748e
--- /dev/null
+++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart
@@ -0,0 +1,266 @@
+// 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:collection/collection.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+part 'stateful_shell_route_example.g.dart';
+
+final GlobalKey<NavigatorState> _sectionANavigatorKey =
+ GlobalKey<NavigatorState>(debugLabel: 'sectionANav');
+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: '/detailsA',
+ );
+}
+
+class HomeScreen extends StatelessWidget {
+ const HomeScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) => Scaffold(
+ appBar: AppBar(title: const Text('foo')),
+ );
+}
+
+@TypedStatefulShellRoute<MyShellRouteData>(
+ branches: <TypedStatefulShellBranch<StatefulShellBranchData>>[
+ TypedStatefulShellBranch<BranchAData>(
+ routes: <TypedRoute<RouteData>>[
+ TypedGoRoute<DetailsARouteData>(path: '/detailsA'),
+ ],
+ ),
+ TypedStatefulShellBranch<BranchBData>(
+ routes: <TypedRoute<RouteData>>[
+ TypedGoRoute<DetailsBRouteData>(path: '/detailsB'),
+ ],
+ ),
+ ],
+)
+class MyShellRouteData extends StatefulShellRouteData {
+ const MyShellRouteData();
+
+ @override
+ Widget builder(
+ BuildContext context,
+ GoRouterState state,
+ StatefulNavigationShell navigationShell,
+ ) {
+ return navigationShell;
+ }
+
+ static const String $restorationScopeId = 'restorationScopeId';
+
+ static Widget $navigatorContainerBuilder(BuildContext context,
+ StatefulNavigationShell navigationShell, List<Widget> children) {
+ return ScaffoldWithNavBar(
+ navigationShell: navigationShell,
+ children: children,
+ );
+ }
+}
+
+class BranchAData extends StatefulShellBranchData {
+ const BranchAData();
+}
+
+class BranchBData extends StatefulShellBranchData {
+ const BranchBData();
+
+ static final GlobalKey<NavigatorState> $navigatorKey = _sectionANavigatorKey;
+ static const String $restorationScopeId = 'restorationScopeId';
+}
+
+class DetailsARouteData extends GoRouteData {
+ const DetailsARouteData();
+
+ @override
+ Widget build(BuildContext context, GoRouterState state) {
+ return const DetailsScreen(label: 'A');
+ }
+}
+
+class DetailsBRouteData extends GoRouteData {
+ const DetailsBRouteData();
+
+ @override
+ Widget build(BuildContext context, GoRouterState state) {
+ return const DetailsScreen(label: 'B');
+ }
+}
+
+/// Builds the "shell" for the app by building a Scaffold with a
+/// BottomNavigationBar, where [child] is placed in the body of the Scaffold.
+class ScaffoldWithNavBar extends StatelessWidget {
+ /// Constructs an [ScaffoldWithNavBar].
+ const ScaffoldWithNavBar({
+ required this.navigationShell,
+ required this.children,
+ Key? key,
+ }) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar'));
+
+ /// The navigation shell and container for the branch Navigators.
+ final StatefulNavigationShell navigationShell;
+
+ /// The children (branch Navigators) to display in a custom container
+ /// ([AnimatedBranchContainer]).
+ final List<Widget> children;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: AnimatedBranchContainer(
+ currentIndex: navigationShell.currentIndex,
+ children: children,
+ ),
+ bottomNavigationBar: BottomNavigationBar(
+ // Here, the items of BottomNavigationBar are hard coded. In a real
+ // world scenario, the items would most likely be generated from the
+ // branches of the shell route, which can be fetched using
+ // `navigationShell.route.branches`.
+ items: const <BottomNavigationBarItem>[
+ BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
+ BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
+ ],
+ currentIndex: navigationShell.currentIndex,
+ onTap: (int index) => _onTap(context, index),
+ ),
+ );
+ }
+
+ /// Navigate to the current location of the branch at the provided index when
+ /// tapping an item in the BottomNavigationBar.
+ void _onTap(BuildContext context, int index) {
+ // When navigating to a new branch, it's recommended to use the goBranch
+ // method, as doing so makes sure the last navigation state of the
+ // Navigator for the branch is restored.
+ navigationShell.goBranch(
+ index,
+ // A common pattern when using bottom navigation bars is to support
+ // navigating to the initial location when tapping the item that is
+ // already active. This example demonstrates how to support this behavior,
+ // using the initialLocation parameter of goBranch.
+ initialLocation: index == navigationShell.currentIndex,
+ );
+ }
+}
+
+/// Custom branch Navigator container that provides animated transitions
+/// when switching branches.
+class AnimatedBranchContainer extends StatelessWidget {
+ /// Creates a AnimatedBranchContainer
+ const AnimatedBranchContainer(
+ {super.key, required this.currentIndex, required this.children});
+
+ /// The index (in [children]) of the branch Navigator to display.
+ final int currentIndex;
+
+ /// The children (branch Navigators) to display in this container.
+ final List<Widget> children;
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ children: children.mapIndexed(
+ (int index, Widget navigator) {
+ return AnimatedScale(
+ scale: index == currentIndex ? 1 : 1.5,
+ duration: const Duration(milliseconds: 400),
+ child: AnimatedOpacity(
+ opacity: index == currentIndex ? 1 : 0,
+ duration: const Duration(milliseconds: 400),
+ child: _branchNavigatorWrapper(index, navigator),
+ ),
+ );
+ },
+ ).toList());
+ }
+
+ Widget _branchNavigatorWrapper(int index, Widget navigator) => IgnorePointer(
+ ignoring: index != currentIndex,
+ child: TickerMode(
+ enabled: index == currentIndex,
+ child: navigator,
+ ),
+ );
+}
+
+/// The details screen for either the A or B screen.
+class DetailsScreen extends StatefulWidget {
+ /// Constructs a [DetailsScreen].
+ const DetailsScreen({
+ required this.label,
+ this.param,
+ this.extra,
+ super.key,
+ });
+
+ /// The label to display in the center of the screen.
+ final String label;
+
+ /// Optional param
+ final String? param;
+
+ /// Optional extra object
+ final Object? extra;
+ @override
+ State<StatefulWidget> createState() => DetailsScreenState();
+}
+
+/// The state for DetailsScreen
+class DetailsScreenState extends State<DetailsScreen> {
+ int _counter = 0;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('Details Screen - ${widget.label}'),
+ ),
+ body: _build(context),
+ );
+ }
+
+ Widget _build(BuildContext context) {
+ return Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: <Widget>[
+ Text('Details for ${widget.label} - Counter: $_counter',
+ style: Theme.of(context).textTheme.titleLarge),
+ const Padding(padding: EdgeInsets.all(4)),
+ TextButton(
+ onPressed: () {
+ setState(() {
+ _counter++;
+ });
+ },
+ child: const Text('Increment counter'),
+ ),
+ const Padding(padding: EdgeInsets.all(8)),
+ if (widget.param != null)
+ Text('Parameter: ${widget.param!}',
+ style: Theme.of(context).textTheme.titleMedium),
+ const Padding(padding: EdgeInsets.all(8)),
+ if (widget.extra != null)
+ Text('Extra: ${widget.extra!}',
+ style: Theme.of(context).textTheme.titleMedium),
+ ],
+ ),
+ );
+ }
+}
diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart
new file mode 100644
index 0000000..9be15d9
--- /dev/null
+++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart
@@ -0,0 +1,80 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// ignore_for_file: always_specify_types, public_member_api_docs
+
+part of 'stateful_shell_route_example.dart';
+
+// **************************************************************************
+// GoRouterGenerator
+// **************************************************************************
+
+List<RouteBase> get $appRoutes => [
+ $myShellRouteData,
+ ];
+
+RouteBase get $myShellRouteData => StatefulShellRouteData.$route(
+ restorationScopeId: MyShellRouteData.$restorationScopeId,
+ navigatorContainerBuilder: MyShellRouteData.$navigatorContainerBuilder,
+ factory: $MyShellRouteDataExtension._fromState,
+ branches: [
+ StatefulShellBranchData.$branch(
+ routes: [
+ GoRouteData.$route(
+ path: '/detailsA',
+ factory: $DetailsARouteDataExtension._fromState,
+ ),
+ ],
+ ),
+ StatefulShellBranchData.$branch(
+ navigatorKey: BranchBData.$navigatorKey,
+ restorationScopeId: BranchBData.$restorationScopeId,
+ routes: [
+ GoRouteData.$route(
+ path: '/detailsB',
+ factory: $DetailsBRouteDataExtension._fromState,
+ ),
+ ],
+ ),
+ ],
+ );
+
+extension $MyShellRouteDataExtension on MyShellRouteData {
+ static MyShellRouteData _fromState(GoRouterState state) =>
+ const MyShellRouteData();
+}
+
+extension $DetailsARouteDataExtension on DetailsARouteData {
+ static DetailsARouteData _fromState(GoRouterState state) =>
+ const DetailsARouteData();
+
+ String get location => GoRouteData.$location(
+ '/detailsA',
+ );
+
+ void go(BuildContext context) => context.go(location);
+
+ Future<T?> push<T>(BuildContext context) => context.push<T>(location);
+
+ void pushReplacement(BuildContext context) =>
+ context.pushReplacement(location);
+
+ void replace(BuildContext context) => context.replace(location);
+}
+
+extension $DetailsBRouteDataExtension on DetailsBRouteData {
+ static DetailsBRouteData _fromState(GoRouterState state) =>
+ const DetailsBRouteData();
+
+ String get location => GoRouteData.$location(
+ '/detailsB',
+ );
+
+ void go(BuildContext context) => context.go(location);
+
+ Future<T?> push<T>(BuildContext context) => context.push<T>(location);
+
+ void pushReplacement(BuildContext context) =>
+ context.pushReplacement(location);
+
+ void replace(BuildContext context) => context.replace(location);
+}
diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml
index 3dea247..1cc07ea 100644
--- a/packages/go_router_builder/example/pubspec.yaml
+++ b/packages/go_router_builder/example/pubspec.yaml
@@ -6,6 +6,7 @@
sdk: ">=2.18.0 <4.0.0"
dependencies:
+ collection: ^1.15.0
flutter:
sdk: flutter
go_router: ^10.0.0
diff --git a/packages/go_router_builder/example/test/stateful_shell_route_test.dart b/packages/go_router_builder/example/test/stateful_shell_route_test.dart
new file mode 100644
index 0000000..f885e54
--- /dev/null
+++ b/packages/go_router_builder/example/test/stateful_shell_route_test.dart
@@ -0,0 +1,26 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:go_router_builder_example/stateful_shell_route_example.dart';
+
+void main() {
+ testWidgets('Navigate between section A and section B',
+ (WidgetTester tester) async {
+ await tester.pumpWidget(App());
+ expect(find.text('Details for A - Counter: 0'), findsOneWidget);
+
+ await tester.tap(find.text('Increment counter'));
+ await tester.pumpAndSettle();
+ expect(find.text('Details for A - Counter: 1'), findsOneWidget);
+
+ await tester.tap(find.text('Section B'));
+ await tester.pumpAndSettle();
+ expect(find.text('Details for B - Counter: 0'), findsOneWidget);
+
+ await tester.tap(find.text('Section A'));
+ await tester.pumpAndSettle();
+ expect(find.text('Details for A - Counter: 1'), findsOneWidget);
+ });
+}
diff --git a/packages/go_router_builder/lib/src/go_router_generator.dart b/packages/go_router_builder/lib/src/go_router_generator.dart
index cf57329..e094d1f 100644
--- a/packages/go_router_builder/lib/src/go_router_generator.dart
+++ b/packages/go_router_builder/lib/src/go_router_generator.dart
@@ -16,6 +16,8 @@
const Map<String, String> _annotations = <String, String>{
'TypedGoRoute': 'GoRouteData',
'TypedShellRoute': 'ShellRouteData',
+ 'TypedStatefulShellBranch': 'StatefulShellBranchData',
+ 'TypedStatefulShellRoute': 'StatefulShellRouteData',
};
/// A [Generator] for classes annotated with a typed go route annotation.
diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart
index 1d60776..5925c11 100644
--- a/packages/go_router_builder/lib/src/route_config.dart
+++ b/packages/go_router_builder/lib/src/route_config.dart
@@ -38,14 +38,17 @@
class ShellRouteConfig extends RouteBaseConfig {
ShellRouteConfig._({
required this.navigatorKey,
+ required this.parentNavigatorKey,
required super.routeDataClass,
required super.parent,
- required super.parentNavigatorKey,
}) : super._();
/// The command for calling the navigator key getter from the ShellRouteData.
final String? navigatorKey;
+ /// The parent navigator key.
+ final String? parentNavigatorKey;
+
@override
Iterable<String> classDeclarations() {
if (routeDataClass.unnamedConstructor == null) {
@@ -68,10 +71,95 @@
@override
String get routeConstructorParameters =>
- navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,';
+ '${navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'}'
+ '${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}';
+
+ @override
+ String get factorConstructorParameters =>
+ 'factory: $_extensionName._fromState,';
@override
String get routeDataClassName => 'ShellRouteData';
+
+ @override
+ String get dataConvertionFunctionName => r'$route';
+}
+
+/// The configuration to generate class declarations for a StatefulShellRouteData.
+class StatefulShellRouteConfig extends RouteBaseConfig {
+ StatefulShellRouteConfig._({
+ required this.parentNavigatorKey,
+ required super.routeDataClass,
+ required super.parent,
+ required this.navigatorContainerBuilder,
+ required this.restorationScopeId,
+ }) : super._();
+
+ /// The parent navigator key.
+ final String? parentNavigatorKey;
+
+ /// The navigator container builder.
+ final String? navigatorContainerBuilder;
+
+ /// The restoration scope id.
+ final String? restorationScopeId;
+
+ @override
+ Iterable<String> classDeclarations() => <String>[
+ '''
+extension $_extensionName on $_className {
+ static $_className _fromState(GoRouterState state) => const $_className();
+}
+'''
+ ];
+
+ @override
+ String get routeConstructorParameters =>
+ '${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}'
+ '${restorationScopeId == null ? '' : 'restorationScopeId: $restorationScopeId,'}'
+ '${navigatorContainerBuilder == null ? '' : 'navigatorContainerBuilder: $navigatorContainerBuilder,'}';
+
+ @override
+ String get factorConstructorParameters =>
+ 'factory: $_extensionName._fromState,';
+
+ @override
+ String get routeDataClassName => 'StatefulShellRouteData';
+
+ @override
+ String get dataConvertionFunctionName => r'$route';
+}
+
+/// The configuration to generate class declarations for a StatefulShellBranchData.
+class StatefulShellBranchConfig extends RouteBaseConfig {
+ StatefulShellBranchConfig._({
+ required this.navigatorKey,
+ required super.routeDataClass,
+ required super.parent,
+ this.restorationScopeId,
+ }) : super._();
+
+ /// The command for calling the navigator key getter from the ShellRouteData.
+ final String? navigatorKey;
+
+ /// The restoration scope id.
+ final String? restorationScopeId;
+
+ @override
+ Iterable<String> classDeclarations() => <String>[];
+
+ @override
+ String get factorConstructorParameters => '';
+ @override
+ String get routeConstructorParameters =>
+ '${navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'}'
+ '${restorationScopeId == null ? '' : 'restorationScopeId: $restorationScopeId,'}';
+
+ @override
+ String get routeDataClassName => 'StatefulShellBranchData';
+
+ @override
+ String get dataConvertionFunctionName => r'$branch';
}
/// The configuration to generate class declarations for a GoRouteData.
@@ -79,9 +167,9 @@
GoRouteConfig._({
required this.path,
required this.name,
+ required this.parentNavigatorKey,
required super.routeDataClass,
required super.parent,
- required super.parentNavigatorKey,
}) : super._();
/// The path of the GoRoute to be created by this configuration.
@@ -90,6 +178,9 @@
/// The name of the GoRoute to be created by this configuration.
final String? name;
+ /// The parent navigator key.
+ final String? parentNavigatorKey;
+
late final Set<String> _pathParams =
pathParametersFromPattern(_rawJoinedPath);
@@ -296,13 +387,21 @@
}
@override
+ String get factorConstructorParameters =>
+ 'factory: $_extensionName._fromState,';
+
+ @override
String get routeConstructorParameters => '''
path: ${escapeDartString(path)},
${name != null ? 'name: ${escapeDartString(name!)},' : ''}
+ ${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}
''';
@override
String get routeDataClassName => 'GoRouteData';
+
+ @override
+ String get dataConvertionFunctionName => r'$route';
}
/// Represents a `TypedGoRoute` annotation to the builder.
@@ -310,7 +409,6 @@
RouteBaseConfig._({
required this.routeDataClass,
required this.parent,
- required this.parentNavigatorKey,
});
/// Creates a new [RouteBaseConfig] represented the annotation data in [reader].
@@ -342,7 +440,7 @@
// TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to
// 5.2+ (when Flutter 3.4+ is on stable).
// ignore: deprecated_member_use
- final bool isShellRoute = type.element.name == 'TypedShellRoute';
+ final String typeName = type.element.name;
final DartType typeParamType = type.typeArguments.single;
if (typeParamType is! InterfaceType) {
throw InvalidGenerationSourceError(
@@ -359,43 +457,81 @@
final InterfaceElement classElement = typeParamType.element;
final RouteBaseConfig value;
- if (isShellRoute) {
- value = ShellRouteConfig._(
- routeDataClass: classElement,
- parent: parent,
- navigatorKey: _generateNavigatorKeyGetterCode(
- classElement,
- keyName: r'$navigatorKey',
- ),
- parentNavigatorKey: _generateNavigatorKeyGetterCode(
- classElement,
- keyName: r'$parentNavigatorKey',
- ),
- );
- } else {
- final ConstantReader pathValue = reader.read('path');
- if (pathValue.isNull) {
- throw InvalidGenerationSourceError(
- 'Missing `path` value on annotation.',
- element: element,
+ switch (typeName) {
+ case 'TypedShellRoute':
+ value = ShellRouteConfig._(
+ routeDataClass: classElement,
+ parent: parent,
+ navigatorKey: _generateParameterGetterCode(
+ classElement,
+ parameterName: r'$navigatorKey',
+ ),
+ parentNavigatorKey: _generateParameterGetterCode(
+ classElement,
+ parameterName: r'$parentNavigatorKey',
+ ),
);
- }
-
- final ConstantReader nameValue = reader.read('name');
- value = GoRouteConfig._(
- path: pathValue.stringValue,
- name: nameValue.isNull ? null : nameValue.stringValue,
- routeDataClass: classElement,
- parent: parent,
- parentNavigatorKey: _generateNavigatorKeyGetterCode(
- classElement,
- keyName: r'$parentNavigatorKey',
- ),
- );
+ break;
+ case 'TypedStatefulShellRoute':
+ value = StatefulShellRouteConfig._(
+ routeDataClass: classElement,
+ parent: parent,
+ parentNavigatorKey: _generateParameterGetterCode(
+ classElement,
+ parameterName: r'$parentNavigatorKey',
+ ),
+ restorationScopeId: _generateParameterGetterCode(
+ classElement,
+ parameterName: r'$restorationScopeId',
+ ),
+ navigatorContainerBuilder: _generateParameterGetterCode(
+ classElement,
+ parameterName: r'$navigatorContainerBuilder',
+ ),
+ );
+ break;
+ case 'TypedStatefulShellBranch':
+ value = StatefulShellBranchConfig._(
+ routeDataClass: classElement,
+ parent: parent,
+ navigatorKey: _generateParameterGetterCode(
+ classElement,
+ parameterName: r'$navigatorKey',
+ ),
+ restorationScopeId: _generateParameterGetterCode(
+ classElement,
+ parameterName: r'$restorationScopeId',
+ ),
+ );
+ break;
+ case 'TypedGoRoute':
+ final ConstantReader pathValue = reader.read('path');
+ if (pathValue.isNull) {
+ throw InvalidGenerationSourceError(
+ 'Missing `path` value on annotation.',
+ element: element,
+ );
+ }
+ final ConstantReader nameValue = reader.read('name');
+ value = GoRouteConfig._(
+ path: pathValue.stringValue,
+ name: nameValue.isNull ? null : nameValue.stringValue,
+ routeDataClass: classElement,
+ parent: parent,
+ parentNavigatorKey: _generateParameterGetterCode(
+ classElement,
+ parameterName: r'$parentNavigatorKey',
+ ),
+ );
+ break;
+ default:
+ throw UnsupportedError('Unrecognized type $typeName');
}
- value._children.addAll(reader.read('routes').listValue.map<RouteBaseConfig>(
- (DartObject e) => RouteBaseConfig._fromAnnotation(
+ value._children.addAll(reader
+ .read(_generateChildrenGetterName(typeName))
+ .listValue
+ .map<RouteBaseConfig>((DartObject e) => RouteBaseConfig._fromAnnotation(
ConstantReader(e), element, value)));
return value;
@@ -409,40 +545,55 @@
/// The parent of this route config.
final RouteBaseConfig? parent;
- /// The parent navigator key string that is used for initialize the
- /// `RouteBase` class this config generates.
- final String? parentNavigatorKey;
+ static String _generateChildrenGetterName(String name) {
+ return (name == 'TypedStatefulShellRoute' ||
+ name == 'StatefulShellRouteData')
+ ? 'branches'
+ : 'routes';
+ }
- static String? _generateNavigatorKeyGetterCode(
- InterfaceElement classElement, {
- required String keyName,
- }) {
+ static String? _generateParameterGetterCode(InterfaceElement classElement,
+ {required String parameterName}) {
final String? fieldDisplayName = classElement.fields
.where((FieldElement element) {
- final DartType type = element.type;
- if (!element.isStatic ||
- element.name != keyName ||
- type is! ParameterizedType) {
+ if (!element.isStatic || element.name != parameterName) {
return false;
}
- final List<DartType> typeArguments = type.typeArguments;
- if (typeArguments.length != 1) {
- return false;
+ if (parameterName.toLowerCase().contains('navigatorkey')) {
+ 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 false;
+ }
}
- final DartType typeArgument = typeArguments.single;
- if (typeArgument.getDisplayString(withNullability: false) ==
- 'NavigatorState') {
- return true;
- }
- return false;
+ return true;
})
.map<String>((FieldElement e) => e.displayName)
.firstOrNull;
- if (fieldDisplayName == null) {
- return null;
+ if (fieldDisplayName != null) {
+ return '${classElement.name}.$fieldDisplayName';
}
- return '${classElement.name}.$fieldDisplayName';
+
+ final String? methodDisplayName = classElement.methods
+ .where((MethodElement element) {
+ return element.isStatic && element.name == parameterName;
+ })
+ .map<String>((MethodElement e) => e.displayName)
+ .firstOrNull;
+
+ if (methodDisplayName != null) {
+ return '${classElement.name}.$methodDisplayName';
+ }
+ return null;
}
/// Generates all of the members that correspond to `this`.
@@ -496,16 +647,13 @@
final String routesBit = _children.isEmpty
? ''
: '''
-routes: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}],
+${_generateChildrenGetterName(routeDataClassName)}: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}],
''';
- final String parentNavigatorKeyParameter = parentNavigatorKey == null
- ? ''
- : 'parentNavigatorKey: $parentNavigatorKey,';
+
return '''
-$routeDataClassName.\$route(
+$routeDataClassName.$dataConvertionFunctionName(
$routeConstructorParameters
- factory: $_extensionName._fromState,
- $parentNavigatorKeyParameter
+ $factorConstructorParameters
$routesBit
)
''';
@@ -518,6 +666,14 @@
@protected
String get routeDataClassName;
+ /// The function name of `RouteData` to get Routes or branches.
+ @protected
+ String get dataConvertionFunctionName;
+
+ /// Additional factory constructor.
+ @protected
+ String get factorConstructorParameters;
+
/// Additional constructor parameter for invoking route constructor.
@protected
String get routeConstructorParameters;
diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml
index dac268f..c3beffd 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: 2.2.5
+version: 2.3.0
repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22