Skip to content
Merged
4 changes: 4 additions & 0 deletions packages/go_router_builder/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.1.6

* Supports default values for `Set`, `List` and `Iterable` route parameters.

## 1.1.5

* Replaces unnecessary Flutter SDK constraint with corresponding Dart
Expand Down
73 changes: 73 additions & 0 deletions packages/go_router_builder/example/lib/all_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ part 'all_types.g.dart';
TypedGoRoute<StringRoute>(path: 'string-route/:requiredStringField'),
TypedGoRoute<UriRoute>(path: 'uri-route/:requiredUriField'),
TypedGoRoute<IterableRoute>(path: 'iterable-route'),
TypedGoRoute<IterableRouteWithDefaultValues>(
path: 'iterable-route-with-default-values'),
])
@immutable
class AllTypesBaseRoute extends GoRouteData {
Expand Down Expand Up @@ -342,6 +344,77 @@ class IterableRoute extends GoRouteData {
);
}

class IterableRouteWithDefaultValues extends GoRouteData {
const IterableRouteWithDefaultValues({
this.intIterableField = const <int>[0],
this.doubleIterableField = const <double>[0, 1, 2],
this.stringIterableField = const <String>['defaultValue'],
this.boolIterableField = const <bool>[false],
this.enumIterableField = const <SportDetails>[
SportDetails.tennis,
SportDetails.hockey
],
this.intListField = const <int>[0],
this.doubleListField = const <double>[1, 2, 3],
this.stringListField = const <String>['defaultValue0', 'defaultValue1'],
this.boolListField = const <bool>[true],
this.enumListField = const <SportDetails>[SportDetails.football],
this.intSetField = const <int>{0, 1},
this.doubleSetField = const <double>{},
this.stringSetField = const <String>{'defaultValue'},
this.boolSetField = const <bool>{true, false},
this.enumSetField = const <SportDetails>{SportDetails.hockey},
});

final Iterable<int> intIterableField;
final List<int> intListField;
final Set<int> intSetField;

final Iterable<double> doubleIterableField;
final List<double> doubleListField;
final Set<double> doubleSetField;

final Iterable<String> stringIterableField;
final List<String> stringListField;
final Set<String> stringSetField;

final Iterable<bool> boolIterableField;
final List<bool> boolListField;
final Set<bool> boolSetField;

final Iterable<SportDetails> enumIterableField;
final List<SportDetails> enumListField;
final Set<SportDetails> enumSetField;

@override
Widget build(BuildContext context, GoRouterState state) => BasePage<String>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe build a widget that holds all these parameter as its property, and then you can use tester.widget<>(find.byType) to get the widget and then you can compare the value of each property in the widget.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented it in test: Add better tests

dataTitle: 'IterableRouteWithDefaultValues',
queryParamWithDefaultValue: <String, Iterable<dynamic>>{
'intIterableField': intIterableField,
'intListField': intListField,
'intSetField': intSetField,
'doubleIterableField': doubleIterableField,
'doubleListField': doubleListField,
'doubleSetField': doubleSetField,
'stringIterableField': stringIterableField,
'stringListField': stringListField,
'stringSetField': stringSetField,
'boolIterableField': boolIterableField,
'boolListField': boolListField,
'boolSetField': boolSetField,
'enumIterableField': enumIterableField,
'enumListField': enumListField,
'enumSetField': enumSetField,
}.toString(),
);

Widget drawerTile(BuildContext context) => ListTile(
title: const Text('IterableRouteWithDefaultValues'),
onTap: () => go(context),
selected: GoRouter.of(context).location == location,
);
}

class BasePage<T> extends StatelessWidget {
const BasePage({
required this.dataTitle,
Expand Down
118 changes: 118 additions & 0 deletions packages/go_router_builder/example/lib/all_types.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/go_router_builder/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ environment:
dependencies:
flutter:
sdk: flutter
go_router: ^6.0.0
go_router: ^6.2.0
provider: ^6.0.0

dev_dependencies:
Expand Down
20 changes: 20 additions & 0 deletions packages/go_router_builder/example/test/all_types_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,24 @@ void main() {
'/iterable-route?int-list-field=1&int-list-field=2&int-list-field=3'),
findsOneWidget);
});

