forked from flutter/packages
-
Notifications
You must be signed in to change notification settings - Fork 0
/
router.dart
316 lines (285 loc) · 10.6 KB
/
router.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
// 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/widgets.dart';
import 'configuration.dart';
import 'delegate.dart';
import 'information_provider.dart';
import 'logging.dart';
import 'match.dart';
import 'matching.dart';
import 'misc/inherited_router.dart';
import 'parser.dart';
import 'typedefs.dart';
/// The route configuration for the app.
///
/// The `routes` list specifies the top-level routes for the app. It must not be
/// empty and must contain an [GoRouter] to match `/`.
///
/// See the [Get
/// started](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/main.dart)
/// example, which shows an app with a simple route configuration.
///
/// The [redirect] callback allows the app to redirect to a new location.
/// Alternatively, you can specify a redirect for an individual route using
/// [GoRoute.redirect]. If [BuildContext.dependOnInheritedWidgetOfExactType] is
/// used during the redirection (which is how `of` methods are usually
/// implemented), a re-evaluation will be triggered when the [InheritedWidget]
/// changes.
///
/// See also:
/// * [Configuration](https://pub.dev/documentation/go_router/topics/Configuration-topic.html)
/// * [GoRoute], which provides APIs to define the routing table.
/// * [examples](https://github.com/flutter/packages/tree/main/packages/go_router/example),
/// which contains examples for different routing scenarios.
/// {@category Get started}
/// {@category Upgrading}
/// {@category Configuration}
/// {@category Navigation}
/// {@category Redirection}
/// {@category Web}
/// {@category Deep linking}
/// {@category Error handling}
/// {@category Named routes}
class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
/// Default constructor to configure a GoRouter with a routes builder
/// and an error page builder.
///
/// The `routes` must not be null and must contain an [GoRouter] to match `/`.
GoRouter({
required List<RouteBase> routes,
// TODO(johnpryan): Change to a route, improve error API
// See https://github.com/flutter/flutter/issues/108144
GoRouterPageBuilder? errorPageBuilder,
GoRouterWidgetBuilder? errorBuilder,
GoRouterRedirect? redirect,
Listenable? refreshListenable,
int redirectLimit = 5,
bool routerNeglect = false,
String? initialLocation,
List<NavigatorObserver>? observers,
bool debugLogDiagnostics = false,
GlobalKey<NavigatorState>? navigatorKey,
String? restorationScopeId,
}) : backButtonDispatcher = RootBackButtonDispatcher() {
setLogging(enabled: debugLogDiagnostics);
WidgetsFlutterBinding.ensureInitialized();
navigatorKey ??= GlobalKey<NavigatorState>();
_routeConfiguration = RouteConfiguration(
routes: routes,
topRedirect: redirect ?? (_, __) => null,
redirectLimit: redirectLimit,
navigatorKey: navigatorKey,
);
_routeInformationParser = GoRouteInformationParser(
configuration: _routeConfiguration,
debugRequireGoRouteInformationProvider: true,
);
_routeInformationProvider = GoRouteInformationProvider(
initialRouteInformation: RouteInformation(
location: _effectiveInitialLocation(initialLocation)),
refreshListenable: refreshListenable);
_routerDelegate = GoRouterDelegate(
configuration: _routeConfiguration,
errorPageBuilder: errorPageBuilder,
errorBuilder: errorBuilder,
routerNeglect: routerNeglect,
observers: <NavigatorObserver>[
...observers ?? <NavigatorObserver>[],
],
restorationScopeId: restorationScopeId,
// wrap the returned Navigator to enable GoRouter.of(context).go() et al,
// allowing the caller to wrap the navigator themselves
builderWithNav: (BuildContext context, Widget child) =>
InheritedGoRouter(goRouter: this, child: child),
);
_routerDelegate.addListener(_handleStateMayChange);
assert(() {
log.info('setting initial location $initialLocation');
return true;
}());
}
late final RouteConfiguration _routeConfiguration;
late final GoRouteInformationParser _routeInformationParser;
late final GoRouterDelegate _routerDelegate;
late final GoRouteInformationProvider _routeInformationProvider;
@override
final BackButtonDispatcher backButtonDispatcher;
/// The router delegate. Provide this to the MaterialApp or CupertinoApp's
/// `.router()` constructor
@override
GoRouterDelegate get routerDelegate => _routerDelegate;
/// The route information provider used by [GoRouter].
@override
GoRouteInformationProvider get routeInformationProvider =>
_routeInformationProvider;
/// The route information parser used by [GoRouter].
@override
GoRouteInformationParser get routeInformationParser =>
_routeInformationParser;
/// The route configuration. Used for testing.
// TODO(johnpryan): Remove this, integration tests shouldn't need access
@visibleForTesting
RouteConfiguration get routeConfiguration => _routeConfiguration;
/// Gets the current location.
// TODO(chunhtai): deprecates this once go_router_builder is migrated to
// GoRouterState.of.
String get location => _location;
String _location = '/';
/// Returns `true` if there is more than 1 page on the stack.
bool canPop() => _routerDelegate.canPop();
void _handleStateMayChange() {
final String newLocation =
_routerDelegate.currentConfiguration.location.toString();
if (_location != newLocation) {
_location = newLocation;
notifyListeners();
}
}
/// Get a location from route name and parameters.
/// This is useful for redirecting to a named location.
String namedLocation(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
}) =>
_routeInformationParser.configuration.namedLocation(
name,
params: params,
queryParams: queryParams,
);
/// Navigate to a URI location w/ optional query parameters, e.g.
/// `/family/f2/person/p1?color=blue`
void go(String location, {Object? extra}) {
assert(() {
log.info('going to $location');
return true;
}());
_routeInformationProvider.value =
RouteInformation(location: location, state: extra);
}
/// Navigate to a named route w/ optional parameters, e.g.
/// `name='person', params={'fid': 'f2', 'pid': 'p1'}`
/// Navigate to the named route.
void goNamed(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
Object? extra,
}) =>
go(
namedLocation(name, params: params, queryParams: queryParams),
extra: extra,
);
/// Push a URI location onto the page stack w/ optional query parameters, e.g.
/// `/family/f2/person/p1?color=blue`
void push(String location, {Object? extra}) {
assert(() {
log.info('pushing $location');
return true;
}());
_routeInformationParser
.parseRouteInformationWithDependencies(
RouteInformation(location: location, state: extra),
// TODO(chunhtai): avoid accessing the context directly through global key.
// https://github.com/flutter/flutter/issues/99112
_routerDelegate.navigatorKey.currentContext!,
)
.then<void>((RouteMatchList matches) {
_routerDelegate.push(matches.last);
});
}
/// Push a named route onto the page stack w/ optional parameters, e.g.
/// `name='person', params={'fid': 'f2', 'pid': 'p1'}`
void pushNamed(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
Object? extra,
}) =>
push(
namedLocation(name, params: params, queryParams: queryParams),
extra: extra,
);
/// Replaces the top-most page of the page stack with the given URL location
/// w/ optional query parameters, e.g. `/family/f2/person/p1?color=blue`.
///
/// See also:
/// * [go] which navigates to the location.
/// * [push] which pushes the location onto the page stack.
void replace(String location, {Object? extra}) {
routeInformationParser
.parseRouteInformationWithDependencies(
RouteInformation(location: location, state: extra),
// TODO(chunhtai): avoid accessing the context directly through global key.
// https://github.com/flutter/flutter/issues/99112
_routerDelegate.navigatorKey.currentContext!,
)
.then<void>((RouteMatchList matchList) {
routerDelegate.replace(matchList.matches.last);
});
}
/// Replaces the top-most page of the page stack with the named route w/
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid':
/// 'p1'}`.
///
/// See also:
/// * [goNamed] which navigates a named route.
/// * [pushNamed] which pushes a named route onto the page stack.
void replaceNamed(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
Object? extra,
}) {
replace(
namedLocation(name, params: params, queryParams: queryParams),
extra: extra,
);
}
/// Pop the top page off the GoRouter's page stack.
void pop() {
assert(() {
log.info('popping $location');
return true;
}());
_routerDelegate.pop();
}
/// Calls [pop] repeatedly until the predicate returns true.
void popUntil(bool Function(RouteMatch) predicate) {
_routerDelegate.popUntil(predicate);
}
/// Refresh the route.
void refresh() {
assert(() {
log.info('refreshing $location');
return true;
}());
_routeInformationProvider.notifyListeners();
}
/// Find the current GoRouter in the widget tree.
static GoRouter of(BuildContext context) {
final InheritedGoRouter? inherited =
context.dependOnInheritedWidgetOfExactType<InheritedGoRouter>();
assert(inherited != null, 'No GoRouter found in context');
return inherited!.goRouter;
}
@override
void dispose() {
_routeInformationProvider.dispose();
_routerDelegate.removeListener(_handleStateMayChange);
_routerDelegate.dispose();
super.dispose();
}
String _effectiveInitialLocation(String? initialLocation) {
final String platformDefault =
WidgetsBinding.instance.platformDispatcher.defaultRouteName;
if (initialLocation == null) {
return platformDefault;
} else if (platformDefault == '/') {
return initialLocation;
} else {
return platformDefault;
}
}
}