Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[go_router_builder] Adds support for required $extra parameter #3627

Merged
merged 11 commits into from
Apr 11, 2023
3 changes: 3 additions & 0 deletions packages/go_router_builder/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 1.1.8
* Supports opt-in required extra parameters. [#117261]
dancamdev marked this conversation as resolved.
Show resolved Hide resolved

## 1.1.7

* Supports default values for `Set`, `List` and `Iterable` route parameters.
Expand Down
123 changes: 123 additions & 0 deletions packages/go_router_builder/example/lib/extra_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// 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, always_specify_types
dancamdev marked this conversation as resolved.
Show resolved Hide resolved

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

part 'extra_example.g.dart';

void main() => runApp(const App());

final GoRouter _router = GoRouter(
routes: $appRoutes,
initialLocation: '/splash',
);

class App extends StatelessWidget {
const App({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}

class Extra {
const Extra(this.value);

final int value;
}

@TypedGoRoute<RequiredExtraRoute>(path: '/requiredExtra')
class RequiredExtraRoute extends GoRouteData {
const RequiredExtraRoute({required this.$extra});

final Extra $extra;

@override
Widget build(BuildContext context, GoRouterState state) =>
RequiredExtraScreen(extra: $extra);
}

class RequiredExtraScreen extends StatelessWidget {
const RequiredExtraScreen({super.key, required this.extra});

final Extra extra;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Required Extra')),
body: Center(child: Text('Extra: ${extra.value}')),
);
}
}

@TypedGoRoute<OptionalExtraRoute>(path: '/optionalExtra')
class OptionalExtraRoute extends GoRouteData {
const OptionalExtraRoute({this.$extra});

final Extra? $extra;

@override
Widget build(BuildContext context, GoRouterState state) =>
OptionalExtraScreen(extra: $extra);
}

class OptionalExtraScreen extends StatelessWidget {
const OptionalExtraScreen({super.key, this.extra});

final Extra? extra;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Optional Extra')),
body: Center(child: Text('Extra: ${extra?.value}')),
);
}
}

@TypedGoRoute<SplashRoute>(path: '/splash')
class SplashRoute extends GoRouteData {
const SplashRoute();

@override
Widget build(BuildContext context, GoRouterState state) => const Splash();
}

class Splash extends StatelessWidget {
const Splash({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Splash')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Placeholder(),
ElevatedButton(
onPressed: () =>
const RequiredExtraRoute($extra: Extra(1)).go(context),
child: const Text('Required Extra'),
),
ElevatedButton(
onPressed: () =>
const OptionalExtraRoute($extra: Extra(2)).go(context),
child: const Text('Optional Extra'),
),
ElevatedButton(
onPressed: () => const OptionalExtraRoute().go(context),
child: const Text('Optional Extra (null)'),
),
],
),
);
}
}
81 changes: 81 additions & 0 deletions packages/go_router_builder/example/lib/extra_example.g.dart

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

25 changes: 6 additions & 19 deletions packages/go_router_builder/lib/src/route_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@ class RouteConfig {
);
}

// TODO(kevmoo): validate that this MUST be a subtype of `GoRouteData`
// TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to
dancamdev marked this conversation as resolved.
Show resolved Hide resolved
// 5.2+ (when Flutter 3.4+ is on stable).
// ignore: deprecated_member_use
final InterfaceElement classElement = typeParamType.element;

final RouteConfig value = RouteConfig._(path, classElement, parent);
Expand Down Expand Up @@ -202,7 +198,10 @@ GoRoute get $_routeGetterName => ${_routeDefinition()};

