From 8cf6f38d514133b5b0a3470c5f911e89ac6e1068 Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Wed, 13 Aug 2025 21:53:22 +0200 Subject: [PATCH 1/6] Add support for relative routes --- packages/go_router_builder/CHANGELOG.md | 4 + packages/go_router_builder/README.md | 27 ++ .../example/lib/go_relative.dart | 198 ++++++++++++ .../example/lib/go_relative.g.dart | 152 +++++++++ .../example/lib/readme_excerpts.dart | 26 ++ .../example/lib/readme_excerpts.g.dart | 31 ++ .../go_router_builder/example/pubspec.yaml | 2 +- .../example/test/go_relative_test.dart | 55 ++++ .../lib/src/go_router_generator.dart | 1 + .../lib/src/route_config.dart | 294 +++++++++++++----- packages/go_router_builder/pubspec.yaml | 2 +- .../test_inputs/go_relative.dart | 42 +++ .../test_inputs/go_relative.dart.expect | 132 ++++++++ .../test_inputs/no_mixin_relative.dart | 8 + .../test_inputs/no_mixin_relative.dart.expect | 1 + .../relative_route_with_absolute_path.dart | 14 + ...ative_route_with_absolute_path.dart.expect | 1 + ..._route_with_direct_absolute_sub_route.dart | 33 ++ ...with_direct_absolute_sub_route.dart.expect | 1 + ...oute_with_indirect_absolute_sub_route.dart | 42 +++ ...th_indirect_absolute_sub_route.dart.expect | 1 + 21 files changed, 979 insertions(+), 88 deletions(-) create mode 100644 packages/go_router_builder/example/lib/go_relative.dart create mode 100644 packages/go_router_builder/example/lib/go_relative.g.dart create mode 100644 packages/go_router_builder/example/test/go_relative_test.dart create mode 100644 packages/go_router_builder/test_inputs/go_relative.dart create mode 100644 packages/go_router_builder/test_inputs/go_relative.dart.expect create mode 100644 packages/go_router_builder/test_inputs/no_mixin_relative.dart create mode 100644 packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect create mode 100644 packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart create mode 100644 packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart.expect create mode 100644 packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart create mode 100644 packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart.expect create mode 100644 packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart create mode 100644 packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart.expect diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index c47f328b9be..344e81a2339 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.3.0 + +- Adds support for `TypedRelativeGoRoute`. + ## 3.2.1 - Changes generated whitespace for better compatibility with new Dart formatter. diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index 643a664b6fa..206c406fd97 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -455,6 +455,33 @@ class MyGoRouteData extends GoRouteData with _$MyGoRouteData { An example is available [here](https://github.com/flutter/packages/blob/main/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart). +## Relative routes + +Define a relative route by extending RelativeGoRouteData. + + +```dart +@TypedRelativeGoRoute( + path: 'details', +) +class DetailsRoute extends RelativeGoRouteData with _$DetailsRoute { + const DetailsRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const DetailsScreen(); +} +``` + +Navigate using the `goRelative` or `pushRelative` methods provided by the code generator: + + +```dart +void onTapRelative() => const DetailsRoute().goRelative(context); +``` + +Relative routing methods are not idempotent and will cause an error when the relative location does not match a route. + ## Run tests To run unit tests, run command `dart tool/run_tests.dart` from `packages/go_router_builder/`. diff --git a/packages/go_router_builder/example/lib/go_relative.dart b/packages/go_router_builder/example/lib/go_relative.dart new file mode 100644 index 00000000000..06a0a758539 --- /dev/null +++ b/packages/go_router_builder/example/lib/go_relative.dart @@ -0,0 +1,198 @@ +// 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, unreachable_from_main + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +part 'go_relative.g.dart'; + +void main() => runApp(const MyApp()); + +/// The main app. +class MyApp extends StatelessWidget { + /// Constructs a [MyApp] + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + routerConfig: _router, + ); + } +} + +/// The route configuration. +final GoRouter _router = GoRouter( + routes: $appRoutes, +); +const TypedRelativeGoRoute detailRoute = + TypedRelativeGoRoute( + path: 'details/:detailId', + routes: >[ + TypedRelativeGoRoute(path: 'settings/:settingId'), + ], +); + +@TypedGoRoute( + path: '/', + routes: >[ + TypedGoRoute( + path: '/dashboard', + routes: >[detailRoute], + ), + detailRoute, + ], +) +class HomeRoute extends GoRouteData with _$HomeRoute { + @override + Widget build(BuildContext context, GoRouterState state) { + return const HomeScreen(); + } +} + +class DashboardRoute extends GoRouteData with _$DashboardRoute { + @override + Widget build(BuildContext context, GoRouterState state) { + return const DashboardScreen(); + } +} + +class DetailsRoute extends RelativeGoRouteData with _$DetailsRoute { + const DetailsRoute({required this.detailId}); + final String detailId; + + @override + Widget build(BuildContext context, GoRouterState state) { + return DetailsScreen(id: detailId); + } +} + +class SettingsRoute extends RelativeGoRouteData with _$SettingsRoute { + const SettingsRoute({ + required this.settingId, + }); + final String settingId; + + @override + Widget build(BuildContext context, GoRouterState state) { + return SettingsScreen(id: settingId); + } +} + +/// The home screen +class HomeScreen extends StatelessWidget { + /// Constructs a [HomeScreen] + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Home Screen')), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + const DetailsRoute(detailId: 'DetailsId').goRelative(context); + }, + child: const Text('Go to the Details screen'), + ), + ElevatedButton( + onPressed: () { + DashboardRoute().go(context); + }, + child: const Text('Go to the Dashboard screen'), + ), + ], + ), + ); + } +} + +/// The home screen +class DashboardScreen extends StatelessWidget { + /// Constructs a [DashboardScreen] + const DashboardScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Dashboard Screen')), + body: Column( + children: [ + ElevatedButton( + onPressed: () { + const DetailsRoute(detailId: 'DetailsId').goRelative(context); + }, + child: const Text('Go to the Details screen'), + ), + ElevatedButton( + onPressed: () => context.pop(), + child: const Text('Go back'), + ), + ], + ), + ); + } +} + +/// The details screen +class DetailsScreen extends StatelessWidget { + /// Constructs a [DetailsScreen] + const DetailsScreen({ + super.key, + required this.id, + }); + + final String id; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Details Screen $id')), + body: Center( + child: Column( + children: [ + ElevatedButton( + onPressed: () => context.pop(), + child: const Text('Go back'), + ), + ElevatedButton( + onPressed: () => const SettingsRoute( + settingId: 'SettingsId', + ).goRelative(context), + child: const Text('Go to the Settings screen'), + ), + ], + ), + ), + ); + } +} + +/// The details screen +class SettingsScreen extends StatelessWidget { + /// Constructs a [SettingsScreen] + const SettingsScreen({ + super.key, + required this.id, + }); + + final String id; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Settings Screen $id')), + body: Center( + child: TextButton( + onPressed: () => context.pop(), + child: const Text('Go back'), + ), + ), + ); + } +} diff --git a/packages/go_router_builder/example/lib/go_relative.g.dart b/packages/go_router_builder/example/lib/go_relative.g.dart new file mode 100644 index 00000000000..eb02878aac2 --- /dev/null +++ b/packages/go_router_builder/example/lib/go_relative.g.dart @@ -0,0 +1,152 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: always_specify_types, public_member_api_docs + +part of 'go_relative.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $homeRoute, + ]; + +RouteBase get $homeRoute => GoRouteData.$route( + path: '/', + factory: _$HomeRoute._fromState, + routes: [ + GoRouteData.$route( + path: '/dashboard', + factory: _$DashboardRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'details/:detailId', + factory: _$DetailsRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'settings/:settingId', + factory: _$SettingsRoute._fromState, + ), + ], + ), + ], + ), + RelativeGoRouteData.$route( + path: 'details/:detailId', + factory: _$DetailsRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'settings/:settingId', + factory: _$SettingsRoute._fromState, + ), + ], + ), + ], + ); + +mixin _$HomeRoute on GoRouteData { + static HomeRoute _fromState(GoRouterState state) => HomeRoute(); + + @override + String get location => GoRouteData.$location( + '/', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin _$DashboardRoute on GoRouteData { + static DashboardRoute _fromState(GoRouterState state) => DashboardRoute(); + + @override + String get location => GoRouteData.$location( + '/dashboard', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin _$DetailsRoute on RelativeGoRouteData { + static DetailsRoute _fromState(GoRouterState state) => DetailsRoute( + detailId: state.pathParameters['detailId']!, + ); + + DetailsRoute get _self => this as DetailsRoute; + + @override + String get location => RelativeGoRouteData.$location( + 'details/${Uri.encodeComponent(_self.detailId)}', + ); + + @override + String get relativeLocation => './$location'; + + @override + void goRelative(BuildContext context) => context.go(relativeLocation); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation); +} + +mixin _$SettingsRoute on RelativeGoRouteData { + static SettingsRoute _fromState(GoRouterState state) => SettingsRoute( + settingId: state.pathParameters['settingId']!, + ); + + SettingsRoute get _self => this as SettingsRoute; + + @override + String get location => RelativeGoRouteData.$location( + 'settings/${Uri.encodeComponent(_self.settingId)}', + ); + + @override + String get relativeLocation => './$location'; + + @override + void goRelative(BuildContext context) => context.go(relativeLocation); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation); +} diff --git a/packages/go_router_builder/example/lib/readme_excerpts.dart b/packages/go_router_builder/example/lib/readme_excerpts.dart index e4a457497cd..0152aaf2eab 100644 --- a/packages/go_router_builder/example/lib/readme_excerpts.dart +++ b/packages/go_router_builder/example/lib/readme_excerpts.dart @@ -57,6 +57,10 @@ void otherDoc(BuildContext context) { } // #enddocregion tapWithExtra + // #docregion goRelative + void onTapRelative() => const DetailsRoute().goRelative(context); + // #enddocregion goRelative + final LoginInfo loginInfo = LoginInfo(); final GoRouter routerWithRedirect = GoRouter( @@ -396,3 +400,25 @@ class MyGoRouteData extends GoRouteData with _$MyGoRouteData { } // #enddocregion MyShellRouteData + +// #docregion relativeRoute +@TypedRelativeGoRoute( + path: 'details', +) +class DetailsRoute extends RelativeGoRouteData with _$DetailsRoute { + const DetailsRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const DetailsScreen(); +} +// #enddocregion relativeRoute + +class DetailsScreen extends StatelessWidget { + const DetailsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/packages/go_router_builder/example/lib/readme_excerpts.g.dart b/packages/go_router_builder/example/lib/readme_excerpts.g.dart index 1a643f748f8..1af692996c8 100644 --- a/packages/go_router_builder/example/lib/readme_excerpts.g.dart +++ b/packages/go_router_builder/example/lib/readme_excerpts.g.dart @@ -18,6 +18,7 @@ List get $appRoutes => [ $myMaterialRouteWithKey, $fancyRoute, $myShellRouteData, + $detailsRoute, ]; RouteBase get $homeRoute => GoRouteData.$route( @@ -356,3 +357,33 @@ mixin _$MyGoRouteData on GoRouteData { @override void replace(BuildContext context) => context.replace(location); } + +RouteBase get $detailsRoute => RelativeGoRouteData.$route( + path: 'details', + factory: _$DetailsRoute._fromState, +); + +mixin _$DetailsRoute on RelativeGoRouteData { + static DetailsRoute _fromState(GoRouterState state) => const DetailsRoute(); + + @override + String get location => RelativeGoRouteData.$location('details'); + + @override + String get relativeLocation => './$location'; + + @override + void goRelative(BuildContext context) => context.go(relativeLocation); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation); +} diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index 5543b0695cf..776b24d3482 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: collection: ^1.15.0 flutter: sdk: flutter - go_router: ^16.0.0 + go_router: ^16.1.1 provider: 6.0.5 dev_dependencies: diff --git a/packages/go_router_builder/example/test/go_relative_test.dart b/packages/go_router_builder/example/test/go_relative_test.dart new file mode 100644 index 00000000000..3efd323e8c2 --- /dev/null +++ b/packages/go_router_builder/example/test/go_relative_test.dart @@ -0,0 +1,55 @@ +// 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. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router_builder_example/go_relative.dart' as example; + +void main() { + testWidgets('example works', (WidgetTester tester) async { + await tester.pumpWidget(const example.MyApp()); + expect(find.byType(example.HomeScreen), findsOneWidget); + + // From Home screen, go to Details screen + await tester.tap(find.text('Go to the Details screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.DetailsScreen), findsOneWidget); + + await tester.tap(find.text('Go to the Settings screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.SettingsScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.DetailsScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.HomeScreen), findsOneWidget); + + await tester.tap(find.text('Go to the Dashboard screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.DashboardScreen), findsOneWidget); + + // From Dashboard screen, go to Details screen + await tester.tap(find.text('Go to the Details screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.DetailsScreen), findsOneWidget); + + await tester.tap(find.text('Go to the Settings screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.SettingsScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.DetailsScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.DashboardScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.HomeScreen), findsOneWidget); + }); +} diff --git a/packages/go_router_builder/lib/src/go_router_generator.dart b/packages/go_router_builder/lib/src/go_router_generator.dart index c18631bdc70..3e325a5647a 100644 --- a/packages/go_router_builder/lib/src/go_router_generator.dart +++ b/packages/go_router_builder/lib/src/go_router_generator.dart @@ -16,6 +16,7 @@ const String _routeDataUrl = 'package:go_router/src/route_data.dart'; const Map _annotations = { 'TypedGoRoute': 'GoRouteData', + 'TypedRelativeGoRoute': 'RelativeGoRouteData', 'TypedShellRoute': 'ShellRouteData', 'TypedStatefulShellBranch': 'StatefulShellBranchData', 'TypedStatefulShellRoute': 'StatefulShellRouteData', diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 7405028bcd9..1ad703a997b 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -186,46 +186,12 @@ class StatefulShellBranchConfig extends RouteBaseConfig { String get dataConvertionFunctionName => r'$branch'; } -/// The configuration to generate class declarations for a GoRouteData. -class GoRouteConfig extends RouteBaseConfig { - GoRouteConfig._({ - required this.path, - required this.name, - required this.caseSensitive, - required this.parentNavigatorKey, - required super.routeDataClass, - required super.parent, - }) : super._(); +/// A mixin that provides common functionality for GoRoute-based configurations. +mixin _GoRouteMixin on RouteBaseConfig { + String get _basePathForLocation; - /// The path of the GoRoute to be created by this configuration. - final String path; - - /// The name of the GoRoute to be created by this configuration. - final String? name; - - /// The case sensitivity of the GoRoute to be created by this configuration. - final bool caseSensitive; - - /// The parent navigator key. - final String? parentNavigatorKey; - - late final Set _pathParams = pathParametersFromPattern( - _rawJoinedPath, - ); - - String get _rawJoinedPath { - final List pathSegments = []; - - RouteBaseConfig? config = this; - while (config != null) { - if (config is GoRouteConfig) { - pathSegments.add(config.path); - } - config = config.parent; - } - - return p.url.joinAll(pathSegments.reversed); - } + late final Set _pathParams = + pathParametersFromPattern(_basePathForLocation); // construct path bits using parent bits // if there are any queryParam objects, add in the `queryParam` bits @@ -252,10 +218,13 @@ class GoRouteConfig extends RouteBaseConfig { return MapEntry(pathParameter, valueBuffer.toString()); }), ); - final String location = patternToPath(_rawJoinedPath, pathParameters); + final String location = patternToPath(_basePathForLocation, pathParameters); return "'$location'"; } + /// The definition of the mixin to be generated. + String get _mixinDefinition; + FormalParameterElement? get _extraParam => _ctor.formalParameters.singleWhereOrNull( (FormalParameterElement element) => element.isExtraField, @@ -403,6 +372,85 @@ class GoRouteConfig extends RouteBaseConfig { ..._enumDeclarations(), ]; + /// Returns code representing the constant maps that contain the `enum` to + /// [String] mapping for each referenced enum. + Iterable _enumDeclarations() { + final Set enumParamTypes = {}; + + for (final FormalParameterElement ctorParam in [ + ..._ctorParams, + ..._ctorQueryParams, + ]) { + DartType potentialEnumType = ctorParam.type; + if (potentialEnumType is ParameterizedType && + (ctorParam.type as ParameterizedType).typeArguments.isNotEmpty) { + potentialEnumType = + (ctorParam.type as ParameterizedType).typeArguments.first; + } + + if (potentialEnumType.isEnum) { + enumParamTypes.add(potentialEnumType as InterfaceType); + } + + // Support for enum extension types + final DartType representedType = potentialEnumType.extensionTypeErasure; + if (potentialEnumType != representedType && representedType.isEnum) { + enumParamTypes.add(representedType as InterfaceType); + } + } + return enumParamTypes.map(_enumMapConst); + } + + @override + String get factorConstructorParameters => 'factory: $_mixinName._fromState,'; + + @override + String get dataConvertionFunctionName => r'$route'; +} + +/// The configuration to generate class declarations for a GoRouteData. +class GoRouteConfig extends RouteBaseConfig with _GoRouteMixin { + GoRouteConfig._({ + required this.path, + required this.name, + required this.caseSensitive, + required this.parentNavigatorKey, + required super.routeDataClass, + required super.parent, + }) : super._(); + + /// The path of the GoRoute to be created by this configuration. + final String path; + + /// The name of the GoRoute to be created by this configuration. + final String? name; + + /// The case sensitivity of the GoRoute to be created by this configuration. + final bool caseSensitive; + + /// The parent navigator key. + final String? parentNavigatorKey; + + String get _rawJoinedPath { + final List pathSegments = []; + + RouteBaseConfig? config = this; + while (config != null) { + if (config + case GoRouteConfig(:final String path) || + RelativeGoRouteConfig(:final String path)) { + pathSegments.add(path); + } + config = config.parent; + } + + return p.url.joinAll(pathSegments.reversed); + } + + @override + String get _basePathForLocation => _rawJoinedPath; + + @override String get _mixinDefinition { final bool hasMixin = getNodeDeclaration(routeDataClass) @@ -444,50 +492,92 @@ mixin $_mixinName on GoRouteData { '''; } - /// Returns code representing the constant maps that contain the `enum` to - /// [String] mapping for each referenced enum. - Iterable _enumDeclarations() { - final Set enumParamTypes = {}; + @override + String get routeConstructorParameters => + 'path: ${escapeDartString(path)},' + '${name != null ? 'name: ${escapeDartString(name!)},' : ''}' + '${caseSensitive ? '' : 'caseSensitive: $caseSensitive,'}' + '${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}'; - for (final FormalParameterElement ctorParam in [ - ..._ctorParams, - ..._ctorQueryParams, - ]) { - DartType potentialEnumType = ctorParam.type; - if (potentialEnumType is ParameterizedType && - (ctorParam.type as ParameterizedType).typeArguments.isNotEmpty) { - potentialEnumType = - (ctorParam.type as ParameterizedType).typeArguments.first; - } + @override + String get routeDataClassName => 'GoRouteData'; +} - if (potentialEnumType.isEnum) { - enumParamTypes.add(potentialEnumType as InterfaceType); - } +/// The configuration to generate class declarations for a RelativeGoRouteData. +class RelativeGoRouteConfig extends RouteBaseConfig with _GoRouteMixin { + RelativeGoRouteConfig._({ + required this.path, + required this.caseSensitive, + required this.parentNavigatorKey, + required super.routeDataClass, + required super.parent, + }) : super._(); - // Support for enum extension types - final DartType representedType = potentialEnumType.extensionTypeErasure; - if (potentialEnumType != representedType && representedType.isEnum) { - enumParamTypes.add(representedType as InterfaceType); - } + /// The path of the GoRoute to be created by this configuration. + final String path; + + /// The case sensitivity of the GoRoute to be created by this configuration. + final bool caseSensitive; + + /// The parent navigator key. + final String? parentNavigatorKey; + + @override + String get _basePathForLocation => path; + + @override + String get _mixinDefinition { + final bool hasMixin = getNodeDeclaration(routeDataClass) + ?.withClause + ?.mixinTypes + .any((NamedType e) => e.name2.toString() == _mixinName) ?? + false; + + if (!hasMixin) { + throw InvalidGenerationSourceError( + 'Missing mixin clause `with $_mixinName`', + element: routeDataClass, + ); } - return enumParamTypes.map(_enumMapConst); - } + return ''' +mixin $_mixinName on RelativeGoRouteData { + static $_className _fromState(GoRouterState state) $_fromStateConstructor + $_castedSelf @override - String get factorConstructorParameters => 'factory: $_mixinName._fromState,'; + String get location => RelativeGoRouteData.\$location($_locationArgs,$_locationQueryParams); + + @override + String get relativeLocation => './\$location'; @override - String get routeConstructorParameters => - 'path: ${escapeDartString(path)},' - '${name != null ? 'name: ${escapeDartString(name!)},' : ''}' - '${caseSensitive ? '' : 'caseSensitive: $caseSensitive,'}' - '${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}'; + void goRelative(BuildContext context) => + context.go(relativeLocation${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); @override - String get routeDataClassName => 'GoRouteData'; + Future pushRelative(BuildContext context) => + context.push(relativeLocation${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); +} +'''; + } @override - String get dataConvertionFunctionName => r'$route'; + String get routeConstructorParameters => ''' + path: ${escapeDartString(path)}, + ${caseSensitive ? '' : 'caseSensitive: $caseSensitive,'} + ${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'} +'''; + + @override + String get routeDataClassName => 'RelativeGoRouteData'; } /// Represents a `TypedGoRoute` annotation to the builder. @@ -519,11 +609,23 @@ abstract class RouteBaseConfig { factory RouteBaseConfig._fromAnnotation( ConstantReader reader, InterfaceElement2 element, - RouteBaseConfig? parent, - ) { + RouteBaseConfig? parent, { + bool isAncestorRelative = false, + }) { assert(!reader.isNull, 'reader should not be null'); final InterfaceType type = reader.objectValue.type! as InterfaceType; final String typeName = type.element.name; + + if (isAncestorRelative && typeName == 'TypedGoRoute') { + throw InvalidGenerationSourceError( + 'TypedRelativeGoRoute cannot have a TypedGoRoute descendant.', + element: element, + ); + } + + final bool isRelative = + isAncestorRelative || typeName == 'TypedRelativeGoRoute'; + final DartType typeParamType = type.typeArguments.single; if (typeParamType is! InterfaceType) { throw InvalidGenerationSourceError( @@ -622,22 +724,42 @@ abstract class RouteBaseConfig { parameterName: r'$parentNavigatorKey', ), ); + case 'TypedRelativeGoRoute': + final ConstantReader pathValue = reader.read('path'); + if (pathValue.isNull) { + throw InvalidGenerationSourceError( + 'Missing `path` value on annotation.', + element: element, + ); + } + final String pathString = pathValue.stringValue; + if (pathString.startsWith('/')) { + throw InvalidGenerationSourceError( + 'The path for a TypedRelativeGoRoute cannot start with "/".', + element: element, + ); + } + final ConstantReader caseSensitiveValue = reader.read('caseSensitive'); + value = RelativeGoRouteConfig._( + path: pathValue.stringValue, + caseSensitive: caseSensitiveValue.boolValue, + routeDataClass: classElement, + parent: parent, + parentNavigatorKey: _generateParameterGetterCode( + classElement, + parameterName: r'$parentNavigatorKey', + ), + ); default: throw UnsupportedError('Unrecognized type $typeName'); } - value._children.addAll( - reader - .read(_generateChildrenGetterName(typeName)) - .listValue - .map( - (DartObject e) => RouteBaseConfig._fromAnnotation( - ConstantReader(e), - element, - value, - ), - ), - ); + value._children.addAll(reader + .read(_generateChildrenGetterName(typeName)) + .listValue + .map((DartObject e) => RouteBaseConfig._fromAnnotation( + ConstantReader(e), element, value, + isAncestorRelative: isRelative),),); return value; } diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 7825f7f28b4..e1cf1be83b4 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -26,7 +26,7 @@ dev_dependencies: dart_style: '>=2.3.7 <4.0.0' flutter: sdk: flutter - go_router: ^16.0.0 + go_router: ^16.1.1 leak_tracker_flutter_testing: ">=3.0.0" package_config: ^2.1.1 pub_semver: ^2.1.5 diff --git a/packages/go_router_builder/test_inputs/go_relative.dart b/packages/go_router_builder/test_inputs/go_relative.dart new file mode 100644 index 00000000000..0ef85ed4b9a --- /dev/null +++ b/packages/go_router_builder/test_inputs/go_relative.dart @@ -0,0 +1,42 @@ +// 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. + +import 'package:go_router/go_router.dart'; + +mixin _$Route1 {} +mixin _$Route2 {} +mixin _$RelativeRoute {} +mixin _$InnerRelativeRoute {} + +const TypedRelativeGoRoute relativeRoute = + TypedRelativeGoRoute( + path: 'relative-route', + routes: >[ + TypedRelativeGoRoute(path: 'inner-relative-route') + ], +); + +@TypedGoRoute( + path: 'route-1', + routes: >[relativeRoute], +) +class Route1 extends GoRouteData with _$Route1 { + const Route1(); +} + +@TypedGoRoute( + path: 'route-2', + routes: >[relativeRoute], +) +class Route2 extends GoRouteData with _$Route2 { + const Route2(); +} + +class RelativeRoute extends RelativeGoRouteData with _$RelativeRoute { + const RelativeRoute(); +} + +class InnerRelativeRoute extends RelativeGoRouteData with _$InnerRelativeRoute { + const InnerRelativeRoute(); +} diff --git a/packages/go_router_builder/test_inputs/go_relative.dart.expect b/packages/go_router_builder/test_inputs/go_relative.dart.expect new file mode 100644 index 00000000000..45697254696 --- /dev/null +++ b/packages/go_router_builder/test_inputs/go_relative.dart.expect @@ -0,0 +1,132 @@ +RouteBase get $route1 => GoRouteData.$route( + path: 'route-1', + factory: _$Route1._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'relative-route', + factory: _$RelativeRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'inner-relative-route', + factory: _$InnerRelativeRoute._fromState, + ), + ], + ), + ], + ); + +mixin _$Route1 on GoRouteData { + static Route1 _fromState(GoRouterState state) => const Route1(); + + @override + String get location => GoRouteData.$location( + 'route-1', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin _$RelativeRoute on RelativeGoRouteData { + static RelativeRoute _fromState(GoRouterState state) => const RelativeRoute(); + + @override + String get location => RelativeGoRouteData.$location( + 'relative-route', + ); + + @override + String get relativeLocation => './$location'; + + @override + void goRelative(BuildContext context) => context.go(relativeLocation); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation); +} + +mixin _$InnerRelativeRoute on RelativeGoRouteData { + static InnerRelativeRoute _fromState(GoRouterState state) => + const InnerRelativeRoute(); + + @override + String get location => RelativeGoRouteData.$location( + 'inner-relative-route', + ); + + @override + String get relativeLocation => './$location'; + + @override + void goRelative(BuildContext context) => context.go(relativeLocation); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation); +} + +RouteBase get $route2 => GoRouteData.$route( + path: 'route-2', + factory: _$Route2._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'relative-route', + factory: _$RelativeRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'inner-relative-route', + factory: _$InnerRelativeRoute._fromState, + ), + ], + ), + ], + ); + +mixin _$Route2 on GoRouteData { + static Route2 _fromState(GoRouterState state) => const Route2(); + + @override + String get location => GoRouteData.$location( + 'route-2', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} \ No newline at end of file diff --git a/packages/go_router_builder/test_inputs/no_mixin_relative.dart b/packages/go_router_builder/test_inputs/no_mixin_relative.dart new file mode 100644 index 00000000000..64d17d17e4f --- /dev/null +++ b/packages/go_router_builder/test_inputs/no_mixin_relative.dart @@ -0,0 +1,8 @@ +// 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. + +import 'package:go_router/go_router.dart'; + +@TypedRelativeGoRoute(path: 'path') +class RelativeRoute extends RelativeGoRouteData {} diff --git a/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect b/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect new file mode 100644 index 00000000000..9940d3bffa9 --- /dev/null +++ b/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect @@ -0,0 +1 @@ +Missing mixin clause `with _$RelativeRoute` \ No newline at end of file diff --git a/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart b/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart new file mode 100644 index 00000000000..66c9c7f51ab --- /dev/null +++ b/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart @@ -0,0 +1,14 @@ +// 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. + +import 'package:go_router/go_router.dart'; + +mixin _$RelativeRoute {} + +@TypedRelativeGoRoute( + path: '/relative-route', +) +class RelativeRoute extends RelativeGoRouteData with _$RelativeRoute { + const RelativeRoute(); +} diff --git a/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart.expect b/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart.expect new file mode 100644 index 00000000000..35ce4f65ff0 --- /dev/null +++ b/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart.expect @@ -0,0 +1 @@ +The path for a TypedRelativeGoRoute cannot start with "/". \ No newline at end of file diff --git a/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart b/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart new file mode 100644 index 00000000000..6642b4d44db --- /dev/null +++ b/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart @@ -0,0 +1,33 @@ +// 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. + +import 'package:go_router/go_router.dart'; + +mixin _$HomeRoute {} +mixin _$RelativeRoute {} +mixin _$NonRelativeRoute {} + +@TypedGoRoute( + path: '/', + routes: >[relativeRoute], +) +class HomeRoute extends GoRouteData with _$HomeRoute { + const HomeRoute(); +} + +const TypedRelativeGoRoute relativeRoute = + TypedRelativeGoRoute( + path: 'relative-route', + routes: >[ + TypedGoRoute(path: 'non-relative-route'), + ], +); + +class RelativeRoute extends RelativeGoRouteData with _$RelativeRoute { + const RelativeRoute(); +} + +class NonRelativeRoute extends GoRouteData with _$NonRelativeRoute { + const NonRelativeRoute(); +} diff --git a/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart.expect b/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart.expect new file mode 100644 index 00000000000..e221154cdb7 --- /dev/null +++ b/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart.expect @@ -0,0 +1 @@ +TypedRelativeGoRoute cannot have a TypedGoRoute descendant. \ No newline at end of file diff --git a/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart b/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart new file mode 100644 index 00000000000..36dcb04a14c --- /dev/null +++ b/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart @@ -0,0 +1,42 @@ +// 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. + +import 'package:go_router/go_router.dart'; + +mixin _$HomeRoute {} +mixin _$ShellRoute {} +mixin _$RelativeRoute {} +mixin _$AbsoluteRoute {} + +@TypedGoRoute( + path: '/', + routes: >[relativeRoute], +) +class HomeRoute extends GoRouteData with _$HomeRoute { + const HomeRoute(); +} + +const TypedRelativeGoRoute relativeRoute = + TypedRelativeGoRoute( + path: 'relative-route', + routes: >[shellRoute], +); + +const TypedShellRoute shellRoute = + TypedShellRoute(routes: >[absoluteRoute]); + +const TypedGoRoute absoluteRoute = + TypedGoRoute(path: 'absolute-route'); + +class RelativeRoute extends RelativeGoRouteData with _$RelativeRoute { + const RelativeRoute(); +} + +class ShellRoute extends ShellRouteData with _$ShellRoute { + const ShellRoute(); +} + +class AbsoluteRoute extends GoRouteData with _$AbsoluteRoute { + const AbsoluteRoute(); +} diff --git a/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart.expect b/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart.expect new file mode 100644 index 00000000000..e221154cdb7 --- /dev/null +++ b/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart.expect @@ -0,0 +1 @@ +TypedRelativeGoRoute cannot have a TypedGoRoute descendant. \ No newline at end of file From 0929426c837d03062576c11ac7888943ff9cbaf3 Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Wed, 13 Aug 2025 21:54:25 +0200 Subject: [PATCH 2/6] Update based on feedback --- packages/go_router_builder/README.md | 3 +- .../example/lib/go_relative.g.dart | 8 ++--- .../example/lib/readme_excerpts.g.dart | 4 +-- .../lib/src/route_config.dart | 35 ++++++++++++------- .../test_inputs/go_relative.dart | 10 +++--- .../test_inputs/go_relative.dart.expect | 8 ++--- .../relative_route_with_absolute_path.dart | 4 +-- ..._route_with_direct_absolute_sub_route.dart | 10 +++--- ...oute_with_indirect_absolute_sub_route.dart | 16 +++++---- 9 files changed, 54 insertions(+), 44 deletions(-) diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index 206c406fd97..d9f05863561 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -457,7 +457,8 @@ An example is available [here](https://github.com/flutter/packages/blob/main/pac ## Relative routes -Define a relative route by extending RelativeGoRouteData. +Relative routes allow reusing the same `RouteData` in different parts of the route tree. +Define a relative route by extending `RelativeGoRouteData`. ```dart diff --git a/packages/go_router_builder/example/lib/go_relative.g.dart b/packages/go_router_builder/example/lib/go_relative.g.dart index eb02878aac2..b233d15b374 100644 --- a/packages/go_router_builder/example/lib/go_relative.g.dart +++ b/packages/go_router_builder/example/lib/go_relative.g.dart @@ -97,12 +97,12 @@ mixin _$DetailsRoute on RelativeGoRouteData { DetailsRoute get _self => this as DetailsRoute; @override - String get location => RelativeGoRouteData.$location( + String get subpath => RelativeGoRouteData.$location( 'details/${Uri.encodeComponent(_self.detailId)}', ); @override - String get relativeLocation => './$location'; + String get relativeLocation => './$subpath'; @override void goRelative(BuildContext context) => context.go(relativeLocation); @@ -128,12 +128,12 @@ mixin _$SettingsRoute on RelativeGoRouteData { SettingsRoute get _self => this as SettingsRoute; @override - String get location => RelativeGoRouteData.$location( + String get subpath => RelativeGoRouteData.$location( 'settings/${Uri.encodeComponent(_self.settingId)}', ); @override - String get relativeLocation => './$location'; + String get relativeLocation => './$subpath'; @override void goRelative(BuildContext context) => context.go(relativeLocation); diff --git a/packages/go_router_builder/example/lib/readme_excerpts.g.dart b/packages/go_router_builder/example/lib/readme_excerpts.g.dart index 1af692996c8..fa6a6357522 100644 --- a/packages/go_router_builder/example/lib/readme_excerpts.g.dart +++ b/packages/go_router_builder/example/lib/readme_excerpts.g.dart @@ -367,10 +367,10 @@ mixin _$DetailsRoute on RelativeGoRouteData { static DetailsRoute _fromState(GoRouterState state) => const DetailsRoute(); @override - String get location => RelativeGoRouteData.$location('details'); + String get subpath => RelativeGoRouteData.$location('details'); @override - String get relativeLocation => './$location'; + String get relativeLocation => './$subpath'; @override void goRelative(BuildContext context) => context.go(relativeLocation); diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 1ad703a997b..9f3c32a9376 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -190,8 +190,9 @@ class StatefulShellBranchConfig extends RouteBaseConfig { mixin _GoRouteMixin on RouteBaseConfig { String get _basePathForLocation; - late final Set _pathParams = - pathParametersFromPattern(_basePathForLocation); + late final Set _pathParams = pathParametersFromPattern( + _basePathForLocation, + ); // construct path bits using parent bits // if there are any queryParam objects, add in the `queryParam` bits @@ -467,7 +468,7 @@ class GoRouteConfig extends RouteBaseConfig with _GoRouteMixin { } return ''' -mixin $_mixinName on GoRouteData { +mixin $_mixinName on $routeDataClassName { static $_className _fromState(GoRouterState state) $_fromStateConstructor $_castedSelf @override @@ -527,7 +528,8 @@ class RelativeGoRouteConfig extends RouteBaseConfig with _GoRouteMixin { @override String get _mixinDefinition { - final bool hasMixin = getNodeDeclaration(routeDataClass) + final bool hasMixin = + getNodeDeclaration(routeDataClass) ?.withClause ?.mixinTypes .any((NamedType e) => e.name2.toString() == _mixinName) ?? @@ -541,14 +543,14 @@ class RelativeGoRouteConfig extends RouteBaseConfig with _GoRouteMixin { } return ''' -mixin $_mixinName on RelativeGoRouteData { +mixin $_mixinName on $routeDataClassName { static $_className _fromState(GoRouterState state) $_fromStateConstructor $_castedSelf @override - String get location => RelativeGoRouteData.\$location($_locationArgs,$_locationQueryParams); + String get subpath => RelativeGoRouteData.\$location($_locationArgs,$_locationQueryParams); @override - String get relativeLocation => './\$location'; + String get relativeLocation => './\$subpath'; @override void goRelative(BuildContext context) => @@ -754,12 +756,19 @@ abstract class RouteBaseConfig { throw UnsupportedError('Unrecognized type $typeName'); } - value._children.addAll(reader - .read(_generateChildrenGetterName(typeName)) - .listValue - .map((DartObject e) => RouteBaseConfig._fromAnnotation( - ConstantReader(e), element, value, - isAncestorRelative: isRelative),),); + value._children.addAll( + reader + .read(_generateChildrenGetterName(typeName)) + .listValue + .map( + (DartObject e) => RouteBaseConfig._fromAnnotation( + ConstantReader(e), + element, + value, + isAncestorRelative: isRelative, + ), + ), + ); return value; } diff --git a/packages/go_router_builder/test_inputs/go_relative.dart b/packages/go_router_builder/test_inputs/go_relative.dart index 0ef85ed4b9a..955ceaaded3 100644 --- a/packages/go_router_builder/test_inputs/go_relative.dart +++ b/packages/go_router_builder/test_inputs/go_relative.dart @@ -11,11 +11,11 @@ mixin _$InnerRelativeRoute {} const TypedRelativeGoRoute relativeRoute = TypedRelativeGoRoute( - path: 'relative-route', - routes: >[ - TypedRelativeGoRoute(path: 'inner-relative-route') - ], -); + path: 'relative-route', + routes: >[ + TypedRelativeGoRoute(path: 'inner-relative-route'), + ], + ); @TypedGoRoute( path: 'route-1', diff --git a/packages/go_router_builder/test_inputs/go_relative.dart.expect b/packages/go_router_builder/test_inputs/go_relative.dart.expect index 45697254696..62337dac18e 100644 --- a/packages/go_router_builder/test_inputs/go_relative.dart.expect +++ b/packages/go_router_builder/test_inputs/go_relative.dart.expect @@ -41,12 +41,12 @@ mixin _$RelativeRoute on RelativeGoRouteData { static RelativeRoute _fromState(GoRouterState state) => const RelativeRoute(); @override - String get location => RelativeGoRouteData.$location( + String get subpath => RelativeGoRouteData.$location( 'relative-route', ); @override - String get relativeLocation => './$location'; + String get relativeLocation => './$subpath'; @override void goRelative(BuildContext context) => context.go(relativeLocation); @@ -69,12 +69,12 @@ mixin _$InnerRelativeRoute on RelativeGoRouteData { const InnerRelativeRoute(); @override - String get location => RelativeGoRouteData.$location( + String get subpath => RelativeGoRouteData.$location( 'inner-relative-route', ); @override - String get relativeLocation => './$location'; + String get relativeLocation => './$subpath'; @override void goRelative(BuildContext context) => context.go(relativeLocation); diff --git a/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart b/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart index 66c9c7f51ab..a5ea6bce2cc 100644 --- a/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart +++ b/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart @@ -6,9 +6,7 @@ import 'package:go_router/go_router.dart'; mixin _$RelativeRoute {} -@TypedRelativeGoRoute( - path: '/relative-route', -) +@TypedRelativeGoRoute(path: '/relative-route') class RelativeRoute extends RelativeGoRouteData with _$RelativeRoute { const RelativeRoute(); } diff --git a/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart b/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart index 6642b4d44db..e89034809c4 100644 --- a/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart +++ b/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart @@ -18,11 +18,11 @@ class HomeRoute extends GoRouteData with _$HomeRoute { const TypedRelativeGoRoute relativeRoute = TypedRelativeGoRoute( - path: 'relative-route', - routes: >[ - TypedGoRoute(path: 'non-relative-route'), - ], -); + path: 'relative-route', + routes: >[ + TypedGoRoute(path: 'non-relative-route'), + ], + ); class RelativeRoute extends RelativeGoRouteData with _$RelativeRoute { const RelativeRoute(); diff --git a/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart b/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart index 36dcb04a14c..decb0675931 100644 --- a/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart +++ b/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart @@ -19,15 +19,17 @@ class HomeRoute extends GoRouteData with _$HomeRoute { const TypedRelativeGoRoute relativeRoute = TypedRelativeGoRoute( - path: 'relative-route', - routes: >[shellRoute], -); + path: 'relative-route', + routes: >[shellRoute], + ); -const TypedShellRoute shellRoute = - TypedShellRoute(routes: >[absoluteRoute]); +const TypedShellRoute shellRoute = TypedShellRoute( + routes: >[absoluteRoute], +); -const TypedGoRoute absoluteRoute = - TypedGoRoute(path: 'absolute-route'); +const TypedGoRoute absoluteRoute = TypedGoRoute( + path: 'absolute-route', +); class RelativeRoute extends RelativeGoRouteData with _$RelativeRoute { const RelativeRoute(); From 86db3dcc543beb9eae472fc4ca5521d8c3d23bc8 Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Wed, 13 Aug 2025 22:17:51 +0200 Subject: [PATCH 3/6] Add missing newlines --- packages/go_router_builder/test_inputs/go_relative.dart.expect | 2 +- .../go_router_builder/test_inputs/no_mixin_relative.dart.expect | 2 +- .../test_inputs/relative_route_with_absolute_path.dart.expect | 2 +- .../relative_route_with_direct_absolute_sub_route.dart.expect | 2 +- .../relative_route_with_indirect_absolute_sub_route.dart.expect | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/go_router_builder/test_inputs/go_relative.dart.expect b/packages/go_router_builder/test_inputs/go_relative.dart.expect index 62337dac18e..8c2f1b758bd 100644 --- a/packages/go_router_builder/test_inputs/go_relative.dart.expect +++ b/packages/go_router_builder/test_inputs/go_relative.dart.expect @@ -129,4 +129,4 @@ mixin _$Route2 on GoRouteData { @override void replace(BuildContext context) => context.replace(location); -} \ No newline at end of file +} diff --git a/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect b/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect index 9940d3bffa9..a9efb29f477 100644 --- a/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect +++ b/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect @@ -1 +1 @@ -Missing mixin clause `with _$RelativeRoute` \ No newline at end of file +Missing mixin clause `with _$RelativeRoute` diff --git a/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart.expect b/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart.expect index 35ce4f65ff0..86fedafd5f8 100644 --- a/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart.expect +++ b/packages/go_router_builder/test_inputs/relative_route_with_absolute_path.dart.expect @@ -1 +1 @@ -The path for a TypedRelativeGoRoute cannot start with "/". \ No newline at end of file +The path for a TypedRelativeGoRoute cannot start with "/". diff --git a/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart.expect b/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart.expect index e221154cdb7..5c43e6722f5 100644 --- a/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart.expect +++ b/packages/go_router_builder/test_inputs/relative_route_with_direct_absolute_sub_route.dart.expect @@ -1 +1 @@ -TypedRelativeGoRoute cannot have a TypedGoRoute descendant. \ No newline at end of file +TypedRelativeGoRoute cannot have a TypedGoRoute descendant. diff --git a/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart.expect b/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart.expect index e221154cdb7..5c43e6722f5 100644 --- a/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart.expect +++ b/packages/go_router_builder/test_inputs/relative_route_with_indirect_absolute_sub_route.dart.expect @@ -1 +1 @@ -TypedRelativeGoRoute cannot have a TypedGoRoute descendant. \ No newline at end of file +TypedRelativeGoRoute cannot have a TypedGoRoute descendant. From 477e71c9a0b54cbcdb099391538c1bf69768b6ca Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Wed, 13 Aug 2025 22:43:06 +0200 Subject: [PATCH 4/6] Rename subpath to subLocation --- packages/go_router_builder/example/lib/go_relative.g.dart | 8 ++++---- .../go_router_builder/example/lib/readme_excerpts.g.dart | 4 ++-- packages/go_router_builder/lib/src/route_config.dart | 4 ++-- .../go_router_builder/test_inputs/go_relative.dart.expect | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/go_router_builder/example/lib/go_relative.g.dart b/packages/go_router_builder/example/lib/go_relative.g.dart index b233d15b374..19aa51cb023 100644 --- a/packages/go_router_builder/example/lib/go_relative.g.dart +++ b/packages/go_router_builder/example/lib/go_relative.g.dart @@ -97,12 +97,12 @@ mixin _$DetailsRoute on RelativeGoRouteData { DetailsRoute get _self => this as DetailsRoute; @override - String get subpath => RelativeGoRouteData.$location( + String get subLocation => RelativeGoRouteData.$location( 'details/${Uri.encodeComponent(_self.detailId)}', ); @override - String get relativeLocation => './$subpath'; + String get relativeLocation => './$subLocation'; @override void goRelative(BuildContext context) => context.go(relativeLocation); @@ -128,12 +128,12 @@ mixin _$SettingsRoute on RelativeGoRouteData { SettingsRoute get _self => this as SettingsRoute; @override - String get subpath => RelativeGoRouteData.$location( + String get subLocation => RelativeGoRouteData.$location( 'settings/${Uri.encodeComponent(_self.settingId)}', ); @override - String get relativeLocation => './$subpath'; + String get relativeLocation => './$subLocation'; @override void goRelative(BuildContext context) => context.go(relativeLocation); diff --git a/packages/go_router_builder/example/lib/readme_excerpts.g.dart b/packages/go_router_builder/example/lib/readme_excerpts.g.dart index fa6a6357522..1c835d78a54 100644 --- a/packages/go_router_builder/example/lib/readme_excerpts.g.dart +++ b/packages/go_router_builder/example/lib/readme_excerpts.g.dart @@ -367,10 +367,10 @@ mixin _$DetailsRoute on RelativeGoRouteData { static DetailsRoute _fromState(GoRouterState state) => const DetailsRoute(); @override - String get subpath => RelativeGoRouteData.$location('details'); + String get subLocation => RelativeGoRouteData.$location('details'); @override - String get relativeLocation => './$subpath'; + String get relativeLocation => './$subLocation'; @override void goRelative(BuildContext context) => context.go(relativeLocation); diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 9f3c32a9376..2bad8d2c8a7 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -547,10 +547,10 @@ mixin $_mixinName on $routeDataClassName { static $_className _fromState(GoRouterState state) $_fromStateConstructor $_castedSelf @override - String get subpath => RelativeGoRouteData.\$location($_locationArgs,$_locationQueryParams); + String get subLocation => RelativeGoRouteData.\$location($_locationArgs,$_locationQueryParams); @override - String get relativeLocation => './\$subpath'; + String get relativeLocation => './\$subLocation'; @override void goRelative(BuildContext context) => diff --git a/packages/go_router_builder/test_inputs/go_relative.dart.expect b/packages/go_router_builder/test_inputs/go_relative.dart.expect index 8c2f1b758bd..90742469050 100644 --- a/packages/go_router_builder/test_inputs/go_relative.dart.expect +++ b/packages/go_router_builder/test_inputs/go_relative.dart.expect @@ -41,12 +41,12 @@ mixin _$RelativeRoute on RelativeGoRouteData { static RelativeRoute _fromState(GoRouterState state) => const RelativeRoute(); @override - String get subpath => RelativeGoRouteData.$location( + String get subLocation => RelativeGoRouteData.$location( 'relative-route', ); @override - String get relativeLocation => './$subpath'; + String get relativeLocation => './$subLocation'; @override void goRelative(BuildContext context) => context.go(relativeLocation); @@ -69,12 +69,12 @@ mixin _$InnerRelativeRoute on RelativeGoRouteData { const InnerRelativeRoute(); @override - String get subpath => RelativeGoRouteData.$location( + String get subLocation => RelativeGoRouteData.$location( 'inner-relative-route', ); @override - String get relativeLocation => './$subpath'; + String get relativeLocation => './$subLocation'; @override void goRelative(BuildContext context) => context.go(relativeLocation); From 609ec7492ffd712a7ebd04a3bd12eb0de392e27d Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Thu, 14 Aug 2025 01:38:34 +0200 Subject: [PATCH 5/6] Update changelog and version --- packages/go_router_builder/README.md | 2 +- .../example/lib/go_relative.dart | 39 ++++------ .../example/lib/go_relative.g.dart | 72 +++++++++---------- .../example/lib/readme_excerpts.dart | 4 +- .../example/lib/readme_excerpts.g.dart | 1 + packages/go_router_builder/pubspec.yaml | 2 +- 6 files changed, 52 insertions(+), 68 deletions(-) diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index d9f05863561..d41f1b1cfd1 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -13,7 +13,7 @@ dependencies: dev_dependencies: # ...along with your other dev-dependencies build_runner: ^2.6.0 - go_router_builder: ^3.1.0 + go_router_builder: ^3.3.0 ``` ### Source code diff --git a/packages/go_router_builder/example/lib/go_relative.dart b/packages/go_router_builder/example/lib/go_relative.dart index 06a0a758539..f9b39040fc4 100644 --- a/packages/go_router_builder/example/lib/go_relative.dart +++ b/packages/go_router_builder/example/lib/go_relative.dart @@ -18,23 +18,19 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp.router( - routerConfig: _router, - ); + return MaterialApp.router(routerConfig: _router); } } /// The route configuration. -final GoRouter _router = GoRouter( - routes: $appRoutes, -); +final GoRouter _router = GoRouter(routes: $appRoutes); const TypedRelativeGoRoute detailRoute = TypedRelativeGoRoute( - path: 'details/:detailId', - routes: >[ - TypedRelativeGoRoute(path: 'settings/:settingId'), - ], -); + path: 'details/:detailId', + routes: >[ + TypedRelativeGoRoute(path: 'settings/:settingId'), + ], + ); @TypedGoRoute( path: '/', @@ -71,9 +67,7 @@ class DetailsRoute extends RelativeGoRouteData with _$DetailsRoute { } class SettingsRoute extends RelativeGoRouteData with _$SettingsRoute { - const SettingsRoute({ - required this.settingId, - }); + const SettingsRoute({required this.settingId}); final String settingId; @override @@ -142,10 +136,7 @@ class DashboardScreen extends StatelessWidget { /// The details screen class DetailsScreen extends StatelessWidget { /// Constructs a [DetailsScreen] - const DetailsScreen({ - super.key, - required this.id, - }); + const DetailsScreen({super.key, required this.id}); final String id; @@ -161,9 +152,10 @@ class DetailsScreen extends StatelessWidget { child: const Text('Go back'), ), ElevatedButton( - onPressed: () => const SettingsRoute( - settingId: 'SettingsId', - ).goRelative(context), + onPressed: + () => const SettingsRoute( + settingId: 'SettingsId', + ).goRelative(context), child: const Text('Go to the Settings screen'), ), ], @@ -176,10 +168,7 @@ class DetailsScreen extends StatelessWidget { /// The details screen class SettingsScreen extends StatelessWidget { /// Constructs a [SettingsScreen] - const SettingsScreen({ - super.key, - required this.id, - }); + const SettingsScreen({super.key, required this.id}); final String id; diff --git a/packages/go_router_builder/example/lib/go_relative.g.dart b/packages/go_router_builder/example/lib/go_relative.g.dart index 19aa51cb023..1554bf21add 100644 --- a/packages/go_router_builder/example/lib/go_relative.g.dart +++ b/packages/go_router_builder/example/lib/go_relative.g.dart @@ -8,50 +8,50 @@ part of 'go_relative.dart'; // GoRouterGenerator // ************************************************************************** -List get $appRoutes => [ - $homeRoute, - ]; +List get $appRoutes => [$homeRoute]; RouteBase get $homeRoute => GoRouteData.$route( - path: '/', - factory: _$HomeRoute._fromState, + path: '/', + factory: _$HomeRoute._fromState, + routes: [ + GoRouteData.$route( + path: '/dashboard', + factory: _$DashboardRoute._fromState, routes: [ - GoRouteData.$route( - path: '/dashboard', - factory: _$DashboardRoute._fromState, - routes: [ - RelativeGoRouteData.$route( - path: 'details/:detailId', - factory: _$DetailsRoute._fromState, - routes: [ - RelativeGoRouteData.$route( - path: 'settings/:settingId', - factory: _$SettingsRoute._fromState, - ), - ], - ), - ], - ), RelativeGoRouteData.$route( path: 'details/:detailId', + factory: _$DetailsRoute._fromState, routes: [ RelativeGoRouteData.$route( path: 'settings/:settingId', + factory: _$SettingsRoute._fromState, ), ], ), ], - ); + ), + RelativeGoRouteData.$route( + path: 'details/:detailId', + + factory: _$DetailsRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'settings/:settingId', + + factory: _$SettingsRoute._fromState, + ), + ], + ), + ], +); mixin _$HomeRoute on GoRouteData { static HomeRoute _fromState(GoRouterState state) => HomeRoute(); @override - String get location => GoRouteData.$location( - '/', - ); + String get location => GoRouteData.$location('/'); @override void go(BuildContext context) => context.go(location); @@ -71,9 +71,7 @@ mixin _$DashboardRoute on GoRouteData { static DashboardRoute _fromState(GoRouterState state) => DashboardRoute(); @override - String get location => GoRouteData.$location( - '/dashboard', - ); + String get location => GoRouteData.$location('/dashboard'); @override void go(BuildContext context) => context.go(location); @@ -90,16 +88,15 @@ mixin _$DashboardRoute on GoRouteData { } mixin _$DetailsRoute on RelativeGoRouteData { - static DetailsRoute _fromState(GoRouterState state) => DetailsRoute( - detailId: state.pathParameters['detailId']!, - ); + static DetailsRoute _fromState(GoRouterState state) => + DetailsRoute(detailId: state.pathParameters['detailId']!); DetailsRoute get _self => this as DetailsRoute; @override String get subLocation => RelativeGoRouteData.$location( - 'details/${Uri.encodeComponent(_self.detailId)}', - ); + 'details/${Uri.encodeComponent(_self.detailId)}', + ); @override String get relativeLocation => './$subLocation'; @@ -121,16 +118,15 @@ mixin _$DetailsRoute on RelativeGoRouteData { } mixin _$SettingsRoute on RelativeGoRouteData { - static SettingsRoute _fromState(GoRouterState state) => SettingsRoute( - settingId: state.pathParameters['settingId']!, - ); + static SettingsRoute _fromState(GoRouterState state) => + SettingsRoute(settingId: state.pathParameters['settingId']!); SettingsRoute get _self => this as SettingsRoute; @override String get subLocation => RelativeGoRouteData.$location( - 'settings/${Uri.encodeComponent(_self.settingId)}', - ); + 'settings/${Uri.encodeComponent(_self.settingId)}', + ); @override String get relativeLocation => './$subLocation'; diff --git a/packages/go_router_builder/example/lib/readme_excerpts.dart b/packages/go_router_builder/example/lib/readme_excerpts.dart index 0152aaf2eab..0bc4660cf36 100644 --- a/packages/go_router_builder/example/lib/readme_excerpts.dart +++ b/packages/go_router_builder/example/lib/readme_excerpts.dart @@ -402,9 +402,7 @@ class MyGoRouteData extends GoRouteData with _$MyGoRouteData { // #enddocregion MyShellRouteData // #docregion relativeRoute -@TypedRelativeGoRoute( - path: 'details', -) +@TypedRelativeGoRoute(path: 'details') class DetailsRoute extends RelativeGoRouteData with _$DetailsRoute { const DetailsRoute(); diff --git a/packages/go_router_builder/example/lib/readme_excerpts.g.dart b/packages/go_router_builder/example/lib/readme_excerpts.g.dart index 1c835d78a54..878f8027153 100644 --- a/packages/go_router_builder/example/lib/readme_excerpts.g.dart +++ b/packages/go_router_builder/example/lib/readme_excerpts.g.dart @@ -360,6 +360,7 @@ mixin _$MyGoRouteData on GoRouteData { RouteBase get $detailsRoute => RelativeGoRouteData.$route( path: 'details', + factory: _$DetailsRoute._fromState, ); diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index e1cf1be83b4..2321cfd6ab3 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,7 +2,7 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router -version: 3.2.1 +version: 3.3.0 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 From 7a6ba60783c634347b21f85d8cb70d534a7405f4 Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Mon, 18 Aug 2025 22:31:45 +0200 Subject: [PATCH 6/6] Bump go_router minor version --- packages/go_router_builder/README.md | 6 ++---- packages/go_router_builder/example/pubspec.yaml | 2 +- packages/go_router_builder/pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index d41f1b1cfd1..9eea7ccf055 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -8,7 +8,7 @@ To use `go_router_builder`, you need to have the following dependencies in ```yaml dependencies: # ...along with your other dependencies - go_router: ^16.0.0 + go_router: ^16.2.0 dev_dependencies: # ...along with your other dev-dependencies @@ -462,9 +462,7 @@ Define a relative route by extending `RelativeGoRouteData`. ```dart -@TypedRelativeGoRoute( - path: 'details', -) +@TypedRelativeGoRoute(path: 'details') class DetailsRoute extends RelativeGoRouteData with _$DetailsRoute { const DetailsRoute(); diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index 776b24d3482..a582818bdbf 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: collection: ^1.15.0 flutter: sdk: flutter - go_router: ^16.1.1 + go_router: ^16.2.0 provider: 6.0.5 dev_dependencies: diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 2321cfd6ab3..801facafad4 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -26,7 +26,7 @@ dev_dependencies: dart_style: '>=2.3.7 <4.0.0' flutter: sdk: flutter - go_router: ^16.1.1 + go_router: ^16.2.0 leak_tracker_flutter_testing: ">=3.0.0" package_config: ^2.1.1 pub_semver: ^2.1.5