Skip to content

Commit 1ddd2d9

Browse files
authored
[go_router_builder] Adds support for required $extra parameter (#3627)
[go_router_builder] Adds support for required $extra parameter
1 parent 02ffd94 commit 1ddd2d9

File tree

8 files changed

+250
-24
lines changed

8 files changed

+250
-24
lines changed

packages/go_router_builder/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.2.1
2+
3+
* Supports opt-in required extra parameters. [#117261](https://github.com/flutter/flutter/issues/117261)
4+
15
## 1.2.0
26

37
* Adds Support for ShellRoute
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// ignore_for_file: public_member_api_docs
6+
7+
import 'package:flutter/material.dart';
8+
import 'package:go_router/go_router.dart';
9+
10+
part 'extra_example.g.dart';
11+
12+
void main() => runApp(const App());
13+
14+
final GoRouter _router = GoRouter(
15+
routes: $appRoutes,
16+
initialLocation: '/splash',
17+
);
18+
19+
class App extends StatelessWidget {
20+
const App({super.key});
21+
22+
@override
23+
Widget build(BuildContext context) {
24+
return MaterialApp.router(
25+
routerConfig: _router,
26+
);
27+
}
28+
}
29+
30+
class Extra {
31+
const Extra(this.value);
32+
33+
final int value;
34+
}
35+
36+
@TypedGoRoute<RequiredExtraRoute>(path: '/requiredExtra')
37+
class RequiredExtraRoute extends GoRouteData {
38+
const RequiredExtraRoute({required this.$extra});
39+
40+
final Extra $extra;
41+
42+
@override
43+
Widget build(BuildContext context, GoRouterState state) =>
44+
RequiredExtraScreen(extra: $extra);
45+
}
46+
47+
class RequiredExtraScreen extends StatelessWidget {
48+
const RequiredExtraScreen({super.key, required this.extra});
49+
50+
final Extra extra;
51+
52+
@override
53+
Widget build(BuildContext context) {
54+
return Scaffold(
55+
appBar: AppBar(title: const Text('Required Extra')),
56+
body: Center(child: Text('Extra: ${extra.value}')),
57+
);
58+
}
59+
}
60+
61+
@TypedGoRoute<OptionalExtraRoute>(path: '/optionalExtra')
62+
class OptionalExtraRoute extends GoRouteData {
63+
const OptionalExtraRoute({this.$extra});
64+
65+
final Extra? $extra;
66+
67+
@override
68+
Widget build(BuildContext context, GoRouterState state) =>
69+
OptionalExtraScreen(extra: $extra);
70+
}
71+
72+
class OptionalExtraScreen extends StatelessWidget {
73+
const OptionalExtraScreen({super.key, this.extra});
74+
75+
final Extra? extra;
76+
77+
@override
78+
Widget build(BuildContext context) {
79+
return Scaffold(
80+
appBar: AppBar(title: const Text('Optional Extra')),
81+
body: Center(child: Text('Extra: ${extra?.value}')),
82+
);
83+
}
84+
}
85+
86+
@TypedGoRoute<SplashRoute>(path: '/splash')
87+
class SplashRoute extends GoRouteData {
88+
const SplashRoute();
89+
90+
@override
91+
Widget build(BuildContext context, GoRouterState state) => const Splash();
92+
}
93+
94+
class Splash extends StatelessWidget {
95+
const Splash({super.key});
96+
97+
@override
98+
Widget build(BuildContext context) {
99+
return Scaffold(
100+
appBar: AppBar(title: const Text('Splash')),
101+
body: Column(
102+
mainAxisAlignment: MainAxisAlignment.center,
103+
children: <Widget>[
104+
const Placeholder(),
105+
ElevatedButton(
106+
onPressed: () =>
107+
const RequiredExtraRoute($extra: Extra(1)).go(context),
108+
child: const Text('Required Extra'),
109+
),
110+
ElevatedButton(
111+
onPressed: () =>
112+
const OptionalExtraRoute($extra: Extra(2)).go(context),
113+
child: const Text('Optional Extra'),
114+
),
115+
ElevatedButton(
116+
onPressed: () => const OptionalExtraRoute().go(context),
117+
child: const Text('Optional Extra (null)'),
118+
),
119+
],
120+
),
121+
);
122+
}
123+
}

packages/go_router_builder/example/lib/extra_example.g.dart

Lines changed: 81 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/go_router_builder/lib/src/route_config.dart

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,10 @@ RouteBase get $_routeGetterName => ${_routeDefinition()};
265265

266266
String get _newFromState {
267267
final StringBuffer buffer = StringBuffer('=>');
268-
if (_ctor.isConst && _ctorParams.isEmpty && _ctorQueryParams.isEmpty) {
268+
if (_ctor.isConst &&
269+
_ctorParams.isEmpty &&
270+
_ctorQueryParams.isEmpty &&
271+
_extraParam == null) {
269272
buffer.writeln('const ');
270273
}
271274

@@ -367,7 +370,7 @@ GoRouteData.\$route(
367370
);
368371
}
369372

370-
if (!_pathParams.contains(element.name)) {
373+
if (!_pathParams.contains(element.name) && !element.isExtraField) {
371374
throw InvalidGenerationSourceError(
372375
'Missing param `${element.name}` in path.',
373376
element: element,
@@ -438,13 +441,7 @@ GoRouteData.\$route(
438441

439442
late final List<ParameterElement> _ctorParams =
440443
_ctor.parameters.where((ParameterElement element) {
441-
if (element.isRequired) {
442-
if (element.isExtraField) {
443-
throw InvalidGenerationSourceError(
444-
'Parameters named `$extraFieldName` cannot be required.',
445-
element: element,
446-
);
447-
}
444+
if (element.isRequired && !element.isExtraField) {
448445
return true;
449446
}
450447
return false;

packages/go_router_builder/lib/src/type_helpers.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ String encodeField(PropertyAccessorElement element) {
9393
String enumMapName(InterfaceType type) => '_\$${type.element.name}EnumMap';
9494

9595
String _stateValueAccess(ParameterElement element) {
96-
if (element.isRequired) {
97-
return 'params[${escapeDartString(element.name)}]!';
96+
if (element.isExtraField) {
97+
return 'extra as ${element.type.getDisplayString(withNullability: element.isOptional)}';
9898
}
9999

100-
if (element.isExtraField) {
101-
return 'extra as ${element.type.getDisplayString(withNullability: true)}';
100+
if (element.isRequired) {
101+
return 'params[${escapeDartString(element.name)}]!';
102102
}
103103

104104
if (element.isOptional) {

packages/go_router_builder/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: go_router_builder
22
description: >-
33
A builder that supports generated strongly-typed route helpers for
44
package:go_router
5-
version: 1.2.0
5+
version: 1.2.1
66
repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder
77
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22
88

packages/go_router_builder/test/builder_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ Future<void> main() async {
2424
const Set<String> _expectedAnnotatedTests = <String>{
2525
'AppliedToWrongClassType',
2626
'BadPathParam',
27-
'ExtraMustBeOptional',
2827
'ExtraValueRoute',
28+
'RequiredExtraValueRoute',
2929
'MissingPathParam',
3030
'MissingPathValue',
3131
'MissingTypeAnnotation',

packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,6 @@ class NullableRequiredParam extends GoRouteData {
5353
final int? id;
5454
}
5555

56-
@ShouldThrow(
57-
r'Parameters named `$extra` cannot be required.',
58-
)
59-
@TypedGoRoute<ExtraMustBeOptional>(path: r'bob/:$extra')
60-
class ExtraMustBeOptional extends GoRouteData {
61-
ExtraMustBeOptional({required this.$extra});
62-
final int $extra;
63-
}
64-
6556
@ShouldThrow(
6657
'Missing param `id` in path.',
6758
)
@@ -204,6 +195,36 @@ class ExtraValueRoute extends GoRouteData {
204195
final int? $extra;
205196
}
206197

198+
@ShouldGenerate(r'''
199+
RouteBase get $requiredExtraValueRoute => GoRouteData.$route(
200+
path: '/default-value-route',
201+
factory: $RequiredExtraValueRouteExtension._fromState,
202+
);
203+
204+
extension $RequiredExtraValueRouteExtension on RequiredExtraValueRoute {
205+
static RequiredExtraValueRoute _fromState(GoRouterState state) =>
206+
RequiredExtraValueRoute(
207+
$extra: state.extra as int,
208+
);
209+
210+
String get location => GoRouteData.$location(
211+
'/default-value-route',
212+
);
213+
214+
void go(BuildContext context) => context.go(location, extra: $extra);
215+
216+
void push(BuildContext context) => context.push(location, extra: $extra);
217+
218+
void pushReplacement(BuildContext context) =>
219+
context.pushReplacement(location, extra: $extra);
220+
}
221+
''')
222+
@TypedGoRoute<RequiredExtraValueRoute>(path: '/default-value-route')
223+
class RequiredExtraValueRoute extends GoRouteData {
224+
RequiredExtraValueRoute({required this.$extra});
225+
final int $extra;
226+
}
227+
207228
@ShouldThrow(
208229
'Default value used with a nullable type. Only non-nullable type can have a default value.',
209230
todo: 'Remove the default value or make the type non-nullable.',

0 commit comments

Comments
 (0)