mirror of
https://github.com/flutter/packages.git
synced 2025-06-29 06:06:59 +08:00
[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:
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
@ -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.
|
||||||
|
@ -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,20 +457,54 @@ abstract class RouteBaseConfig {
|
|||||||
final InterfaceElement classElement = typeParamType.element;
|
final InterfaceElement classElement = typeParamType.element;
|
||||||
|
|
||||||
final RouteBaseConfig value;
|
final RouteBaseConfig value;
|
||||||
if (isShellRoute) {
|
switch (typeName) {
|
||||||
|
case 'TypedShellRoute':
|
||||||
value = ShellRouteConfig._(
|
value = ShellRouteConfig._(
|
||||||
routeDataClass: classElement,
|
routeDataClass: classElement,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
navigatorKey: _generateNavigatorKeyGetterCode(
|
navigatorKey: _generateParameterGetterCode(
|
||||||
classElement,
|
classElement,
|
||||||
keyName: r'$navigatorKey',
|
parameterName: r'$navigatorKey',
|
||||||
),
|
),
|
||||||
parentNavigatorKey: _generateNavigatorKeyGetterCode(
|
parentNavigatorKey: _generateParameterGetterCode(
|
||||||
classElement,
|
classElement,
|
||||||
keyName: r'$parentNavigatorKey',
|
parameterName: r'$parentNavigatorKey',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
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');
|
final ConstantReader pathValue = reader.read('path');
|
||||||
if (pathValue.isNull) {
|
if (pathValue.isNull) {
|
||||||
throw InvalidGenerationSourceError(
|
throw InvalidGenerationSourceError(
|
||||||
@ -380,22 +512,26 @@ abstract class RouteBaseConfig {
|
|||||||
element: element,
|
element: element,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ConstantReader nameValue = reader.read('name');
|
final ConstantReader nameValue = reader.read('name');
|
||||||
value = GoRouteConfig._(
|
value = GoRouteConfig._(
|
||||||
path: pathValue.stringValue,
|
path: pathValue.stringValue,
|
||||||
name: nameValue.isNull ? null : nameValue.stringValue,
|
name: nameValue.isNull ? null : nameValue.stringValue,
|
||||||
routeDataClass: classElement,
|
routeDataClass: classElement,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
parentNavigatorKey: _generateNavigatorKeyGetterCode(
|
parentNavigatorKey: _generateParameterGetterCode(
|
||||||
classElement,
|
classElement,
|
||||||
keyName: r'$parentNavigatorKey',
|
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,20 +545,23 @@ 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) {
|
||||||
|
if (!element.isStatic || element.name != parameterName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (parameterName.toLowerCase().contains('navigatorkey')) {
|
||||||
final DartType type = element.type;
|
final DartType type = element.type;
|
||||||
if (!element.isStatic ||
|
if (type is! ParameterizedType) {
|
||||||
element.name != keyName ||
|
|
||||||
type is! ParameterizedType) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final List<DartType> typeArguments = type.typeArguments;
|
final List<DartType> typeArguments = type.typeArguments;
|
||||||
@ -430,21 +569,33 @@ abstract class RouteBaseConfig {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final DartType typeArgument = typeArguments.single;
|
final DartType typeArgument = typeArguments.single;
|
||||||
if (typeArgument.getDisplayString(withNullability: false) ==
|
if (typeArgument.getDisplayString(withNullability: false) !=
|
||||||
'NavigatorState') {
|
'NavigatorState') {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
})
|
})
|
||||||
.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`.
|
||||||
InfoIterable generateMembers() => InfoIterable._(
|
InfoIterable generateMembers() => InfoIterable._(
|
||||||
members: _generateMembers().toList(),
|
members: _generateMembers().toList(),
|
||||||
@ -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;
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user