testWidgets(
'It should navigate to the iterable route with the its default values',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'It should navigate to the iterable route with the its default values',
'It should navigate to the iterable route with its default values',

(WidgetTester tester) async {
await tester.pumpWidget(AllTypesApp());

final ScaffoldState scaffoldState =
tester.firstState(find.byType(Scaffold));

const IterableRouteWithDefaultValues().go(scaffoldState.context);
await tester.pumpAndSettle();
expect(find.text('IterableRouteWithDefaultValues'), findsOneWidget);
expect(
find.text(
'Query param with default value: {intIterableField: [0], intListField: [0], intSetField: {0, 1}, doubleIterableField: [0.0, 1.0, 2.0], doubleListField: [1.0, 2.0, 3.0], doubleSetField: {}, stringIterableField: [defaultValue], stringListField: [defaultValue0, defaultValue1], stringSetField: {defaultValue}, boolIterableField: [false], boolListField: [true], boolSetField: {true, false}, enumIterableField: [SportDetails.tennis, SportDetails.hockey], enumListField: [SportDetails.football], enumSetField: {SportDetails.hockey}}',
),
findsOneWidget,
);
expect(find.text('/iterable-route-with-default-values'), findsOneWidget);
});
}
29 changes: 13 additions & 16 deletions packages/go_router_builder/lib/src/type_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,14 @@ String decodeParameter(ParameterElement element) {
final DartType paramType = element.type;
for (final _TypeHelper helper in _helpers) {
if (helper._matchesType(paramType)) {
return helper._decode(element);
String decoded = helper._decode(element);
if (element.isOptional && element.hasDefaultValue) {
if (element.type.isNullableType) {
throw NullableDefaultValueError(element);
}
decoded += ' ?? ${element.defaultValueCode!}';
}
return decoded;
}
}

Expand Down Expand Up @@ -95,14 +102,7 @@ String _stateValueAccess(ParameterElement element) {
}

if (element.isOptional) {
String value = 'queryParams[${escapeDartString(element.name.kebab)}]';
if (element.hasDefaultValue) {
if (element.type.isNullableType) {
throw NullableDefaultValueError(element);
}
value += ' ?? ${element.defaultValueCode!}';
}
return value;
return 'queryParams[${escapeDartString(element.name.kebab)}]';
}

throw InvalidGenerationSourceError(
Expand Down Expand Up @@ -292,6 +292,7 @@ state.queryParametersAll[${escapeDartString(parameterElement.name.kebab)}]''';

@override
String _encode(String fieldName, DartType type) {
final String nullAwareAccess = type.isNullableType ? '?' : '';
if (type is ParameterizedType) {
final DartType iterableType = type.typeArguments.first;

Expand All @@ -300,15 +301,15 @@ state.queryParametersAll[${escapeDartString(parameterElement.name.kebab)}]''';
for (final _TypeHelper helper in _helpers) {
if (helper._matchesType(iterableType)) {
entriesTypeEncoder = '''
?.map((e) => ${helper._encode('e', iterableType)}).toList()''';
$nullAwareAccess.map((e) => ${helper._encode('e', iterableType)}).toList()''';
}
}
return '''
$fieldName$entriesTypeEncoder''';
}

return '''
$fieldName?.map((e) => e.toString()).toList()''';
$fieldName$nullAwareAccess.map((e) => e.toString()).toList()''';
}

@override
Expand All @@ -326,14 +327,10 @@ abstract class _TypeHelperWithHelper extends _TypeHelper {
final DartType paramType = parameterElement.type;

if (!parameterElement.isRequired) {
String decoded = '$convertMapValueHelperName('
return '$convertMapValueHelperName('
'${escapeDartString(parameterElement.name.kebab)}, '
'state.queryParams, '
'${helperName(paramType)})';
if (parameterElement.hasDefaultValue) {
decoded += ' ?? ${parameterElement.defaultValueCode!}';
}
return decoded;
}
return '${helperName(paramType)}'
'(state.${_stateValueAccess(parameterElement)})';
Expand Down
2 changes: 1 addition & 1 deletion packages/go_router_builder/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: go_router_builder
description: >-
A builder that supports generated strongly-typed route helpers for
package:go_router
version: 1.1.5
version: 1.1.6
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

Expand Down
1 change: 1 addition & 0 deletions packages/go_router_builder/test/builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ const Set<String> _expectedAnnotatedTests = <String>{
'EnumParam',
'DefaultValueRoute',
'NullableDefaultValueRoute',
'IterableDefaultValueRoute',
};
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,38 @@ class NullableDefaultValueRoute extends GoRouteData {
NullableDefaultValueRoute({this.param = 0});
final int? param;
}

@ShouldGenerate(r'''
GoRoute get $iterableDefaultValueRoute => GoRouteData.$route(
path: '/iterable-default-value-route',
factory: $IterableDefaultValueRouteExtension._fromState,
);

extension $IterableDefaultValueRouteExtension on IterableDefaultValueRoute {
static IterableDefaultValueRoute _fromState(GoRouterState state) =>
IterableDefaultValueRoute(
param:
state.queryParametersAll['param']?.map(int.parse) ?? const <int>[0],
);

String get location => GoRouteData.$location(
'/iterable-default-value-route',
queryParams: {
if (param != const <int>[0])
'param': param.map((e) => e.toString()).toList(),
},
);

void go(BuildContext context) => context.go(location);

void push(BuildContext context) => context.push(location);

void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
}
''')
@TypedGoRoute<IterableDefaultValueRoute>(path: '/iterable-default-value-route')
class IterableDefaultValueRoute extends GoRouteData {
IterableDefaultValueRoute({this.param = const <int>[0]});
final Iterable<int> param;
}