@@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
88import 'package:meta/meta.dart' ;
99import 'package:meta/meta_meta.dart' ;
1010
11+ import 'configuration.dart' ;
1112import 'route.dart' ;
1213import 'state.dart' ;
1314
@@ -18,17 +19,10 @@ abstract class RouteData {
1819 const RouteData ();
1920}
2021
21- /// A class to represent a [GoRoute] in
22- /// [Type-safe routing] (https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).
23- ///
24- /// Subclasses must override one of [build] , [buildPage] , or
25- /// [redirect] .
26- /// {@category Type-safe routes}
27- abstract class GoRouteData extends RouteData {
28- /// Allows subclasses to have `const` constructors.
29- ///
30- /// [GoRouteData] is abstract and cannot be instantiated directly.
31- const GoRouteData ();
22+ /// A base class for [GoRouteData] and [RelativeGoRouteData] that provides
23+ /// common functionality for type-safe routing.
24+ abstract class _GoRouteDataBase extends RouteData {
25+ const _GoRouteDataBase ();
3226
3327 /// Creates the [Widget] for `this` route.
3428 ///
@@ -68,17 +62,93 @@ abstract class GoRouteData extends RouteData {
6862 /// Corresponds to [GoRoute.onExit] .
6963 FutureOr <bool > onExit (BuildContext context, GoRouterState state) => true ;
7064
65+ /// The error thrown when a user-facing method is not implemented by the
66+ /// generated code.
67+ static UnimplementedError get shouldBeGeneratedError => UnimplementedError (
68+ 'Should be generated using [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).' ,
69+ );
70+
71+ /// Used to cache [_GoRouteDataBase] that corresponds to a given [GoRouterState]
72+ /// to minimize the number of times it has to be deserialized.
73+ static final Expando <_GoRouteDataBase > stateObjectExpando =
74+ Expando <_GoRouteDataBase >('GoRouteState to _GoRouteDataBase expando' );
75+ }
76+
77+ /// Helper to build a location string from a path and query parameters.
78+ String _buildLocation (String path, {Map <String , dynamic >? queryParams}) =>
79+ Uri .parse (path)
80+ .replace (
81+ queryParameters:
82+ // Avoid `?` in generated location if `queryParams` is empty
83+ queryParams? .isNotEmpty ?? false ? queryParams : null ,
84+ )
85+ .toString ();
86+
87+ /// Holds the parameters for constructing a [GoRoute] .
88+ class _GoRouteParameters {
89+ const _GoRouteParameters ({
90+ required this .builder,
91+ required this .pageBuilder,
92+ required this .redirect,
93+ required this .onExit,
94+ });
95+
96+ final GoRouterWidgetBuilder builder;
97+ final GoRouterPageBuilder pageBuilder;
98+ final GoRouterRedirect redirect;
99+ final ExitCallback onExit;
100+ }
101+
102+ /// Helper to create [GoRoute] parameters from a factory function and an Expando.
103+ _GoRouteParameters _createGoRouteParameters <T extends _GoRouteDataBase >({
104+ required T Function (GoRouterState ) factory ,
105+ required Expando <_GoRouteDataBase > expando,
106+ }) {
107+ T factoryImpl (GoRouterState state) {
108+ final Object ? extra = state.extra;
109+
110+ // If the "extra" value is of type `T` then we know it's the source
111+ // instance, so it doesn't need to be recreated.
112+ if (extra is T ) {
113+ return extra;
114+ }
115+
116+ return (expando[state] ?? = factory (state)) as T ;
117+ }
118+
119+ return _GoRouteParameters (
120+ builder:
121+ (BuildContext context, GoRouterState state) =>
122+ factoryImpl (state).build (context, state),
123+ pageBuilder:
124+ (BuildContext context, GoRouterState state) =>
125+ factoryImpl (state).buildPage (context, state),
126+ redirect:
127+ (BuildContext context, GoRouterState state) =>
128+ factoryImpl (state).redirect (context, state),
129+ onExit:
130+ (BuildContext context, GoRouterState state) =>
131+ factoryImpl (state).onExit (context, state),
132+ );
133+ }
134+
135+ /// A class to represent a [GoRoute] in
136+ /// [Type-safe routing] (https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).
137+ ///
138+ /// Subclasses must override one of [build] , [buildPage] , or
139+ /// [redirect] .
140+ /// {@category Type-safe routes}
141+ abstract class GoRouteData extends _GoRouteDataBase {
142+ /// Allows subclasses to have `const` constructors.
143+ ///
144+ /// [GoRouteData] is abstract and cannot be instantiated directly.
145+ const GoRouteData ();
146+
71147 /// A helper function used by generated code.
72148 ///
73149 /// Should not be used directly.
74150 static String $location (String path, {Map <String , dynamic >? queryParams}) =>
75- Uri .parse (path)
76- .replace (
77- queryParameters:
78- // Avoid `?` in generated location if `queryParams` is empty
79- queryParams? .isNotEmpty ?? false ? queryParams : null ,
80- )
81- .toString ();
151+ _buildLocation (path, queryParams: queryParams);
82152
83153 /// A helper function used by generated code.
84154 ///
@@ -91,72 +161,120 @@ abstract class GoRouteData extends RouteData {
91161 GlobalKey <NavigatorState >? parentNavigatorKey,
92162 List <RouteBase > routes = const < RouteBase > [],
93163 }) {
94- T factoryImpl (GoRouterState state) {
95- final Object ? extra = state.extra;
164+ final _GoRouteParameters params = _createGoRouteParameters <T >(
165+ factory : factory ,
166+ expando: _GoRouteDataBase .stateObjectExpando,
167+ );
96168
97- // If the "extra" value is of type `T` then we know it's the source
98- // instance of `GoRouteData`, so it doesn't need to be recreated.
99- if (extra is T ) {
100- return extra;
101- }
169+ return GoRoute (
170+ path: path,
171+ name: name,
172+ caseSensitive: caseSensitive,
173+ builder: params.builder,
174+ pageBuilder: params.pageBuilder,
175+ redirect: params.redirect,
176+ routes: routes,
177+ parentNavigatorKey: parentNavigatorKey,
178+ onExit: params.onExit,
179+ );
180+ }
102181
103- return (_stateObjectExpando[state] ?? = factory (state)) as T ;
104- }
182+ /// The location of this route, e.g. /family/f2/person/p1
183+ String get location => throw _GoRouteDataBase .shouldBeGeneratedError;
184+
185+ /// Navigate to the route.
186+ void go (BuildContext context) =>
187+ throw _GoRouteDataBase .shouldBeGeneratedError;
105188
106- Widget builder (BuildContext context, GoRouterState state) =>
107- factoryImpl (state).build (context, state);
189+ /// Push the route onto the page stack.
190+ Future <T ?> push <T >(BuildContext context) =>
191+ throw _GoRouteDataBase .shouldBeGeneratedError;
108192
109- Page <void > pageBuilder (BuildContext context, GoRouterState state) =>
110- factoryImpl (state).buildPage (context, state);
193+ /// Replaces the top-most page of the page stack with the route.
194+ void pushReplacement (BuildContext context) =>
195+ throw _GoRouteDataBase .shouldBeGeneratedError;
111196
112- FutureOr <String ?> redirect (BuildContext context, GoRouterState state) =>
113- factoryImpl (state).redirect (context, state);
197+ /// Replaces the top-most page of the page stack with the route but treats
198+ /// it as the same page.
199+ ///
200+ /// The page key will be reused. This will preserve the state and not run any
201+ /// page animation.
202+ ///
203+ void replace (BuildContext context) =>
204+ throw _GoRouteDataBase .shouldBeGeneratedError;
205+ }
114206
115- FutureOr <bool > onExit (BuildContext context, GoRouterState state) =>
116- factoryImpl (state).onExit (context, state);
207+ /// A class to represent a relative [GoRoute] in
208+ /// [Type-safe routing] (https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).
209+ ///
210+ /// Subclasses must override one of [build] , [buildPage] , or
211+ /// [redirect] .
212+ /// {@category Type-safe routes}
213+ abstract class RelativeGoRouteData extends _GoRouteDataBase {
214+ /// Allows subclasses to have `const` constructors.
215+ ///
216+ /// [RelativeGoRouteData] is abstract and cannot be instantiated directly.
217+ const RelativeGoRouteData ();
218+
219+ /// A helper function used by generated code.
220+ ///
221+ /// Should not be used directly.
222+ static String $location (String path, {Map <String , dynamic >? queryParams}) =>
223+ _buildLocation (path, queryParams: queryParams);
224+
225+ /// A helper function used by generated code.
226+ ///
227+ /// Should not be used directly.
228+ static GoRoute $route <T extends RelativeGoRouteData >({
229+ required String path,
230+ bool caseSensitive = true ,
231+ required T Function (GoRouterState ) factory ,
232+ GlobalKey <NavigatorState >? parentNavigatorKey,
233+ List <RouteBase > routes = const < RouteBase > [],
234+ }) {
235+ final _GoRouteParameters params = _createGoRouteParameters <T >(
236+ factory : factory ,
237+ expando: _GoRouteDataBase .stateObjectExpando,
238+ );
117239
118240 return GoRoute (
119241 path: path,
120- name: name,
121242 caseSensitive: caseSensitive,
122- builder: builder,
123- pageBuilder: pageBuilder,
124- redirect: redirect,
243+ builder: params. builder,
244+ pageBuilder: params. pageBuilder,
245+ redirect: params. redirect,
125246 routes: routes,
126247 parentNavigatorKey: parentNavigatorKey,
127- onExit: onExit,
248+ onExit: params. onExit,
128249 );
129250 }
130251
131- /// Used to cache [GoRouteData] that corresponds to a given [GoRouterState]
132- /// to minimize the number of times it has to be deserialized.
133- static final Expando <GoRouteData > _stateObjectExpando = Expando <GoRouteData >(
134- 'GoRouteState to GoRouteData expando' ,
135- );
252+ /// The sub-location of this route, e.g. person/p1
253+ String get subLocation => throw _GoRouteDataBase .shouldBeGeneratedError;
136254
137- /// The location of this route.
138- String get location => throw _shouldBeGeneratedError ;
255+ /// The relative location of this route, e.g. ./person/p1
256+ String get relativeLocation => throw _GoRouteDataBase .shouldBeGeneratedError ;
139257
140258 /// Navigate to the route.
141- void go (BuildContext context) => throw _shouldBeGeneratedError;
259+ void goRelative (BuildContext context) =>
260+ throw _GoRouteDataBase .shouldBeGeneratedError;
142261
143262 /// Push the route onto the page stack.
144- Future <T ?> push <T >(BuildContext context) => throw _shouldBeGeneratedError;
263+ Future <T ?> pushRelative <T >(BuildContext context) =>
264+ throw _GoRouteDataBase .shouldBeGeneratedError;
145265
146266 /// Replaces the top-most page of the page stack with the route.
147- void pushReplacement (BuildContext context) => throw _shouldBeGeneratedError;
267+ void pushReplacementRelative (BuildContext context) =>
268+ throw _GoRouteDataBase .shouldBeGeneratedError;
148269
149270 /// Replaces the top-most page of the page stack with the route but treats
150271 /// it as the same page.
151272 ///
152273 /// The page key will be reused. This will preserve the state and not run any
153274 /// page animation.
154275 ///
155- void replace (BuildContext context) => throw _shouldBeGeneratedError;
156-
157- static UnimplementedError get _shouldBeGeneratedError => UnimplementedError (
158- 'Should be generated using [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).' ,
159- );
276+ void replaceRelative (BuildContext context) =>
277+ throw _GoRouteDataBase .shouldBeGeneratedError;
160278}
161279
162280/// A class to represent a [ShellRoute] in
@@ -402,6 +520,41 @@ class TypedGoRoute<T extends GoRouteData> extends TypedRoute<T> {
402520 final bool caseSensitive;
403521}
404522
523+ /// A superclass for each typed relative go route descendant
524+ @Target (< TargetKind > {TargetKind .library, TargetKind .classType})
525+ class TypedRelativeGoRoute <T extends RelativeGoRouteData >
526+ extends TypedRoute <T > {
527+ /// Default const constructor
528+ const TypedRelativeGoRoute ({
529+ required this .path,
530+ this .routes = const < TypedRoute <RouteData >> [],
531+ this .caseSensitive = true ,
532+ });
533+
534+ /// The relative path that corresponds to this route.
535+ ///
536+ /// See [GoRoute.path] .
537+ ///
538+ ///
539+ final String path;
540+
541+ /// Child route definitions.
542+ ///
543+ /// See [RouteBase.routes] .
544+ final List <TypedRoute <RouteData >> routes;
545+
546+ /// Determines whether the route matching is case sensitive.
547+ ///
548+ /// When `true` , the path must match the specified case. For example,
549+ /// a route with `path: '/family/:fid'` will not match `/FaMiLy/f2` .
550+ ///
551+ /// When `false` , the path matching is case insensitive. The route
552+ /// with `path: '/family/:fid'` will match `/FaMiLy/f2` .
553+ ///
554+ /// Defaults to `true` .
555+ final bool caseSensitive;
556+ }
557+
405558/// A superclass for each typed shell route descendant
406559@Target (< TargetKind > {TargetKind .library, TargetKind .classType})
407560class TypedShellRoute <T extends ShellRouteData > extends TypedRoute <T > {
0 commit comments