[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