[go_router_builder] Add go_router StatefulShellRoute support to go_router_builder (#4238)

fixes: https://github.com/flutter/flutter/issues/127371
This commit is contained in:
hangyu
2023-08-02 15:57:32 -07:00
committed by GitHub
parent 11b79b5fbe
commit c3a5fb9c7c
10 changed files with 608 additions and 73 deletions

View File

@ -1,3 +1,7 @@
## 2.3.0
* Adds Support for StatefulShellRoute
## 2.2.5 ## 2.2.5
* Fixes a bug where shell routes without const constructor were not generated correctly. * Fixes a bug where shell routes without const constructor were not generated correctly.

View File

@ -8,12 +8,12 @@ To use `go_router_builder`, you need to have the following dependencies in
```yaml ```yaml
dependencies: dependencies:
# ...along with your other dependencies # ...along with your other dependencies
go_router: ^7.0.0 go_router: ^9.0.3
dev_dependencies: dev_dependencies:
# ...along with your other dev-dependencies # ...along with your other dev-dependencies
build_runner: ^2.0.0 build_runner: ^2.0.0
go_router_builder: ^2.0.0 go_router_builder: ^2.3.0
``` ```
### Source code ### Source code

View File

@ -26,8 +26,8 @@ RouteBase get $myShellRouteData => ShellRouteData.$route(
routes: [ routes: [
GoRouteData.$route( GoRouteData.$route(
path: ':id', path: ':id',
factory: $UserRouteDataExtension._fromState,
parentNavigatorKey: UserRouteData.$parentNavigatorKey, parentNavigatorKey: UserRouteData.$parentNavigatorKey,
factory: $UserRouteDataExtension._fromState,
), ),
], ],
), ),

View File

@ -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),
],
),
);
}
}

View File

@ -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);
}

View File

@ -6,6 +6,7 @@ environment:
sdk: ">=2.18.0 <4.0.0" sdk: ">=2.18.0 <4.0.0"
dependencies: dependencies:
collection: ^1.15.0
flutter: flutter:
sdk: flutter sdk: flutter
go_router: ^10.0.0 go_router: ^10.0.0

View File

@ -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);
});
}

View File

@ -16,6 +16,8 @@ const String _routeDataUrl = 'package:go_router/src/route_data.dart';
const Map<String, String> _annotations = <String, String>{ const Map<String, String> _annotations = <String, String>{
'TypedGoRoute': 'GoRouteData', 'TypedGoRoute': 'GoRouteData',
'TypedShellRoute': 'ShellRouteData', 'TypedShellRoute': 'ShellRouteData',
'TypedStatefulShellBranch': 'StatefulShellBranchData',
'TypedStatefulShellRoute': 'StatefulShellRouteData',
}; };
/// A [Generator] for classes annotated with a typed go route annotation. /// A [Generator] for classes annotated with a typed go route annotation.

View File

