Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]

## 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

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
// 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
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
/// 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