Skip to content

Commit 810faf6

Browse files
[go_router_builder] Support custom types (#11068)
Closes flutter/flutter#112152 Closes flutter/flutter#110781 ## 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 01c505f commit 810faf6

11 files changed

Lines changed: 332 additions & 25 deletions

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+
## 4.3.0
2+
3+
- Adds support for custom types through `TypedQueryParameter` annotation. The `encoder`, `decoder` and `compare` parameters allow specifying custom functions for encoding, decoding and comparing query parameters in `TypedGoRoute` constructors. For example, you can use a `DateTime` parameter with a custom encoder and decoder to convert it to and from a string representation in the URL.
4+
15
## 4.2.1
26

37
* Adds support for analyzer 11 and 12.

packages/go_router_builder/example/lib/typed_query_parameter_example.dart

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@ part 'typed_query_parameter_example.g.dart';
1111

1212
void main() => runApp(App());
1313

14+
class CustomParameter {
15+
const CustomParameter({required this.valueString, required this.valueInt});
16+
17+
final String valueString;
18+
final int valueInt;
19+
20+
static String encode(CustomParameter parameter) {
21+
return '${parameter.valueString},${parameter.valueInt}';
22+
}
23+
24+
static CustomParameter decode(String value) {
25+
final List<String> parts = value.split(',');
26+
return CustomParameter(
27+
valueString: parts[0],
28+
valueInt: int.parse(parts[1]),
29+
);
30+
}
31+
32+
static bool compare(CustomParameter a, CustomParameter b) {
33+
return a.valueString != b.valueString || a.valueInt != b.valueInt;
34+
}
35+
}
36+
1437
class App extends StatelessWidget {
1538
App({super.key});
1639

@@ -31,30 +54,52 @@ class IntRoute extends GoRouteData with $IntRoute {
3154
@TypedQueryParameter(name: 'int_field_with_default_value')
3255
this.intFieldWithDefaultValue = 1,
3356
@TypedQueryParameter(name: 'int field') this.intFieldWithSpace,
57+
@TypedQueryParameter<CustomParameter>(
58+
encoder: CustomParameter.encode,
59+
decoder: CustomParameter.decode,
60+
)
61+
this.customField,
62+
@TypedQueryParameter<CustomParameter>(
63+
encoder: CustomParameter.encode,
64+
decoder: CustomParameter.decode,
65+
compare: CustomParameter.compare,
66+
)
67+
this.customFieldWithDefaultValue = const CustomParameter(
68+
valueString: 'default',
69+
valueInt: 0,
70+
),
3471
});
3572

3673
final int? intField;
3774
final int intFieldWithDefaultValue;
3875
final int? intFieldWithSpace;
76+
final CustomParameter? customField;
77+
final CustomParameter customFieldWithDefaultValue;
3978
@override
4079
Widget build(BuildContext context, GoRouterState state) => Screen(
4180
intField: intField,
4281
intFieldWithDefaultValue: intFieldWithDefaultValue,
4382
intFieldWithSpace: intFieldWithSpace,
83+
customField: customField,
84+
customFieldWithDefaultValue: customFieldWithDefaultValue,
4485
);
4586
}
4687