@ -38,14 +38,17 @@ class InfoIterable extends IterableBase<String> {
class ShellRouteConfig extends RouteBaseConfig { class ShellRouteConfig extends RouteBaseConfig {
ShellRouteConfig._({ ShellRouteConfig._({
required this.navigatorKey, required this.navigatorKey,
required this.parentNavigatorKey,
required super.routeDataClass, required super.routeDataClass,
required super.parent, required super.parent,
required super.parentNavigatorKey,
}) : super._(); }) : super._();
/// The command for calling the navigator key getter from the ShellRouteData. /// The command for calling the navigator key getter from the ShellRouteData.
final String? navigatorKey; final String? navigatorKey;
/// The parent navigator key.
final String? parentNavigatorKey;
@override @override
Iterable<String> classDeclarations() { Iterable<String> classDeclarations() {
if (routeDataClass.unnamedConstructor == null) { if (routeDataClass.unnamedConstructor == null) {
@ -68,10 +71,95 @@ class ShellRouteConfig extends RouteBaseConfig {
@override @override
String get routeConstructorParameters => String get routeConstructorParameters =>
navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'; '${navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'}'
'${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}';
@override
String get factorConstructorParameters =>
'factory: $_extensionName._fromState,';
@override @override
String get routeDataClassName => 'ShellRouteData'; 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. /// The configuration to generate class declarations for a GoRouteData.
@ -79,9 +167,9 @@ class GoRouteConfig extends RouteBaseConfig {
GoRouteConfig._({ GoRouteConfig._({
required this.path, required this.path,
required this.name, required this.name,
required this.parentNavigatorKey,
required super.routeDataClass, required super.routeDataClass,
required super.parent, required super.parent,
required super.parentNavigatorKey,
}) : super._(); }) : super._();
/// The path of the GoRoute to be created by this configuration. /// The path of the GoRoute to be created by this configuration.
@ -90,6 +178,9 @@ class GoRouteConfig extends RouteBaseConfig {
/// The name of the GoRoute to be created by this configuration. /// The name of the GoRoute to be created by this configuration.
final String? name; final String? name;
/// The parent navigator key.
final String? parentNavigatorKey;
late final Set<String> _pathParams = late final Set<String> _pathParams =
pathParametersFromPattern(_rawJoinedPath); pathParametersFromPattern(_rawJoinedPath);
@ -295,14 +386,22 @@ extension $_extensionName on $_className {
return enumParamTypes.map<String>(_enumMapConst); return enumParamTypes.map<String>(_enumMapConst);
} }
@override
String get factorConstructorParameters =>
'factory: $_extensionName._fromState,';
@override @override
String get routeConstructorParameters => ''' String get routeConstructorParameters => '''
path: ${escapeDartString(path)}, path: ${escapeDartString(path)},
${name != null ? 'name: ${escapeDartString(name!)},' : ''} ${name != null ? 'name: ${escapeDartString(name!)},' : ''}
${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}
'''; ''';
@override @override
String get routeDataClassName => 'GoRouteData'; String get routeDataClassName => 'GoRouteData';
@override
String get dataConvertionFunctionName => r'$route';
} }
/// Represents a `TypedGoRoute` annotation to the builder. /// Represents a `TypedGoRoute` annotation to the builder.
@ -310,7 +409,6 @@ abstract class RouteBaseConfig {
RouteBaseConfig._({ RouteBaseConfig._({
required this.routeDataClass, required this.routeDataClass,
required this.parent, required this.parent,
required this.parentNavigatorKey,
}); });
/// Creates a new [RouteBaseConfig] represented the annotation data in [reader]. /// Creates a new [RouteBaseConfig] represented the annotation data in [reader].
@ -342,7 +440,7 @@ abstract class RouteBaseConfig {
// TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to // TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to
// 5.2+ (when Flutter 3.4+ is on stable). // 5.2+ (when Flutter 3.4+ is on stable).
// ignore: deprecated_member_use // ignore: deprecated_member_use
final bool isShellRoute = type.element.name == 'TypedShellRoute'; final String typeName = type.element.name;
final DartType typeParamType = type.typeArguments.single; final DartType typeParamType = type.typeArguments.single;
if (typeParamType is! InterfaceType) { if (typeParamType is! InterfaceType) {
throw InvalidGenerationSourceError( throw InvalidGenerationSourceError(
@ -359,43 +457,81 @@ abstract class RouteBaseConfig {
final InterfaceElement classElement = typeParamType.element; final InterfaceElement classElement = typeParamType.element;
final RouteBaseConfig value; final RouteBaseConfig value;
if (isShellRoute) { switch (typeName) {
value = ShellRouteConfig._( case 'TypedShellRoute':
routeDataClass: classElement, value = ShellRouteConfig._(
parent: parent, routeDataClass: classElement,
navigatorKey: _generateNavigatorKeyGetterCode( parent: parent,
classElement, navigatorKey: _generateParameterGetterCode(
keyName: r'$navigatorKey', classElement,
), parameterName: r'$navigatorKey',
parentNavigatorKey: _generateNavigatorKeyGetterCode( ),
classElement, parentNavigatorKey: _generateParameterGetterCode(
keyName: r'$parentNavigatorKey', classElement,
), parameterName: r'$parentNavigatorKey',
); ),
} else {
final ConstantReader pathValue = reader.read('path');
if (pathValue.isNull) {
throw InvalidGenerationSourceError(
'Missing `path` value on annotation.',
element: element,
); );
} break;
case 'TypedStatefulShellRoute':
final ConstantReader nameValue = reader.read('name'); value = StatefulShellRouteConfig._(
value = GoRouteConfig._( routeDataClass: classElement,
path: pathValue.stringValue, parent: parent,
name: nameValue.isNull ? null : nameValue.stringValue, parentNavigatorKey: _generateParameterGetterCode(
routeDataClass: classElement, classElement,
parent: parent, parameterName: r'$parentNavigatorKey',
parentNavigatorKey: _generateNavigatorKeyGetterCode( ),
classElement, restorationScopeId: _generateParameterGetterCode(
keyName: r'$parentNavigatorKey', 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>( value._children.addAll(reader
(DartObject e) => RouteBaseConfig._fromAnnotation( .read(_generateChildrenGetterName(typeName))
.listValue
.map<RouteBaseConfig>((DartObject e) => RouteBaseConfig._fromAnnotation(
ConstantReader(e), element, value))); ConstantReader(e), element, value)));
return value; return value;
@ -409,40 +545,55 @@ abstract class RouteBaseConfig {
/// The parent of this route config. /// The parent of this route config.
final RouteBaseConfig? parent; final RouteBaseConfig? parent;
/// The parent navigator key string that is used for initialize the static String _generateChildrenGetterName(String name) {
/// `RouteBase` class this config generates. return (name == 'TypedStatefulShellRoute' ||
final String? parentNavigatorKey; name == 'StatefulShellRouteData')
? 'branches'
: 'routes';
}
static String? _generateNavigatorKeyGetterCode( static String? _generateParameterGetterCode(InterfaceElement classElement,
InterfaceElement classElement, { {required String parameterName}) {
required String keyName,
}) {
final String? fieldDisplayName = classElement.fields final String? fieldDisplayName = classElement.fields
.where((FieldElement element) { .where((FieldElement element) {
final DartType type = element.type; if (!element.isStatic || element.name != parameterName) {
if (!element.isStatic ||
element.name != keyName ||
type is! ParameterizedType) {
return false; return false;
} }
final List<DartType> typeArguments = type.typeArguments; if (parameterName.toLowerCase().contains('navigatorkey')) {
if (typeArguments.length != 1) { final DartType type = element.type;
return false; 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; return true;
if (typeArgument.getDisplayString(withNullability: false) ==
'NavigatorState') {
return true;
}
return false;
}) })
.map<String>((FieldElement e) => e.displayName) .map<String>((FieldElement e) => e.displayName)
.firstOrNull; .firstOrNull;
if (fieldDisplayName == null) { if (fieldDisplayName != null) {
return 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`. /// Generates all of the members that correspond to `this`.
@ -496,16 +647,13 @@ RouteBase get $_routeGetterName => ${_invokesRouteConstructor()};
final String routesBit = _children.isEmpty 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 ''' return '''
$routeDataClassName.\$route( $routeDataClassName.$dataConvertionFunctionName(
$routeConstructorParameters $routeConstructorParameters
factory: $_extensionName._fromState, $factorConstructorParameters
$parentNavigatorKeyParameter
$routesBit $routesBit
) )
'''; ''';
@ -518,6 +666,14 @@ $routeDataClassName.\$route(
@protected @protected
String get routeDataClassName; 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. /// Additional constructor parameter for invoking route constructor.
@protected @protected
String get routeConstructorParameters; String get routeConstructorParameters;

View File

@ -2,7 +2,7 @@ name: go_router_builder
description: >- description: >-
A builder that supports generated strongly-typed route helpers for A builder that supports generated strongly-typed route helpers for
package:go_router package:go_router
version: 2.2.5 version: 2.3.0
repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder 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 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22