Skip to content

Commit df85ef0

Browse files
[go_router_builder] Add support for @TypedGoRouteParameter to customize parameter names (#10793)
Relates to flutter/flutter#112152 Add support for `@TypedGoRouteParameter` to customize parameter names Needs #10792 to get merged first ## Pre-Review Checklist **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent f07e754 commit df85ef0

10 files changed

Lines changed: 325 additions & 10 deletions

packages/go_router_builder/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 4.2.0
2+
3+
- Adds supports for `TypedQueryParameter` annotation.
4+
Its `name` parameter allows specifying a different name for the query parameter than the field name. The name is escaped to be URL-safe. For example `@TypedQueryParameter(name: 'field with space')` will generate a query parameter named `field+with+space`.
5+
16
## 4.1.3
27

38
* Requires `analyzer` 8.2 or higher, to avoid experimental APIs.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2013 The Flutter Authors
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 'typed_query_parameter_example.g.dart';
11+
12+
void main() => runApp(App());
13+
14+
class App extends StatelessWidget {
15+
App({super.key});
16+
17+
@override
18+
Widget build(BuildContext context) =>
19+
MaterialApp.router(routerConfig: _router);
20+
21+
final GoRouter _router = GoRouter(
22+
initialLocation: '/int-route',
23+
routes: $appRoutes,
24+
);
25+
}
26+
27+
@TypedGoRoute<IntRoute>(path: '/int-route')
28+
class IntRoute extends GoRouteData with $IntRoute {
29+
IntRoute({
30+
@TypedQueryParameter(name: 'intField') this.intField,
31+
@TypedQueryParameter(name: 'int_field_with_default_value')
32+
this.intFieldWithDefaultValue = 1,
33+
@TypedQueryParameter(name: 'int field') this.intFieldWithSpace,
34+
});
35+
36+
final int? intField;
37+
final int intFieldWithDefaultValue;
38+
final int? intFieldWithSpace;
39+
@override
40+
Widget build(BuildContext context, GoRouterState state) => Screen(
41+
intField: intField,
42+
intFieldWithDefaultValue: intFieldWithDefaultValue,
43+
intFieldWithSpace: intFieldWithSpace,
44+
);
45+
}
46+
47+
class Screen extends StatelessWidget {
48+
const Screen({
49+
required this.intField,
50+
required this.intFieldWithDefaultValue,
51+
this.intFieldWithSpace,
52+
super.key,
53+
});
54+
55+
final int? intField;
56+
final int intFieldWithDefaultValue;
57+
final int? intFieldWithSpace;
58+
59+
@override
60+
Widget build(BuildContext context) => Scaffold(
61+
appBar: AppBar(
62+
title: const Text('Go router with custom URI parameter names'),
63+
),
64+
body: Center(
65+
child: Column(
66+
mainAxisSize: MainAxisSize.min,
67+
children: <Widget>[
68+
ListTile(
69+
title: const Text('intField:'),
70+
subtitle: Text('$intField'),
71+
trailing: const Icon(Icons.add),
72+
onTap: () {
73+
final int newValue = (intField ?? 0) + 1;
74+
IntRoute(
75+
intField: newValue,
76+
intFieldWithDefaultValue: intFieldWithDefaultValue,
77+
intFieldWithSpace: intFieldWithSpace,
78+
).go(context);
79+
},
80+
),
81+
ListTile(
82+
title: const Text('intFieldWithDefaultValue:'),
83+
subtitle: Text('$intFieldWithDefaultValue'),
84+
trailing: const Icon(Icons.add),
85+
onTap: () {
86+
final int newValue = intFieldWithDefaultValue + 1;
87+
IntRoute(
88+
intField: intField,
89+
intFieldWithDefaultValue: newValue,
90+
intFieldWithSpace: intFieldWithSpace,
91+
).go(context);
92+
},
93+
),
94+
ListTile(
95+
title: const Text('intFieldWithSpace:'),
96+
subtitle: Text('$intFieldWithSpace'),
97+
trailing: const Icon(Icons.add),
98+
onTap: () {
99+
final int newValue = (intFieldWithSpace ?? 0) + 1;
100+
IntRoute(
101+
intField: intField,
102+
intFieldWithDefaultValue: intFieldWithDefaultValue,
103+
intFieldWithSpace: newValue,
104+
).go(context);
105+
},
106+
),
107+
],
108+
),
109+
),
110+
);
111+
}

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

Lines changed: 73 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/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ dependencies:
99
collection: ^1.15.0
1010
flutter:
1111
sdk: flutter
12-
go_router: ^16.2.0
12+
go_router: ^17.1.0
1313
provider: 6.0.5
1414

1515
dev_dependencies:
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:go_router_builder_example/typed_query_parameter_example.dart';
7+
8+
void main() {
9+
testWidgets('It should increase the counts when pressed', (tester) async {
10+
await tester.pumpWidget(App());
11+
12+
expect(
13+
find.text('null'),
14+
findsExactly(2),
15+
reason: 'intField and intFieldWithSpace are both null',
16+
);
17+
expect(find.text('1'), findsOne);
18+
19+
await tester.tap(find.text('intField:'));
20+
await tester.pumpAndSettle();
21+
expect(
22+
find.text('1'),
23+
findsExactly(2),
24+
reason: 'intField and intFieldWithDefaultValue are both 1',
25+
);
26+
expect(find.text('null'), findsOne);
27+
28+
await tester.tap(find.text('intFieldWithDefaultValue:'));
29+
await tester.pumpAndSettle();
30+
expect(find.text('1'), findsOne);
31+
expect(find.text('2'), findsOne);
32+
expect(find.text('null'), findsOne);
33+
34+
await tester.tap(find.text('intFieldWithSpace:'));
35+
await tester.pumpAndSettle();
36+
expect(
37+
find.text('1'),
38+
findsExactly(2),
39+
reason: 'intField and intFieldWithSpace are both 1',
40+
);
41+
expect(find.text('2'), findsOne);
42+
});
43+
}

packages/go_router_builder/lib/src/route_config.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,6 @@ mixin _GoRouteMixin on RouteBaseConfig {
318318

319319
for (final FormalParameterElement param in _ctorQueryParams) {
320320
final String parameterName = param.displayName;
321-
322321
final conditions = <String>[];
323322
if (param.hasDefaultValue) {
324323
if (param.type.isNullableType) {
@@ -335,7 +334,7 @@ mixin _GoRouteMixin on RouteBaseConfig {
335334
line = 'if (${conditions.join(' && ')}) ';
336335
}
337336
line +=
338-
'${escapeDartString(parameterName.kebab)}: '
337+
'${param.uriName}: '
339338
'${_encodeFor(parameterName)},';
340339

341340
buffer.writeln(line);

packages/go_router_builder/lib/src/type_helpers.dart

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,7 @@ String _stateValueAccess(
244244
if (pathParameters.contains(element.displayName)) {
245245
access = 'pathParameters[${escapeDartString(element.displayName)}]$suffix';
246246
} else {
247-
access =
248-
'uri.queryParameters[${escapeDartString(element.displayName.kebab)}]$suffix';
247+
access = 'uri.queryParameters[${element.uriName}]$suffix';
249248
}
250249

251250
return access;
@@ -635,11 +634,11 @@ class _TypeHelperIterable extends _TypeHelperWithHelper {
635634

636635
return '''
637636
state.uri.queryParametersAll[
638-
${escapeDartString(parameterElement.displayName.kebab)}]
637+
${parameterElement.uriName}]
639638
?.map($entriesTypeDecoder)$convertToNotNull$iterableCaster$fallBack''';
640639
}
641640
return '''
642-
state.uri.queryParametersAll[${escapeDartString(parameterElement.displayName.kebab)}]''';
641+
state.uri.queryParametersAll[${parameterElement.uriName}]''';
643642
}
644643

645644
@override
@@ -822,7 +821,7 @@ abstract class _TypeHelperWithHelper extends _TypeHelper {
822821
if (!pathParameters.contains(parameterName) &&
823822
(paramType.isNullableType || parameterElement.hasDefaultValue)) {
824823
return '$convertMapValueHelperName('
825-
'${escapeDartString(parameterName.kebab)}, '
824+
'${parameterElement.uriName}, '
826825
'state.uri.queryParameters, '
827826
'${helperName(paramType)})';
828827
}
@@ -853,6 +852,18 @@ extension FormalParameterElementExtension on FormalParameterElement {
853852

854853
/// Returns `true` if `this` has a name that matches [extraFieldName];
855854
bool get isExtraField => displayName == extraFieldName;
855+
856+
/// Returns the URI name for this parameter, taking into account any
857+
/// `TypedQueryParameter` annotation that may override the name.
858+
String get uriName {
859+
final typedQueryParameterReader = ConstantReader(
860+
_typedQueryParameterChecker.firstAnnotationOf(this),
861+
);
862+
final String name =
863+
typedQueryParameterReader.peek('name')?.stringValue ??
864+
displayName.kebab;
865+
return escapeDartString(name);
866+
}
856867
}
857868

858869
/// An error thrown when a default value is used with a nullable type.
@@ -865,3 +876,7 @@ class NullableDefaultValueError extends InvalidGenerationSourceError {
865876
element: element,
866877
);
867878
}
879+
880+
const _typedQueryParameterChecker = TypeChecker.fromUrl(
881+
'package:go_router/src/route_data.dart#TypedQueryParameter',
882+
);

packages/go_router_builder/pubspec.yaml

Lines changed: 2 additions & 2 deletions
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: 4.1.3
5+
version: 4.2.0
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

@@ -27,7 +27,7 @@ dev_dependencies:
2727
dart_style: ">=2.3.7 <4.0.0"
2828
flutter:
2929
sdk: flutter
30-
go_router: ^16.2.0
30+
go_router: ^17.1.0
3131
leak_tracker_flutter_testing: ">=3.0.0"
3232
package_config: ^2.1.1
3333
pub_semver: ^2.1.5
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:go_router/go_router.dart';
6+
7+
mixin $OverriddenParameterNameRoute {}
8+
9+
@TypedGoRoute<OverriddenParameterNameRoute>(path: '/typed-go-route-parameter')
10+
class OverriddenParameterNameRoute extends GoRouteData
11+
with $OverriddenParameterNameRoute {
12+
OverriddenParameterNameRoute({
13+
@TypedQueryParameter(name: 'parameterNameOverride') this.withAnnotation,
14+
@TypedQueryParameter(name: 'name with space') this.withSpace,
15+
});
16+
final int? withAnnotation;
17+
final String? withSpace;
18+
}

0 commit comments

Comments
 (0)