4788
class Screen extends StatelessWidget {
4889
const Screen({
90+
super.key,
4991
required this.intField,
5092
required this.intFieldWithDefaultValue,
5193
this.intFieldWithSpace,
52-
super.key,
94+
this.customField,
95+
required this.customFieldWithDefaultValue,
5396
});
5497

5598
final int? intField;
5699
final int intFieldWithDefaultValue;
57100
final int? intFieldWithSpace;
101+
final CustomParameter? customField;
102+
final CustomParameter customFieldWithDefaultValue;
58103

59104
@override
60105
Widget build(BuildContext context) => Scaffold(
@@ -75,6 +120,8 @@ class Screen extends StatelessWidget {
75120
intField: newValue,
76121
intFieldWithDefaultValue: intFieldWithDefaultValue,
77122
intFieldWithSpace: intFieldWithSpace,
123+
customField: customField,
124+
customFieldWithDefaultValue: customFieldWithDefaultValue,
78125
).go(context);
79126
},
80127
),
@@ -88,6 +135,8 @@ class Screen extends StatelessWidget {
88135
intField: intField,
89136
intFieldWithDefaultValue: newValue,
90137
intFieldWithSpace: intFieldWithSpace,
138+
customField: customField,
139+
customFieldWithDefaultValue: customFieldWithDefaultValue,
91140
).go(context);
92141
},
93142
),
@@ -101,6 +150,46 @@ class Screen extends StatelessWidget {
101150
intField: intField,
102151
intFieldWithDefaultValue: intFieldWithDefaultValue,
103152
intFieldWithSpace: newValue,
153+
customField: customField,
154+
customFieldWithDefaultValue: customFieldWithDefaultValue,
155+
).go(context);
156+
},
157+
),
158+
ListTile(
159+
title: const Text('customField:'),
160+
subtitle: Text(
161+
customField == null ? '' : CustomParameter.encode(customField!),
162+
),
163+
trailing: const Icon(Icons.add),
164+
onTap: () {
165+
final newValue = CustomParameter(
166+
valueString: '${customField?.valueString ?? ''}-',
167+
valueInt: (customField?.valueInt ?? 0) + 1,
168+
);
169+
IntRoute(
170+
intField: intField,
171+
intFieldWithDefaultValue: intFieldWithDefaultValue,
172+
intFieldWithSpace: intFieldWithSpace,
173+
customField: newValue,
174+
customFieldWithDefaultValue: customFieldWithDefaultValue,
175+
).go(context);
176+
},
177+
),
178+
ListTile(
179+
title: const Text('customFieldWithDefaultValue:'),
180+
subtitle: Text(CustomParameter.encode(customFieldWithDefaultValue)),
181+
trailing: const Icon(Icons.add),
182+
onTap: () {
183+
final newValue = CustomParameter(
184+
valueString: '${customFieldWithDefaultValue.valueString}-',
185+
valueInt: customFieldWithDefaultValue.valueInt + 1,
186+
);
187+
IntRoute(
188+
intField: intField,
189+
intFieldWithDefaultValue: intFieldWithDefaultValue,
190+
intFieldWithSpace: intFieldWithSpace,
191+
customField: customField,
192+
customFieldWithDefaultValue: newValue,
104193
).go(context);
105194
},
106195
),

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

Lines changed: 21 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: ^17.1.0
12+
go_router: ^17.2.0
1313
provider: 6.0.5
1414

1515
dev_dependencies:

packages/go_router_builder/example/test/typed_query_parameter_test.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,23 @@ void main() {
4040
);
4141
expect(find.text('2'), findsOne);
4242
});
43+
44+
testWidgets('It should modify the custom fields when tapped', (tester) async {
45+
await tester.pumpWidget(App());
46+
47+
expect(find.text('customField:'), findsOne);
48+
expect(find.text('customFieldWithDefaultValue:'), findsOne);
49+
50+
expect(find.text('default,0'), findsOne);
51+
52+
await tester.tap(find.text('customField:'));
53+
await tester.pumpAndSettle();
54+
expect(find.text('-,1'), findsOne);
55+
expect(find.text('default,0'), findsOne);
56+
57+
await tester.tap(find.text('customFieldWithDefaultValue:'));
58+
await tester.pumpAndSettle();
59+
expect(find.text('-,1'), findsOne);
60+
expect(find.text('default-,1'), findsOne);
61+
});
4362
}

packages/go_router_builder/lib/src/route_config.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,7 @@ mixin _GoRouteMixin on RouteBaseConfig {
323323
if (param.type.isNullableType) {
324324
throw NullableDefaultValueError(param);
325325
}
326-
conditions.add(
327-
compareField(param, parameterName, param.defaultValueCode!),
328-
);
326+
conditions.add(compareField(param));
329327
} else if (param.type.isNullableType) {
330328
conditions.add('$selfFieldName.$parameterName != null');
331329
}

0 commit comments

Comments
 (0)