@@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
8
8
import 'package:meta/meta.dart' ;
9
9
import 'package:meta/meta_meta.dart' ;
10
10
11
+ import 'configuration.dart' ;
11
12
import 'route.dart' ;
12
13
import 'state.dart' ;
13
14
@@ -18,17 +19,10 @@ abstract class RouteData {
18
19
const RouteData ();
19
20
}
20
21
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 ();
32
26
33
27
/// Creates the [Widget] for `this` route.
34
28
///
@@ -68,17 +62,93 @@ abstract class GoRouteData extends RouteData {
68
62
/// Corresponds to [GoRoute.onExit] .
69
63
FutureOr <bool > onExit (BuildContext context, GoRouterState state) => true ;
70
64
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
+
71
147
/// A helper function used by generated code.
72
148
///
73
149
/// Should not be used directly.
74
150
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);
82
152
83
153
/// A helper function used by generated code.
84
154
///
@@ -91,72 +161,120 @@ abstract class GoRouteData extends RouteData {
91
161
GlobalKey <NavigatorState >? parentNavigatorKey,
92
162
List <RouteBase > routes = const < RouteBase > [],
93
163
}) {
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
+ );
96
168
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
+ }
102
181
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;
105
188
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;
108
192
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;
111
196
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
+ }
114
206
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
+ );
117
239
118
240
return GoRoute (
119
241
path: path,
120
- name: name,
121
242
caseSensitive: caseSensitive,
122
- builder: builder,
123
- pageBuilder: pageBuilder,
124
- redirect: redirect,
243
+ builder: params. builder,
244
+ pageBuilder: params. pageBuilder,
245
+ redirect: params. redirect,
125
246
routes: routes,
126
247
parentNavigatorKey: parentNavigatorKey,
127
- onExit: onExit,
248
+ onExit: params. onExit,
128
249
);
129
250
}
130
251
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;
136
254
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 ;
139
257
140
258
/// Navigate to the route.
141
- void go (BuildContext context) => throw _shouldBeGeneratedError;
259
+ void goRelative (BuildContext context) =>
260
+ throw _GoRouteDataBase .shouldBeGeneratedError;
142
261
143
262
/// 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;
145
265
146
266
/// 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;
148
269
149
270
/// Replaces the top-most page of the page stack with the route but treats
150
271
/// it as the same page.
151
272
///
152
273
/// The page key will be reused. This will preserve the state and not run any
153
274
/// page animation.
154
275
///
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;
160
278
}
161
279
162
280
/// A class to represent a [ShellRoute] in
@@ -402,6 +520,41 @@ class TypedGoRoute<T extends GoRouteData> extends TypedRoute<T> {
402
520
final bool caseSensitive;
403
521
}
404
522
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
+
405
558
/// A superclass for each typed shell route descendant
406
559
@Target (< TargetKind > {TargetKind .library, TargetKind .classType})
407
560
class TypedShellRoute <T extends ShellRouteData > extends TypedRoute <T > {
0 commit comments