String get _newFromState {
final StringBuffer buffer = StringBuffer('=>');
if (_ctor.isConst && _ctorParams.isEmpty && _ctorQueryParams.isEmpty) {
if (_ctor.isConst &&
_ctorParams.isEmpty &&
_ctorQueryParams.isEmpty &&
_extraParam == null) {
buffer.writeln('const ');
}

Expand Down Expand Up @@ -290,7 +289,7 @@ GoRouteData.\$route(
);
}

if (!_pathParams.contains(element.name)) {
if (!_pathParams.contains(element.name) && !element.isExtraField) {
throw InvalidGenerationSourceError(
'Missing param `${element.name}` in path.',
element: element,
Expand Down Expand Up @@ -361,13 +360,7 @@ GoRouteData.\$route(

late final List<ParameterElement> _ctorParams =
_ctor.parameters.where((ParameterElement element) {
if (element.isRequired) {
if (element.isExtraField) {
throw InvalidGenerationSourceError(
'Parameters named `$extraFieldName` cannot be required.',
element: element,
);
}
if (element.isRequired && !element.isExtraField) {
return true;
}
return false;
Expand Down Expand Up @@ -397,16 +390,10 @@ GoRouteData.\$route(
String _enumMapConst(InterfaceType type) {
assert(type.isEnum);

// TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to
// 5.2+ (when Flutter 3.4+ is on stable).
// ignore: deprecated_member_use
final String enumName = type.element.name;

final StringBuffer buffer = StringBuffer('const ${enumMapName(type)} = {');

// TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to
// 5.2+ (when Flutter 3.4+ is on stable).
// ignore: deprecated_member_use
dancamdev marked this conversation as resolved.
Show resolved Hide resolved
for (final FieldElement enumField in type.element.fields
.where((FieldElement element) => element.isEnumConstant)) {
buffer.writeln(
Expand Down
13 changes: 5 additions & 8 deletions packages/go_router_builder/lib/src/type_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,16 @@ String encodeField(PropertyAccessorElement element) {
);
}

/// Gets the name of the `const` map generated to help encode [Enum] types.
// TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to
// 5.2+ (when Flutter 3.4+ is on stable).
// ignore: deprecated_member_use
dancamdev marked this conversation as resolved.
Show resolved Hide resolved
/// Maps the [type] to a [String] representation of the enum.
String enumMapName(InterfaceType type) => '_\$${type.element.name}EnumMap';

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

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

if (element.isOptional) {
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.7
version: 1.1.8
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
2 changes: 1 addition & 1 deletion packages/go_router_builder/test/builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ Future<void> main() async {
const Set<String> _expectedAnnotatedTests = <String>{
'AppliedToWrongClassType',
'BadPathParam',
'ExtraMustBeOptional',
'ExtraValueRoute',
'RequiredExtraValueRoute',
'MissingPathParam',
'MissingPathValue',
'MissingTypeAnnotation',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,6 @@ class NullableRequiredParam extends GoRouteData {
final int? id;
}

@ShouldThrow(
r'Parameters named `$extra` cannot be required.',
)
@TypedGoRoute<ExtraMustBeOptional>(path: r'bob/:$extra')
class ExtraMustBeOptional extends GoRouteData {
ExtraMustBeOptional({required this.$extra});
final int $extra;
}

@ShouldThrow(
'Missing param `id` in path.',
)
Expand Down Expand Up @@ -204,6 +195,36 @@ class ExtraValueRoute extends GoRouteData {
final int? $extra;
}

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

extension $RequiredExtraValueRouteExtension on RequiredExtraValueRoute {
static RequiredExtraValueRoute _fromState(GoRouterState state) =>
RequiredExtraValueRoute(
$extra: state.extra as int,
);

String get location => GoRouteData.$location(
'/default-value-route',
);

void go(BuildContext context) => context.go(location, extra: $extra);

void push(BuildContext context) => context.push(location, extra: $extra);

void pushReplacement(BuildContext context) =>
context.pushReplacement(location, extra: $extra);
}
''')
@TypedGoRoute<RequiredExtraValueRoute>(path: '/default-value-route')
class RequiredExtraValueRoute extends GoRouteData {
RequiredExtraValueRoute({required this.$extra});
final int $extra;
}

@ShouldThrow(
'Default value used with a nullable type. Only non-nullable type can have a default value.',
todo: 'Remove the default value or make the type non-nullable.',
Expand Down