1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'package:flutter/cupertino.dart'; |
6 | /// @docImport 'package:flutter/material.dart'; |
7 | /// |
8 | /// @docImport 'app.dart'; |
9 | /// @docImport 'scroll_view.dart'; |
10 | library; |
11 | |
12 | import 'dart:async'; |
13 | import 'dart:collection'; |
14 | |
15 | import 'package:collection/collection.dart'; |
16 | import 'package:flutter/foundation.dart'; |
17 | import 'package:flutter/scheduler.dart'; |
18 | import 'package:flutter/services.dart'; |
19 | |
20 | import 'basic.dart'; |
21 | import 'binding.dart'; |
22 | import 'framework.dart'; |
23 | import 'navigator.dart'; |
24 | import 'restoration.dart'; |
25 | import 'restoration_properties.dart'; |
26 | |
27 | /// A piece of routing information. |
28 | /// |
29 | /// The route information consists of a location string of the application and |
30 | /// a state object that configures the application in that location. |
31 | /// |
32 | /// This information flows two ways, from the [RouteInformationProvider] to the |
33 | /// [Router] or from the [Router] to [RouteInformationProvider]. |
34 | /// |
35 | /// In the former case, the [RouteInformationProvider] notifies the [Router] |
36 | /// widget when a new [RouteInformation] is available. The [Router] widget takes |
37 | /// these information and navigates accordingly. |
38 | /// |
39 | /// The latter case happens in web application where the [Router] reports route |
40 | /// changes back to the web engine. |
41 | /// |
42 | /// The current [RouteInformation] of an application is also used for state |
43 | /// restoration purposes. Before an application is killed, the [Router] converts |
44 | /// its current configurations into a [RouteInformation] object utilizing the |
45 | /// [RouteInformationProvider]. The [RouteInformation] object is then serialized |
46 | /// out and persisted. During state restoration, the object is deserialized and |
47 | /// passed back to the [RouteInformationProvider], which turns it into a |
48 | /// configuration for the [Router] again to restore its state from. |
49 | class RouteInformation { |
50 | /// Creates a route information object. |
51 | /// |
52 | /// Either `location` or `uri` must not be null. |
53 | const RouteInformation({ |
54 | @Deprecated( |
55 | 'Pass Uri.parse(location) to uri parameter instead. ' |
56 | 'This feature was deprecated after v3.8.0-3.0.pre.' |
57 | ) |
58 | String? location, |
59 | Uri? uri, |
60 | this.state, |
61 | }) : _location = location, |
62 | _uri = uri, |
63 | assert((location != null) != (uri != null)); |
64 | |
65 | /// The location of the application. |
66 | /// |
67 | /// The string is usually in the format of multiple string identifiers with |
68 | /// slashes in between. ex: `/`, `/path`, `/path/to/the/app`. |
69 | @Deprecated( |
70 | 'Use uri instead. ' |
71 | 'This feature was deprecated after v3.8.0-3.0.pre.' |
72 | ) |
73 | String get location { |
74 | return _location ?? Uri.decodeComponent( |
75 | Uri( |
76 | path: uri.path.isEmpty ? '/': uri.path, |
77 | queryParameters: uri.queryParametersAll.isEmpty ? null : uri.queryParametersAll, |
78 | fragment: uri.fragment.isEmpty ? null : uri.fragment, |
79 | ).toString(), |
80 | ); |
81 | } |
82 | final String? _location; |
83 | |
84 | /// The uri location of the application. |
85 | /// |
86 | /// The host and scheme will not be empty if this object is created from a |
87 | /// deep link request. They represents the website that redirect the deep |
88 | /// link. |
89 | /// |
90 | /// In web platform, the host and scheme are always empty. |
91 | Uri get uri { |
92 | if (_uri != null){ |
93 | return _uri; |
94 | } |
95 | return Uri.parse(_location!); |
96 | } |
97 | final Uri? _uri; |
98 | |
99 | /// The state of the application in the [uri]. |
100 | /// |
101 | /// The app can have different states even in the same location. For example, |
102 | /// the text inside a [TextField] or the scroll position in a [ScrollView]. |
103 | /// These widget states can be stored in the [state]. |
104 | /// |
105 | /// On the web, this information is stored in the browser history when the |
106 | /// [Router] reports this route information back to the web engine |
107 | /// through the [PlatformRouteInformationProvider]. The information |
108 | /// is then passed back, along with the [uri], when the user |
109 | /// clicks the back or forward buttons. |
110 | /// |
111 | /// This information is also serialized and persisted alongside the |
112 | /// [uri] for state restoration purposes. During state restoration, |
113 | /// the information is made available again to the [Router] so it can restore |
114 | /// its configuration to the previous state. |
115 | /// |
116 | /// The state must be serializable. |
117 | final Object? state; |
118 | } |
119 | |
120 | /// A convenient bundle to configure a [Router] widget. |
121 | /// |
122 | /// To configure a [Router] widget, one needs to provide several delegates, |
123 | /// [RouteInformationProvider], [RouteInformationParser], [RouterDelegate], |
124 | /// and [BackButtonDispatcher]. This abstract class provides way to bundle these |
125 | /// delegates into a single object to configure a [Router]. |
126 | /// |
127 | /// The [backButtonDispatcher], [routeInformationProvider], and |
128 | /// [routeInformationProvider] are optional. |
129 | /// |
130 | /// The [routeInformationProvider] and [routeInformationParser] must |
131 | /// both be provided or both not provided. |
132 | class RouterConfig<T> { |
133 | /// Creates a [RouterConfig]. |
134 | /// |
135 | /// The [backButtonDispatcher], [routeInformationProvider], and |
136 | /// [routeInformationParser] are optional. |
137 | /// |
138 | /// The [routeInformationProvider] and [routeInformationParser] must both be |
139 | /// provided or both not provided. |
140 | const RouterConfig({ |
141 | this.routeInformationProvider, |
142 | this.routeInformationParser, |
143 | required this.routerDelegate, |
144 | this.backButtonDispatcher, |
145 | }) : assert((routeInformationProvider == null) == (routeInformationParser == null)); |
146 | |
147 | /// The [RouteInformationProvider] that is used to configure the [Router]. |
148 | final RouteInformationProvider? routeInformationProvider; |
149 | |
150 | /// The [RouteInformationParser] that is used to configure the [Router]. |
151 | final RouteInformationParser<T>? routeInformationParser; |
152 | |
153 | /// The [RouterDelegate] that is used to configure the [Router]. |
154 | final RouterDelegate<T> routerDelegate; |
155 | |
156 | /// The [BackButtonDispatcher] that is used to configure the [Router]. |
157 | final BackButtonDispatcher? backButtonDispatcher; |
158 | } |
159 | |
160 | /// The dispatcher for opening and closing pages of an application. |
161 | /// |
162 | /// This widget listens for routing information from the operating system (e.g. |
163 | /// an initial route provided on app startup, a new route obtained when an |
164 | /// intent is received, or a notification that the user hit the system back |
165 | /// button), parses route information into data of type `T`, and then converts |
166 | /// that data into [Page] objects that it passes to a [Navigator]. |
167 | /// |
168 | /// Each part of this process can be overridden and configured as desired. |
169 | /// |
170 | /// The [routeInformationProvider] can be overridden to change how the name of |
171 | /// the route is obtained. The [RouteInformationProvider.value] is used as the |
172 | /// initial route when the [Router] is first created. Subsequent notifications |
173 | /// from the [RouteInformationProvider] to its listeners are treated as |
174 | /// notifications that the route information has changed. |
175 | /// |
176 | /// The [backButtonDispatcher] can be overridden to change how back button |
177 | /// notifications are received. This must be a [BackButtonDispatcher], which is |
178 | /// an object where callbacks can be registered, and which can be chained so |
179 | /// that back button presses are delegated to subsidiary routers. The callbacks |
180 | /// are invoked to indicate that the user is trying to close the current route |
181 | /// (by pressing the system back button); the [Router] ensures that when this |
182 | /// callback is invoked, the message is passed to the [routerDelegate] and its |
183 | /// result is provided back to the [backButtonDispatcher]. Some platforms don't |
184 | /// have back buttons (e.g. iOS and desktop platforms); on those platforms this |
185 | /// notification is never sent. Typically, the [backButtonDispatcher] for the |
186 | /// root router is an instance of [RootBackButtonDispatcher], which uses a |
187 | /// [WidgetsBindingObserver] to listen to the `popRoute` notifications from |
188 | /// [SystemChannels.navigation]. Nested [Router]s typically use a |
189 | /// [ChildBackButtonDispatcher], which must be provided the |
190 | /// [BackButtonDispatcher] of its ancestor [Router] (available via [Router.of]). |
191 | /// |
192 | /// The [routeInformationParser] can be overridden to change how names obtained |
193 | /// from the [routeInformationProvider] are interpreted. It must implement the |
194 | /// [RouteInformationParser] interface, specialized with the same type as the |
195 | /// [Router] itself. This type, `T`, represents the data type that the |
196 | /// [routeInformationParser] will generate. |
197 | /// |
198 | /// The [routerDelegate] can be overridden to change how the output of the |
199 | /// [routeInformationParser] is interpreted. It must implement the |
200 | /// [RouterDelegate] interface, also specialized with `T`; it takes as input |
201 | /// the data (of type `T`) from the [routeInformationParser], and is responsible |
202 | /// for providing a navigating widget to insert into the widget tree. The |
203 | /// [RouterDelegate] interface is also [Listenable]; notifications are taken |
204 | /// to mean that the [Router] needs to rebuild. |
205 | /// |
206 | /// ## Concerns regarding asynchrony |
207 | /// |
208 | /// Some of the APIs (notably those involving [RouteInformationParser] and |
209 | /// [RouterDelegate]) are asynchronous. |
210 | /// |
211 | /// When developing objects implementing these APIs, if the work can be done |
212 | /// entirely synchronously, then consider using [SynchronousFuture] for the |
213 | /// future returned from the relevant methods. This will allow the [Router] to |
214 | /// proceed in a completely synchronous way, which removes a number of |
215 | /// complications. |
216 | /// |
217 | /// Using asynchronous computation is entirely reasonable, however, and the API |
218 | /// is designed to support it. For example, maybe a set of images need to be |
219 | /// loaded before a route can be shown; waiting for those images to be loaded |
220 | /// before [RouterDelegate.setNewRoutePath] returns is a reasonable approach to |
221 | /// handle this case. |
222 | /// |
223 | /// If an asynchronous operation is ongoing when a new one is to be started, the |
224 | /// precise behavior will depend on the exact circumstances, as follows: |
225 | /// |
226 | /// If the active operation is a [routeInformationParser] parsing a new route information: |
227 | /// that operation's result, if it ever completes, will be discarded. |
228 | /// |
229 | /// If the active operation is a [routerDelegate] handling a pop request: |
230 | /// the previous pop is immediately completed with "false", claiming that the |
231 | /// previous pop was not handled (this may cause the application to close). |
232 | /// |
233 | /// If the active operation is a [routerDelegate] handling an initial route |
234 | /// or a pushed route, the result depends on the new operation. If the new |
235 | /// operation is a pop request, then the original operation's result, if it ever |
236 | /// completes, will be discarded. If the new operation is a push request, |
237 | /// however, the [routeInformationParser] will be requested to start the parsing, and |
238 | /// only if that finishes before the original [routerDelegate] request |
239 | /// completes will that original request's result be discarded. |
240 | /// |
241 | /// If the identity of the [Router] widget's delegates change while an |
242 | /// asynchronous operation is in progress, to keep matters simple, all active |
243 | /// asynchronous operations will have their results discarded. It is generally |
244 | /// considered unusual for these delegates to change during the lifetime of the |
245 | /// [Router]. |
246 | /// |
247 | /// If the [Router] itself is disposed while an asynchronous operation is in |
248 | /// progress, all active asynchronous operations will have their results |
249 | /// discarded also. |
250 | /// |
251 | /// No explicit signals are provided to the [routeInformationParser] or |
252 | /// [routerDelegate] to indicate when any of the above happens, so it is |
253 | /// strongly recommended that [RouteInformationParser] and [RouterDelegate] |
254 | /// implementations not perform extensive computation. |
255 | /// |
256 | /// ## Application architectural design |
257 | /// |
258 | /// An application can have zero, one, or many [Router] widgets, depending on |
259 | /// its needs. |
260 | /// |
261 | /// An application might have no [Router] widgets if it has only one "screen", |
262 | /// or if the facilities provided by [Navigator] are sufficient. This is common |
263 | /// for desktop applications, where subsidiary "screens" are represented using |
264 | /// different windows rather than changing the active interface. |
265 | /// |
266 | /// A particularly elaborate application might have multiple [Router] widgets, |
267 | /// in a tree configuration, with the first handling the entire route parsing |
268 | /// and making the result available for routers in the subtree. The routers in |
269 | /// the subtree do not participate in route information parsing but merely take the |
270 | /// result from the first router to build their sub routes. |
271 | /// |
272 | /// Most applications only need a single [Router]. |
273 | /// |
274 | /// ## URL updates for web applications |
275 | /// |
276 | /// In the web platform, keeping the URL in the browser's location bar up to |
277 | /// date with the application state ensures that the browser constructs its |
278 | /// history entry correctly, allowing its back and forward buttons to function |
279 | /// as the user expects. |
280 | /// |
281 | /// If an app state change leads to the [Router] rebuilding, the [Router] will |
282 | /// retrieve the new route information from the [routerDelegate]'s |
283 | /// [RouterDelegate.currentConfiguration] method and the |
284 | /// [routeInformationParser]'s [RouteInformationParser.restoreRouteInformation] |
285 | /// method. |
286 | /// |
287 | /// If the location in the new route information is different from the |
288 | /// current location, this is considered to be a navigation event, the |
289 | /// [PlatformRouteInformationProvider.routerReportsNewRouteInformation] method |
290 | /// calls [SystemNavigator.routeInformationUpdated] with `replace = false` to |
291 | /// notify the engine, and through that the browser, to create a history entry |
292 | /// with the new url. Otherwise, |
293 | /// [PlatformRouteInformationProvider.routerReportsNewRouteInformation] calls |
294 | /// [SystemNavigator.routeInformationUpdated] with `replace = true` to update |
295 | /// the current history entry with the latest [RouteInformation]. |
296 | /// |
297 | /// One can force the [Router] to report new route information as navigation |
298 | /// event to the [routeInformationProvider] (and thus the browser) even if the |
299 | /// [RouteInformation.uri] has not changed by calling the [Router.navigate] |
300 | /// method with a callback that performs the state change. This causes [Router] |
301 | /// to call the [RouteInformationProvider.routerReportsNewRouteInformation] with |
302 | /// [RouteInformationReportingType.navigate], and thus causes |
303 | /// [PlatformRouteInformationProvider] to push a new history entry regardlessly. |
304 | /// This allows one to support the browser's back and forward buttons without |
305 | /// changing the URL. For example, the scroll position of a scroll view may be |
306 | /// saved in the [RouteInformation.state]. Using [Router.navigate] to update the |
307 | /// scroll position causes the browser to create a new history entry with the |
308 | /// [RouteInformation.state] that stores this new scroll position. When the user |
309 | /// clicks the back button, the app will go back to the previous scroll position |
310 | /// without changing the URL in the location bar. |
311 | /// |
312 | /// One can also force the [Router] to ignore a navigation event by making |
313 | /// those changes during a callback passed to [Router.neglect]. The [Router] |
314 | /// calls the [RouteInformationProvider.routerReportsNewRouteInformation] with |
315 | /// [RouteInformationReportingType.neglect], and thus causes |
316 | /// [PlatformRouteInformationProvider] to replace the current history entry |
317 | /// regardlessly even if it detects location change. |
318 | /// |
319 | /// To opt out of URL updates entirely, pass null for [routeInformationProvider] |
320 | /// and [routeInformationParser]. This is not recommended in general, but may be |
321 | /// appropriate in the following cases: |
322 | /// |
323 | /// * The application does not target the web platform. |
324 | /// |
325 | /// * There are multiple router widgets in the application. Only one [Router] |
326 | /// widget should update the URL (typically the top-most one created by the |
327 | /// [WidgetsApp.router], [MaterialApp.router], or [CupertinoApp.router]). |
328 | /// |
329 | /// * The application does not need to implement in-app navigation using the |
330 | /// browser's back and forward buttons. |
331 | /// |
332 | /// In other cases, it is strongly recommended to implement the |
333 | /// [RouterDelegate.currentConfiguration] and |
334 | /// [RouteInformationParser.restoreRouteInformation] APIs to provide an optimal |
335 | /// user experience when running on the web platform. |
336 | /// |
337 | /// ## State Restoration |
338 | /// |
339 | /// The [Router] will restore the current configuration of the [routerDelegate] |
340 | /// during state restoration if it is configured with a [restorationScopeId] and |
341 | /// state restoration is enabled for the subtree. For that, the value of |
342 | /// [RouterDelegate.currentConfiguration] is serialized and persisted before the |
343 | /// app is killed by the operating system. After the app is restarted, the value |
344 | /// is deserialized and passed back to the [RouterDelegate] via a call to |
345 | /// [RouterDelegate.setRestoredRoutePath] (which by default just calls |
346 | /// [RouterDelegate.setNewRoutePath]). It is the responsibility of the |
347 | /// [RouterDelegate] to use the configuration information provided to restore |
348 | /// its internal state. |
349 | /// |
350 | /// To serialize [RouterDelegate.currentConfiguration] and to deserialize it |
351 | /// again, the [Router] calls [RouteInformationParser.restoreRouteInformation] |
352 | /// and [RouteInformationParser.parseRouteInformation], respectively. Therefore, |
353 | /// if a [restorationScopeId] is provided, a [routeInformationParser] must be |
354 | /// configured as well. |
355 | class Router<T> extends StatefulWidget { |
356 | /// Creates a router. |
357 | /// |
358 | /// The [routeInformationProvider] and [routeInformationParser] can be null if this |
359 | /// router does not depend on route information. A common example is a sub router |
360 | /// that builds its content completely based on the app state. |
361 | /// |
362 | /// The [routeInformationProvider] and [routeInformationParser] must |
363 | /// both be provided or not provided. |
364 | const Router({ |
365 | super.key, |
366 | this.routeInformationProvider, |
367 | this.routeInformationParser, |
368 | required this.routerDelegate, |
369 | this.backButtonDispatcher, |
370 | this.restorationScopeId, |
371 | }) : assert( |
372 | routeInformationProvider == null || routeInformationParser != null, |
373 | 'A routeInformationParser must be provided when a routeInformationProvider is specified.', |
374 | ); |
375 | |
376 | /// Creates a router with a [RouterConfig]. |
377 | /// |
378 | /// The [RouterConfig.routeInformationProvider] and |
379 | /// [RouterConfig.routeInformationParser] can be null if this router does not |
380 | /// depend on route information. A common example is a sub router that builds |
381 | /// its content completely based on the app state. |
382 | /// |
383 | /// If the [RouterConfig.routeInformationProvider] is not null, then |
384 | /// [RouterConfig.routeInformationParser] must also not be |
385 | /// null. |
386 | factory Router.withConfig({ |
387 | Key? key, |
388 | required RouterConfig<T> config, |
389 | String? restorationScopeId, |
390 | }) { |
391 | return Router<T>( |
392 | key: key, |
393 | routeInformationProvider: config.routeInformationProvider, |
394 | routeInformationParser: config.routeInformationParser, |
395 | routerDelegate: config.routerDelegate, |
396 | backButtonDispatcher: config.backButtonDispatcher, |
397 | restorationScopeId: restorationScopeId, |
398 | ); |
399 | } |
400 | |
401 | /// The route information provider for the router. |
402 | /// |
403 | /// The value at the time of first build will be used as the initial route. |
404 | /// The [Router] listens to this provider and rebuilds with new names when |
405 | /// it notifies. |
406 | /// |
407 | /// This can be null if this router does not rely on the route information |
408 | /// to build its content. In such case, the [routeInformationParser] must also |
409 | /// be null. |
410 | final RouteInformationProvider? routeInformationProvider; |
411 | |
412 | /// The route information parser for the router. |
413 | /// |
414 | /// When the [Router] gets a new route information from the [routeInformationProvider], |
415 | /// the [Router] uses this delegate to parse the route information and produce a |
416 | /// configuration. The configuration will be used by [routerDelegate] and |
417 | /// eventually rebuilds the [Router] widget. |
418 | /// |
419 | /// Since this delegate is the primary consumer of the [routeInformationProvider], |
420 | /// it must not be null if [routeInformationProvider] is not null. |
421 | final RouteInformationParser<T>? routeInformationParser; |
422 | |
423 | /// The router delegate for the router. |
424 | /// |
425 | /// This delegate consumes the configuration from [routeInformationParser] and |
426 | /// builds a navigating widget for the [Router]. |
427 | /// |
428 | /// It is also the primary respondent for the [backButtonDispatcher]. The |
429 | /// [Router] relies on [RouterDelegate.popRoute] to handle the back |
430 | /// button. |
431 | /// |
432 | /// If the [RouterDelegate.currentConfiguration] returns a non-null object, |
433 | /// this [Router] will opt for URL updates. |
434 | final RouterDelegate<T> routerDelegate; |
435 | |
436 | /// The back button dispatcher for the router. |
437 | /// |
438 | /// The two common alternatives are the [RootBackButtonDispatcher] for root |
439 | /// router, or the [ChildBackButtonDispatcher] for other routers. |
440 | final BackButtonDispatcher? backButtonDispatcher; |
441 | |
442 | /// Restoration ID to save and restore the state of the [Router]. |
443 | /// |
444 | /// If non-null, the [Router] will persist the [RouterDelegate]'s current |
445 | /// configuration (i.e. [RouterDelegate.currentConfiguration]). During state |
446 | /// restoration, the [Router] informs the [RouterDelegate] of the previous |
447 | /// configuration by calling [RouterDelegate.setRestoredRoutePath] (which by |
448 | /// default just calls [RouterDelegate.setNewRoutePath]). It is the |
449 | /// responsibility of the [RouterDelegate] to restore its internal state based |
450 | /// on the provided configuration. |
451 | /// |
452 | /// The router uses the [RouteInformationParser] to serialize and deserialize |
453 | /// [RouterDelegate.currentConfiguration]. Therefore, a |
454 | /// [routeInformationParser] must be provided when [restorationScopeId] is |
455 | /// non-null. |
456 | /// |
457 | /// See also: |
458 | /// |
459 | /// * [RestorationManager], which explains how state restoration works in |
460 | /// Flutter. |
461 | final String? restorationScopeId; |
462 | |
463 | /// Retrieves the immediate [Router] ancestor from the given context. |
464 | /// |
465 | /// This method provides access to the delegates in the [Router]. For example, |
466 | /// this can be used to access the [backButtonDispatcher] of the parent router |
467 | /// when creating a [ChildBackButtonDispatcher] for a nested [Router]. |
468 | /// |
469 | /// If no [Router] ancestor exists for the given context, this will assert in |
470 | /// debug mode, and throw an exception in release mode. |
471 | /// |
472 | /// See also: |
473 | /// |
474 | /// * [maybeOf], which is a similar function, but it will return null instead |
475 | /// of throwing an exception if no [Router] ancestor exists. |
476 | static Router<T> of<T extends Object?>(BuildContext context) { |
477 | final _RouterScope? scope = context.dependOnInheritedWidgetOfExactType<_RouterScope>(); |
478 | assert(() { |
479 | if (scope == null) { |
480 | throw FlutterError( |
481 | 'Router operation requested with a context that does not include a Router.\n' |
482 | 'The context used to retrieve the Router must be that of a widget that ' |
483 | 'is a descendant of a Router widget.', |
484 | ); |
485 | } |
486 | return true; |
487 | }()); |
488 | return scope!.routerState.widget as Router<T>; |
489 | } |
490 | |
491 | /// Retrieves the immediate [Router] ancestor from the given context. |
492 | /// |
493 | /// This method provides access to the delegates in the [Router]. For example, |
494 | /// this can be used to access the [backButtonDispatcher] of the parent router |
495 | /// when creating a [ChildBackButtonDispatcher] for a nested [Router]. |
496 | /// |
497 | /// If no `Router` ancestor exists for the given context, this will return |
498 | /// null. |
499 | /// |
500 | /// See also: |
501 | /// |
502 | /// * [of], a similar method that returns a non-nullable value, and will |
503 | /// throw if no [Router] ancestor exists. |
504 | static Router<T>? maybeOf<T extends Object?>(BuildContext context) { |
505 | final _RouterScope? scope = context.dependOnInheritedWidgetOfExactType<_RouterScope>(); |
506 | return scope?.routerState.widget as Router<T>?; |
507 | } |
508 | |
509 | /// Forces the [Router] to run the [callback] and create a new history |
510 | /// entry in the browser. |
511 | /// |
512 | /// The web application relies on the [Router] to report new route information |
513 | /// in order to create browser history entry. The [Router] will only report |
514 | /// them if it detects the [RouteInformation.uri] changes. Use this |
515 | /// method if you want the [Router] to report the route information even if |
516 | /// the location does not change. This can be useful when you want to |
517 | /// support the browser backward and forward button without changing the URL. |
518 | /// |
519 | /// For example, you can store certain state such as the scroll position into |
520 | /// the [RouteInformation.state]. If you use this method to update the |
521 | /// scroll position multiple times with the same URL, the browser will create |
522 | /// a stack of new history entries with the same URL but different |
523 | /// [RouteInformation.state]s that store the new scroll positions. If the user |
524 | /// click the backward button in the browser, the browser will restore the |
525 | /// scroll positions saved in history entries without changing the URL. |
526 | /// |
527 | /// See also: |
528 | /// |
529 | /// * [Router]: see the "URL updates for web applications" section for more |
530 | /// information about route information reporting. |
531 | /// * [neglect]: which forces the [Router] to not create a new history entry |
532 | /// even if location does change. |
533 | static void navigate(BuildContext context, VoidCallback callback) { |
534 | final _RouterScope scope = context |
535 | .getElementForInheritedWidgetOfExactType<_RouterScope>()! |
536 | .widget as _RouterScope; |
537 | scope.routerState._setStateWithExplicitReportStatus(RouteInformationReportingType.navigate, callback); |
538 | } |
539 | |
540 | /// Forces the [Router] to run the [callback] without creating a new history |
541 | /// entry in the browser. |
542 | /// |
543 | /// The web application relies on the [Router] to report new route information |
544 | /// in order to create browser history entry. The [Router] will report them |
545 | /// automatically if it detects the [RouteInformation.uri] changes. |
546 | /// |
547 | /// Creating a new route history entry makes users feel they have visited a |
548 | /// new page, and the browser back button brings them back to previous history |
549 | /// entry. Use this method if you don't want the [Router] to create a new |
550 | /// route information even if it detects changes as a result of running the |
551 | /// [callback]. |
552 | /// |
553 | /// Using this method will still update the URL and state in current history |
554 | /// entry. |
555 | /// |
556 | /// See also: |
557 | /// |
558 | /// * [Router]: see the "URL updates for web applications" section for more |
559 | /// information about route information reporting. |
560 | /// * [navigate]: which forces the [Router] to create a new history entry |
561 | /// even if location does not change. |
562 | static void neglect(BuildContext context, VoidCallback callback) { |
563 | final _RouterScope scope = context |
564 | .getElementForInheritedWidgetOfExactType<_RouterScope>()! |
565 | .widget as _RouterScope; |
566 | scope.routerState._setStateWithExplicitReportStatus(RouteInformationReportingType.neglect, callback); |
567 | } |
568 | |
569 | @override |
570 | State<Router<T>> createState() => _RouterState<T>(); |
571 | } |
572 | |
573 | typedef _AsyncPassthrough<Q> = Future<Q> Function(Q); |
574 | typedef _RouteSetter<T> = Future<void> Function(T); |
575 | |
576 | /// The [Router]'s intention when it reports a new [RouteInformation] to the |
577 | /// [RouteInformationProvider]. |
578 | /// |
579 | /// See also: |
580 | /// |
581 | /// * [RouteInformationProvider.routerReportsNewRouteInformation]: which is |
582 | /// called by the router when it has a new route information to report. |
583 | enum RouteInformationReportingType { |
584 | /// Router does not have a specific intention. |
585 | /// |
586 | /// The router generates a new route information every time it detects route |
587 | /// information may have change due to a rebuild. This is the default type if |
588 | /// neither [Router.neglect] nor [Router.navigate] was used during the |
589 | /// rebuild. |
590 | none, |
591 | /// The accompanying [RouteInformation] were generated during a |
592 | /// [Router.neglect] call. |
593 | neglect, |
594 | /// The accompanying [RouteInformation] were generated during a |
595 | /// [Router.navigate] call. |
596 | navigate, |
597 | } |
598 | |
599 | class _RouterState<T> extends State<Router<T>> with RestorationMixin { |
600 | Object? _currentRouterTransaction; |
601 | RouteInformationReportingType? _currentIntentionToReport; |
602 | final _RestorableRouteInformation _routeInformation = _RestorableRouteInformation(); |
603 | late bool _routeParsePending; |
604 | |
605 | @override |
606 | String? get restorationId => widget.restorationScopeId; |
607 | |
608 | @override |
609 | void initState() { |
610 | super.initState(); |
611 | widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification); |
612 | widget.backButtonDispatcher?.addCallback(_handleBackButtonDispatcherNotification); |
613 | widget.routerDelegate.addListener(_handleRouterDelegateNotification); |
614 | } |
615 | |
616 | @override |
617 | void restoreState(RestorationBucket? oldBucket, bool initialRestore) { |
618 | registerForRestoration(_routeInformation, 'route'); |
619 | if (_routeInformation.value != null) { |
620 | assert(widget.routeInformationParser != null); |
621 | _processRouteInformation(_routeInformation.value!, () => widget.routerDelegate.setRestoredRoutePath); |
622 | } else if (widget.routeInformationProvider != null) { |
623 | _processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setInitialRoutePath); |
624 | } |
625 | } |
626 | |
627 | bool _routeInformationReportingTaskScheduled = false; |
628 | |
629 | void _scheduleRouteInformationReportingTask() { |
630 | if (_routeInformationReportingTaskScheduled || widget.routeInformationProvider == null) { |
631 | return; |
632 | } |
633 | assert(_currentIntentionToReport != null); |
634 | _routeInformationReportingTaskScheduled = true; |
635 | SchedulerBinding.instance.addPostFrameCallback( |
636 | _reportRouteInformation, |
637 | debugLabel: 'Router.reportRouteInfo', |
638 | ); |
639 | } |
640 | |
641 | void _reportRouteInformation(Duration timestamp) { |
642 | if (!mounted) { |
643 | return; |
644 | } |
645 | |
646 | assert(_routeInformationReportingTaskScheduled); |
647 | _routeInformationReportingTaskScheduled = false; |
648 | |
649 | if (_routeInformation.value != null) { |
650 | final RouteInformation currentRouteInformation = _routeInformation.value!; |
651 | assert(_currentIntentionToReport != null); |
652 | widget.routeInformationProvider!.routerReportsNewRouteInformation(currentRouteInformation, type: _currentIntentionToReport!); |
653 | } |
654 | _currentIntentionToReport = RouteInformationReportingType.none; |
655 | } |
656 | |
657 | RouteInformation? _retrieveNewRouteInformation() { |
658 | final T? configuration = widget.routerDelegate.currentConfiguration; |
659 | if (configuration == null) { |
660 | return null; |
661 | } |
662 | return widget.routeInformationParser?.restoreRouteInformation(configuration); |
663 | } |
664 | |
665 | void _setStateWithExplicitReportStatus( |
666 | RouteInformationReportingType status, |
667 | VoidCallback fn, |
668 | ) { |
669 | assert(status.index >= RouteInformationReportingType.neglect.index); |
670 | assert(() { |
671 | if (_currentIntentionToReport != null && |
672 | _currentIntentionToReport != RouteInformationReportingType.none && |
673 | _currentIntentionToReport != status) { |
674 | FlutterError.reportError( |
675 | const FlutterErrorDetails( |
676 | exception: |
677 | 'Both Router.navigate and Router.neglect have been called in this ' |
678 | 'build cycle, and the Router cannot decide whether to report the ' |
679 | 'route information. Please make sure only one of them is called ' |
680 | 'within the same build cycle.', |
681 | ), |
682 | ); |
683 | } |
684 | return true; |
685 | }()); |
686 | _currentIntentionToReport = status; |
687 | _scheduleRouteInformationReportingTask(); |
688 | fn(); |
689 | } |
690 | |
691 | void _maybeNeedToReportRouteInformation() { |
692 | _routeInformation.value = _retrieveNewRouteInformation(); |
693 | _currentIntentionToReport ??= RouteInformationReportingType.none; |
694 | _scheduleRouteInformationReportingTask(); |
695 | } |
696 | |
697 | @override |
698 | void didChangeDependencies() { |
699 | _routeParsePending = true; |
700 | super.didChangeDependencies(); |
701 | // The super.didChangeDependencies may have parsed the route information. |
702 | // This can happen if the didChangeDependencies is triggered by state |
703 | // restoration or first build. |
704 | final RouteInformation? currentRouteInformation = _routeInformation.value ?? widget.routeInformationProvider?.value; |
705 | if (currentRouteInformation != null && _routeParsePending) { |
706 | _processRouteInformation(currentRouteInformation, () => widget.routerDelegate.setNewRoutePath); |
707 | } |
708 | _routeParsePending = false; |
709 | _maybeNeedToReportRouteInformation(); |
710 | } |
711 | |
712 | @override |
713 | void didUpdateWidget(Router<T> oldWidget) { |
714 | super.didUpdateWidget(oldWidget); |
715 | if (widget.routeInformationProvider != oldWidget.routeInformationProvider || |
716 | widget.backButtonDispatcher != oldWidget.backButtonDispatcher || |
717 | widget.routeInformationParser != oldWidget.routeInformationParser || |
718 | widget.routerDelegate != oldWidget.routerDelegate) { |
719 | _currentRouterTransaction = Object(); |
720 | } |
721 | if (widget.routeInformationProvider != oldWidget.routeInformationProvider) { |
722 | oldWidget.routeInformationProvider?.removeListener(_handleRouteInformationProviderNotification); |
723 | widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification); |
724 | if (oldWidget.routeInformationProvider?.value != widget.routeInformationProvider?.value) { |
725 | _handleRouteInformationProviderNotification(); |
726 | } |
727 | } |
728 | if (widget.backButtonDispatcher != oldWidget.backButtonDispatcher) { |
729 | oldWidget.backButtonDispatcher?.removeCallback(_handleBackButtonDispatcherNotification); |
730 | widget.backButtonDispatcher?.addCallback(_handleBackButtonDispatcherNotification); |
731 | } |
732 | if (widget.routerDelegate != oldWidget.routerDelegate) { |
733 | oldWidget.routerDelegate.removeListener(_handleRouterDelegateNotification); |
734 | widget.routerDelegate.addListener(_handleRouterDelegateNotification); |
735 | _maybeNeedToReportRouteInformation(); |
736 | } |
737 | } |
738 | |
739 | @override |
740 | void dispose() { |
741 | _routeInformation.dispose(); |
742 | widget.routeInformationProvider?.removeListener(_handleRouteInformationProviderNotification); |
743 | widget.backButtonDispatcher?.removeCallback(_handleBackButtonDispatcherNotification); |
744 | widget.routerDelegate.removeListener(_handleRouterDelegateNotification); |
745 | _currentRouterTransaction = null; |
746 | super.dispose(); |
747 | } |
748 | |
749 | void _processRouteInformation(RouteInformation information, ValueGetter<_RouteSetter<T>> delegateRouteSetter) { |
750 | assert(_routeParsePending); |
751 | _routeParsePending = false; |
752 | _currentRouterTransaction = Object(); |
753 | widget.routeInformationParser! |
754 | .parseRouteInformationWithDependencies(information, context) |
755 | .then<void>(_processParsedRouteInformation(_currentRouterTransaction, delegateRouteSetter)); |
756 | } |
757 | |
758 | _RouteSetter<T> _processParsedRouteInformation(Object? transaction, ValueGetter<_RouteSetter<T>> delegateRouteSetter) { |
759 | return (T data) async { |
760 | if (_currentRouterTransaction != transaction) { |
761 | return; |
762 | } |
763 | await delegateRouteSetter()(data); |
764 | if (_currentRouterTransaction == transaction) { |
765 | _rebuild(); |
766 | } |
767 | }; |
768 | } |
769 | |
770 | void _handleRouteInformationProviderNotification() { |
771 | _routeParsePending = true; |
772 | _processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath); |
773 | } |
774 | |
775 | Future<bool> _handleBackButtonDispatcherNotification() { |
776 | _currentRouterTransaction = Object(); |
777 | return widget.routerDelegate |
778 | .popRoute() |
779 | .then<bool>(_handleRoutePopped(_currentRouterTransaction)); |
780 | } |
781 | |
782 | _AsyncPassthrough<bool> _handleRoutePopped(Object? transaction) { |
783 | return (bool data) { |
784 | if (transaction != _currentRouterTransaction) { |
785 | // A rebuilt was trigger from a different source. Returns true to |
786 | // prevent bubbling. |
787 | return SynchronousFuture<bool>(true); |
788 | } |
789 | _rebuild(); |
790 | return SynchronousFuture<bool>(data); |
791 | }; |
792 | } |
793 | |
794 | Future<void> _rebuild([void value]) { |
795 | setState(() {/* routerDelegate is ready to rebuild */}); |
796 | _maybeNeedToReportRouteInformation(); |
797 | return SynchronousFuture<void>(value); |
798 | } |
799 | |
800 | void _handleRouterDelegateNotification() { |
801 | setState(() {/* routerDelegate wants to rebuild */}); |
802 | _maybeNeedToReportRouteInformation(); |
803 | } |
804 | |
805 | @override |
806 | Widget build(BuildContext context) { |
807 | return UnmanagedRestorationScope( |
808 | bucket: bucket, |
809 | child: _RouterScope( |
810 | routeInformationProvider: widget.routeInformationProvider, |
811 | backButtonDispatcher: widget.backButtonDispatcher, |
812 | routeInformationParser: widget.routeInformationParser, |
813 | routerDelegate: widget.routerDelegate, |
814 | routerState: this, |
815 | child: Builder( |
816 | // Use a Builder so that the build method below will have a |
817 | // BuildContext that contains the _RouterScope. This also prevents |
818 | // dependencies look ups in routerDelegate from rebuilding Router |
819 | // widget that may result in re-parsing the route information. |
820 | builder: widget.routerDelegate.build, |
821 | ), |
822 | ), |
823 | ); |
824 | } |
825 | } |
826 | |
827 | class _RouterScope extends InheritedWidget { |
828 | const _RouterScope({ |
829 | required this.routeInformationProvider, |
830 | required this.backButtonDispatcher, |
831 | required this.routeInformationParser, |
832 | required this.routerDelegate, |
833 | required this.routerState, |
834 | required super.child, |
835 | }) : assert(routeInformationProvider == null || routeInformationParser != null); |
836 | |
837 | final ValueListenable<RouteInformation?>? routeInformationProvider; |
838 | final BackButtonDispatcher? backButtonDispatcher; |
839 | final RouteInformationParser<Object?>? routeInformationParser; |
840 | final RouterDelegate<Object?> routerDelegate; |
841 | final _RouterState<Object?> routerState; |
842 | |
843 | @override |
844 | bool updateShouldNotify(_RouterScope oldWidget) { |
845 | return routeInformationProvider != oldWidget.routeInformationProvider || |
846 | backButtonDispatcher != oldWidget.backButtonDispatcher || |
847 | routeInformationParser != oldWidget.routeInformationParser || |
848 | routerDelegate != oldWidget.routerDelegate || |
849 | routerState != oldWidget.routerState; |
850 | } |
851 | } |
852 | |
853 | /// A class that can be extended or mixed in that invokes a single callback, |
854 | /// which then returns a value. |
855 | /// |
856 | /// While multiple callbacks can be registered, when a notification is |
857 | /// dispatched there must be only a single callback. The return values of |
858 | /// multiple callbacks are not aggregated. |
859 | /// |
860 | /// `T` is the return value expected from the callback. |
861 | /// |
862 | /// See also: |
863 | /// |
864 | /// * [Listenable] and its subclasses, which provide a similar mechanism for |
865 | /// one-way signaling. |
866 | class _CallbackHookProvider<T> { |
867 | final ObserverList<ValueGetter<T>> _callbacks = ObserverList<ValueGetter<T>>(); |
868 | |
869 | /// Whether a callback is currently registered. |
870 | @protected |
871 | bool get hasCallbacks => _callbacks.isNotEmpty; |
872 | |
873 | /// Register the callback to be called when the object changes. |
874 | /// |
875 | /// If other callbacks have already been registered, they must be removed |
876 | /// (with [removeCallback]) before the callback is next called. |
877 | void addCallback(ValueGetter<T> callback) => _callbacks.add(callback); |
878 | |
879 | /// Remove a previously registered callback. |
880 | /// |
881 | /// If the given callback is not registered, the call is ignored. |
882 | void removeCallback(ValueGetter<T> callback) => _callbacks.remove(callback); |
883 | |
884 | /// Calls the (single) registered callback and returns its result. |
885 | /// |
886 | /// If no callback is registered, or if the callback throws, returns |
887 | /// `defaultValue`. |
888 | /// |
889 | /// Call this method whenever the callback is to be invoked. If there is more |
890 | /// than one callback registered, this method will throw a [StateError]. |
891 | /// |
892 | /// Exceptions thrown by callbacks will be caught and reported using |
893 | /// [FlutterError.reportError]. |
894 | @protected |
895 | @pragma('vm:notify-debugger-on-exception') |
896 | T invokeCallback(T defaultValue) { |
897 | if (_callbacks.isEmpty) { |
898 | return defaultValue; |
899 | } |
900 | try { |
901 | return _callbacks.single(); |
902 | } catch (exception, stack) { |
903 | FlutterError.reportError(FlutterErrorDetails( |
904 | exception: exception, |
905 | stack: stack, |
906 | library: 'widget library', |
907 | context: ErrorDescription('while invoking the callback for$runtimeType '), |
908 | informationCollector: () => <DiagnosticsNode>[ |
909 | DiagnosticsProperty<_CallbackHookProvider<T>>( |
910 | 'The$runtimeType that invoked the callback was', |
911 | this, |
912 | style: DiagnosticsTreeStyle.errorProperty, |
913 | ), |
914 | ], |
915 | )); |
916 | return defaultValue; |
917 | } |
918 | } |
919 | } |
920 | |
921 | /// Report to a [Router] when the user taps the back button on platforms that |
922 | /// support back buttons (such as Android). |
923 | /// |
924 | /// When [Router] widgets are nested, consider using a |
925 | /// [ChildBackButtonDispatcher], passing it the parent [BackButtonDispatcher], |
926 | /// so that the back button requests get dispatched to the appropriate [Router]. |
927 | /// To make this work properly, it's important that whenever a [Router] thinks |
928 | /// it should get the back button messages (e.g. after the user taps inside it), |
929 | /// it calls [takePriority] on its [BackButtonDispatcher] (or |
930 | /// [ChildBackButtonDispatcher]) instance. |
931 | /// |
932 | /// The class takes a single callback, which must return a [Future<bool>]. The |
933 | /// callback's semantics match [WidgetsBindingObserver.didPopRoute]'s, namely, |
934 | /// the callback should return a future that completes to true if it can handle |
935 | /// the pop request, and a future that completes to false otherwise. |
936 | abstract class BackButtonDispatcher extends _CallbackHookProvider<Future<bool>> { |
937 | late final LinkedHashSet<ChildBackButtonDispatcher> _children = |
938 | <ChildBackButtonDispatcher>{} as LinkedHashSet<ChildBackButtonDispatcher>; |
939 | |
940 | @override |
941 | bool get hasCallbacks => super.hasCallbacks || (_children.isNotEmpty); |
942 | |
943 | /// Handles a pop route request. |
944 | /// |
945 | /// This method prioritizes the children list in reverse order and calls |
946 | /// [ChildBackButtonDispatcher.notifiedByParent] on them. If any of them |
947 | /// handles the request (by returning a future with true), it exits this |
948 | /// method by returning this future. Otherwise, it keeps moving on to the next |
949 | /// child until a child handles the request. If none of the children handles |
950 | /// the request, this back button dispatcher will then try to handle the request |
951 | /// by itself. This back button dispatcher handles the request by notifying the |
952 | /// router which in turn calls the [RouterDelegate.popRoute] and returns its |
953 | /// result. |
954 | /// |
955 | /// To decide whether this back button dispatcher will handle the pop route |
956 | /// request, you can override the [RouterDelegate.popRoute] of the router |
957 | /// delegate you pass into the router with this back button dispatcher to |
958 | /// return a future of true or false. |
959 | @override |
960 | Future<bool> invokeCallback(Future<bool> defaultValue) { |
961 | if (_children.isNotEmpty) { |
962 | final List<ChildBackButtonDispatcher> children = _children.toList(); |
963 | int childIndex = children.length - 1; |
964 | |
965 | Future<bool> notifyNextChild(bool result) { |
966 | // If the previous child handles the callback, we return the result. |
967 | if (result) { |
968 | return SynchronousFuture<bool>(result); |
969 | } |
970 | // If the previous child did not handle the callback, we ask the next |
971 | // child to handle the it. |
972 | if (childIndex > 0) { |
973 | childIndex -= 1; |
974 | return children[childIndex] |
975 | .notifiedByParent(defaultValue) |
976 | .then<bool>(notifyNextChild); |
977 | } |
978 | // If none of the child handles the callback, the parent will then handle it. |
979 | return super.invokeCallback(defaultValue); |
980 | } |
981 | |
982 | return children[childIndex] |
983 | .notifiedByParent(defaultValue) |
984 | .then<bool>(notifyNextChild); |
985 | } |
986 | return super.invokeCallback(defaultValue); |
987 | } |
988 | |
989 | /// Creates a [ChildBackButtonDispatcher] that is a direct descendant of this |
990 | /// back button dispatcher. |
991 | /// |
992 | /// To participate in handling the pop route request, call the [takePriority] |
993 | /// on the [ChildBackButtonDispatcher] created from this method. |
994 | /// |
995 | /// When the pop route request is handled by this back button dispatcher, it |
996 | /// propagate the request to its direct descendants that have called the |
997 | /// [takePriority] method. If there are multiple candidates, the latest one |
998 | /// that called the [takePriority] wins the right to handle the request. If |
999 | /// the latest one does not handle the request (by returning a future of |
1000 | /// false in [ChildBackButtonDispatcher.notifiedByParent]), the second latest |
1001 | /// one will then have the right to handle the request. This dispatcher |
1002 | /// continues finding the next candidate until there are no more candidates |
1003 | /// and finally handles the request itself. |
1004 | ChildBackButtonDispatcher createChildBackButtonDispatcher() { |
1005 | return ChildBackButtonDispatcher(this); |
1006 | } |
1007 | |
1008 | /// Make this [BackButtonDispatcher] take priority among its peers. |
1009 | /// |
1010 | /// This has no effect when a [BackButtonDispatcher] has no parents and no |
1011 | /// children. If a [BackButtonDispatcher] does have parents or children, |
1012 | /// however, it causes this object to be the one to dispatch the notification |
1013 | /// when the parent would normally notify its callback. |
1014 | /// |
1015 | /// The [BackButtonDispatcher] must have a listener registered before it can |
1016 | /// be told to take priority. |
1017 | void takePriority() => _children.clear(); |
1018 | |
1019 | /// Mark the given child as taking priority over this object and the other |
1020 | /// children. |
1021 | /// |
1022 | /// This causes [invokeCallback] to defer to the given child instead of |
1023 | /// calling this object's callback. |
1024 | /// |
1025 | /// Children are stored in a list, so that if the current child is removed |
1026 | /// using [forget], a previous child will return to take its place. When |
1027 | /// [takePriority] is called, the list is cleared. |
1028 | /// |
1029 | /// Calling this again without first calling [forget] moves the child back to |
1030 | /// the head of the list. |
1031 | /// |
1032 | /// The [BackButtonDispatcher] must have a listener registered before it can |
1033 | /// be told to defer to a child. |
1034 | void deferTo(ChildBackButtonDispatcher child) { |
1035 | assert(hasCallbacks); |
1036 | _children.remove(child); // child may or may not be in the set already |
1037 | _children.add(child); |
1038 | } |
1039 | |
1040 | /// Causes the given child to be removed from the list of children to which |
1041 | /// this object might defer, as if [deferTo] had never been called for that |
1042 | /// child. |
1043 | /// |
1044 | /// This should only be called once per child, even if [deferTo] was called |
1045 | /// multiple times for that child. |
1046 | /// |
1047 | /// If no children are left in the list, this object will stop deferring to |
1048 | /// its children. (This is not the same as calling [takePriority], since, if |
1049 | /// this object itself is a [ChildBackButtonDispatcher], [takePriority] would |
1050 | /// additionally attempt to claim priority from its parent, whereas removing |
1051 | /// the last child does not.) |
1052 | void forget(ChildBackButtonDispatcher child) => _children.remove(child); |
1053 | } |
1054 | |
1055 | /// The default implementation of back button dispatcher for the root router. |
1056 | /// |
1057 | /// This dispatcher listens to platform pop route notifications. When the |
1058 | /// platform wants to pop the current route, this dispatcher calls the |
1059 | /// [BackButtonDispatcher.invokeCallback] method to handle the request. |
1060 | class RootBackButtonDispatcher extends BackButtonDispatcher with WidgetsBindingObserver { |
1061 | /// Create a root back button dispatcher. |
1062 | RootBackButtonDispatcher(); |
1063 | |
1064 | @override |
1065 | void addCallback(ValueGetter<Future<bool>> callback) { |
1066 | if (!hasCallbacks) { |
1067 | WidgetsBinding.instance.addObserver(this); |
1068 | } |
1069 | super.addCallback(callback); |
1070 | } |
1071 | |
1072 | @override |
1073 | void removeCallback(ValueGetter<Future<bool>> callback) { |
1074 | super.removeCallback(callback); |
1075 | if (!hasCallbacks) { |
1076 | WidgetsBinding.instance.removeObserver(this); |
1077 | } |
1078 | } |
1079 | |
1080 | @override |
1081 | Future<bool> didPopRoute() => invokeCallback(Future<bool>.value(false)); |
1082 | } |
1083 | |
1084 | /// A variant of [BackButtonDispatcher] which listens to notifications from a |
1085 | /// parent back button dispatcher, and can take priority from its parent for the |
1086 | /// handling of such notifications. |
1087 | /// |
1088 | /// Useful when [Router]s are being nested within each other. |
1089 | /// |
1090 | /// Use [Router.of] to obtain a reference to the nearest ancestor [Router], from |
1091 | /// which the [Router.backButtonDispatcher] can be found, and then used as the |
1092 | /// [parent] of the [ChildBackButtonDispatcher]. |
1093 | class ChildBackButtonDispatcher extends BackButtonDispatcher { |
1094 | /// Creates a back button dispatcher that acts as the child of another. |
1095 | ChildBackButtonDispatcher(this.parent); |
1096 | |
1097 | /// The back button dispatcher that this object will attempt to take priority |
1098 | /// over when [takePriority] is called. |
1099 | /// |
1100 | /// The parent must have a listener registered before this child object can |
1101 | /// have its [takePriority] or [deferTo] methods used. |
1102 | final BackButtonDispatcher parent; |
1103 | |
1104 | /// The parent of this child back button dispatcher decide to let this |
1105 | /// child to handle the invoke the callback request in |
1106 | /// [BackButtonDispatcher.invokeCallback]. |
1107 | /// |
1108 | /// Return a boolean future with true if this child will handle the request; |
1109 | /// otherwise, return a boolean future with false. |
1110 | @protected |
1111 | Future<bool> notifiedByParent(Future<bool> defaultValue) { |
1112 | return invokeCallback(defaultValue); |
1113 | } |
1114 | |
1115 | @override |
1116 | void takePriority() { |
1117 | parent.deferTo(this); |
1118 | super.takePriority(); |
1119 | } |
1120 | |
1121 | @override |
1122 | void deferTo(ChildBackButtonDispatcher child) { |
1123 | assert(hasCallbacks); |
1124 | parent.deferTo(this); |
1125 | super.deferTo(child); |
1126 | } |
1127 | |
1128 | @override |
1129 | void removeCallback(ValueGetter<Future<bool>> callback) { |
1130 | super.removeCallback(callback); |
1131 | if (!hasCallbacks) { |
1132 | parent.forget(this); |
1133 | } |
1134 | } |
1135 | } |
1136 | |
1137 | /// A convenience widget that registers a callback for when the back button is pressed. |
1138 | /// |
1139 | /// In order to use this widget, there must be an ancestor [Router] widget in the tree |
1140 | /// that has a [RootBackButtonDispatcher]. e.g. The [Router] widget created by the |
1141 | /// [MaterialApp.router] has a built-in [RootBackButtonDispatcher] by default. |
1142 | /// |
1143 | /// It only applies to platforms that accept back button clicks, such as Android. |
1144 | /// |
1145 | /// It can be useful for scenarios, in which you create a different state in your |
1146 | /// screen but don't want to use a new page for that. |
1147 | class BackButtonListener extends StatefulWidget { |
1148 | /// Creates a BackButtonListener widget . |
1149 | const BackButtonListener({ |
1150 | super.key, |
1151 | required this.child, |
1152 | required this.onBackButtonPressed, |
1153 | }); |
1154 | |
1155 | /// The widget below this widget in the tree. |
1156 | final Widget child; |
1157 | |
1158 | /// The callback function that will be called when the back button is pressed. |
1159 | /// |
1160 | /// It must return a boolean future with true if this child will handle the request; |
1161 | /// otherwise, return a boolean future with false. |
1162 | final ValueGetter<Future<bool>> onBackButtonPressed; |
1163 | |
1164 | @override |
1165 | State<BackButtonListener> createState() => _BackButtonListenerState(); |
1166 | } |
1167 | |
1168 | class _BackButtonListenerState extends State<BackButtonListener> { |
1169 | BackButtonDispatcher? dispatcher; |
1170 | |
1171 | @override |
1172 | void didChangeDependencies() { |
1173 | dispatcher?.removeCallback(widget.onBackButtonPressed); |
1174 | |
1175 | final BackButtonDispatcher? rootBackDispatcher = Router.of(context).backButtonDispatcher; |
1176 | assert(rootBackDispatcher != null, 'The parent router must have a backButtonDispatcher to use this widget'); |
1177 | |
1178 | dispatcher = rootBackDispatcher!.createChildBackButtonDispatcher() |
1179 | ..addCallback(widget.onBackButtonPressed) |
1180 | ..takePriority(); |
1181 | super.didChangeDependencies(); |
1182 | } |
1183 | |
1184 | @override |
1185 | void didUpdateWidget(covariant BackButtonListener oldWidget) { |
1186 | super.didUpdateWidget(oldWidget); |
1187 | if (oldWidget.onBackButtonPressed != widget.onBackButtonPressed) { |
1188 | dispatcher?.removeCallback(oldWidget.onBackButtonPressed); |
1189 | dispatcher?.addCallback(widget.onBackButtonPressed); |
1190 | dispatcher?.takePriority(); |
1191 | } |
1192 | } |
1193 | |
1194 | @override |
1195 | void dispose() { |
1196 | dispatcher?.removeCallback(widget.onBackButtonPressed); |
1197 | super.dispose(); |
1198 | } |
1199 | |
1200 | @override |
1201 | Widget build(BuildContext context) => widget.child; |
1202 | } |
1203 | |
1204 | /// A delegate that is used by the [Router] widget to parse a route information |
1205 | /// into a configuration of type T. |
1206 | /// |
1207 | /// This delegate is used when the [Router] widget is first built with initial |
1208 | /// route information from [Router.routeInformationProvider] and any subsequent |
1209 | /// new route notifications from it. The [Router] widget calls the [parseRouteInformation] |
1210 | /// with the route information from [Router.routeInformationProvider]. |
1211 | /// |
1212 | /// One of the [parseRouteInformation] or |
1213 | /// [parseRouteInformationWithDependencies] must be implemented, otherwise a |
1214 | /// runtime error will be thrown. |
1215 | abstract class RouteInformationParser<T> { |
1216 | /// Abstract const constructor. This constructor enables subclasses to provide |
1217 | /// const constructors so that they can be used in const expressions. |
1218 | const RouteInformationParser(); |
1219 | |
1220 | /// {@template flutter.widgets.RouteInformationParser.parseRouteInformation} |
1221 | /// Converts the given route information into parsed data to pass to a |
1222 | /// [RouterDelegate]. |
1223 | /// |
1224 | /// The method should return a future which completes when the parsing is |
1225 | /// complete. The parsing may be asynchronous if, e.g., the parser needs to |
1226 | /// communicate with the OEM thread to obtain additional data about the route. |
1227 | /// |
1228 | /// Consider using a [SynchronousFuture] if the result can be computed |
1229 | /// synchronously, so that the [Router] does not need to wait for the next |
1230 | /// microtask to pass the data to the [RouterDelegate]. |
1231 | /// {@endtemplate} |
1232 | /// |
1233 | /// One can implement [parseRouteInformationWithDependencies] instead if |
1234 | /// the parsing depends on other dependencies from the [BuildContext]. |
1235 | Future<T> parseRouteInformation(RouteInformation routeInformation) { |
1236 | throw UnimplementedError( |
1237 | 'One of the parseRouteInformation or ' |
1238 | 'parseRouteInformationWithDependencies must be implemented' |
1239 | ); |
1240 | } |
1241 | |
1242 | /// {@macro flutter.widgets.RouteInformationParser.parseRouteInformation} |
1243 | /// |
1244 | /// The input [BuildContext] can be used for looking up [InheritedWidget]s |
1245 | /// If one uses [BuildContext.dependOnInheritedWidgetOfExactType], a |
1246 | /// dependency will be created. The [Router] will re-parse the |
1247 | /// [RouteInformation] from its [RouteInformationProvider] if the dependency |
1248 | /// notifies its listeners. |
1249 | /// |
1250 | /// One can also use [BuildContext.getElementForInheritedWidgetOfExactType] to |
1251 | /// look up [InheritedWidget]s without creating dependencies. |
1252 | Future<T> parseRouteInformationWithDependencies(RouteInformation routeInformation, BuildContext context) { |
1253 | return parseRouteInformation(routeInformation); |
1254 | } |
1255 | |
1256 | /// Restore the route information from the given configuration. |
1257 | /// |
1258 | /// This may return null, in which case the browser history will not be |
1259 | /// updated and state restoration is disabled. See [Router]'s documentation |
1260 | /// for details. |
1261 | /// |
1262 | /// The [parseRouteInformation] method must produce an equivalent |
1263 | /// configuration when passed this method's return value. |
1264 | RouteInformation? restoreRouteInformation(T configuration) => null; |
1265 | } |
1266 | |
1267 | /// A delegate that is used by the [Router] widget to build and configure a |
1268 | /// navigating widget. |
1269 | /// |
1270 | /// This delegate is the core piece of the [Router] widget. It responds to |
1271 | /// push route and pop route intents from the engine and notifies the [Router] |
1272 | /// to rebuild. It also acts as a builder for the [Router] widget and builds a |
1273 | /// navigating widget, typically a [Navigator], when the [Router] widget |
1274 | /// builds. |
1275 | /// |
1276 | /// When the engine pushes a new route, the route information is parsed by the |
1277 | /// [RouteInformationParser] to produce a configuration of type T. The router |
1278 | /// delegate receives the configuration through [setInitialRoutePath] or |
1279 | /// [setNewRoutePath] to configure itself and builds the latest navigating |
1280 | /// widget when asked ([build]). |
1281 | /// |
1282 | /// When implementing subclasses, consider defining a [Listenable] app state object to be |
1283 | /// used for building the navigating widget. The router delegate would update |
1284 | /// the app state accordingly and notify its own listeners when the app state has |
1285 | /// changed and when it receive route related engine intents (e.g. |
1286 | /// [setNewRoutePath], [setInitialRoutePath], or [popRoute]). |
1287 | /// |
1288 | /// All subclass must implement [setNewRoutePath], [popRoute], and [build]. |
1289 | /// |
1290 | /// ## State Restoration |
1291 | /// |
1292 | /// If the [Router] owning this delegate is configured for state restoration, it |
1293 | /// will persist and restore the configuration of this [RouterDelegate] using |
1294 | /// the following mechanism: Before the app is killed by the operating system, |
1295 | /// the value of [currentConfiguration] is serialized out and persisted. After |
1296 | /// the app has restarted, the value is deserialized and passed back to the |
1297 | /// [RouterDelegate] via a call to [setRestoredRoutePath] (which by default just |
1298 | /// calls [setNewRoutePath]). It is the responsibility of the [RouterDelegate] |
1299 | /// to use the configuration information provided to restore its internal state. |
1300 | /// |
1301 | /// See also: |
1302 | /// |
1303 | /// * [RouteInformationParser], which is responsible for parsing the route |
1304 | /// information to a configuration before passing in to router delegate. |
1305 | /// * [Router], which is the widget that wires all the delegates together to |
1306 | /// provide a fully functional routing solution. |
1307 | abstract class RouterDelegate<T> extends Listenable { |
1308 | /// Called by the [Router] at startup with the structure that the |
1309 | /// [RouteInformationParser] obtained from parsing the initial route. |
1310 | /// |
1311 | /// This should configure the [RouterDelegate] so that when [build] is |
1312 | /// invoked, it will create a widget tree that matches the initial route. |
1313 | /// |
1314 | /// By default, this method forwards the [configuration] to [setNewRoutePath]. |
1315 | /// |
1316 | /// Consider using a [SynchronousFuture] if the result can be computed |
1317 | /// synchronously, so that the [Router] does not need to wait for the next |
1318 | /// microtask to schedule a build. |
1319 | /// |
1320 | /// See also: |
1321 | /// |
1322 | /// * [setRestoredRoutePath], which is called instead of this method during |
1323 | /// state restoration. |
1324 | Future<void> setInitialRoutePath(T configuration) { |
1325 | return setNewRoutePath(configuration); |
1326 | } |
1327 | |
1328 | /// Called by the [Router] during state restoration. |
1329 | /// |
1330 | /// When the [Router] is configured for state restoration, it will persist |
1331 | /// the value of [currentConfiguration] during state serialization. During |
1332 | /// state restoration, the [Router] calls this method (instead of |
1333 | /// [setInitialRoutePath]) to pass the previous configuration back to the |
1334 | /// delegate. It is the responsibility of the delegate to restore its internal |
1335 | /// state based on the provided configuration. |
1336 | /// |
1337 | /// By default, this method forwards the `configuration` to [setNewRoutePath]. |
1338 | Future<void> setRestoredRoutePath(T configuration) { |
1339 | return setNewRoutePath(configuration); |
1340 | } |
1341 | |
1342 | /// Called by the [Router] when the [Router.routeInformationProvider] reports that a |
1343 | /// new route has been pushed to the application by the operating system. |
1344 | /// |
1345 | /// Consider using a [SynchronousFuture] if the result can be computed |
1346 | /// synchronously, so that the [Router] does not need to wait for the next |
1347 | /// microtask to schedule a build. |
1348 | Future<void> setNewRoutePath(T configuration); |
1349 | |
1350 | /// Called by the [Router] when the [Router.backButtonDispatcher] reports that |
1351 | /// the operating system is requesting that the current route be popped. |
1352 | /// |
1353 | /// The method should return a boolean [Future] to indicate whether this |
1354 | /// delegate handles the request. Returning false will cause the entire app |
1355 | /// to be popped. |
1356 | /// |
1357 | /// Consider using a [SynchronousFuture] if the result can be computed |
1358 | /// synchronously, so that the [Router] does not need to wait for the next |
1359 | /// microtask to schedule a build. |
1360 | Future<bool> popRoute(); |
1361 | |
1362 | /// Called by the [Router] when it detects a route information may have |
1363 | /// changed as a result of rebuild. |
1364 | /// |
1365 | /// If this getter returns non-null, the [Router] will start to report new |
1366 | /// route information back to the engine. In web applications, the new |
1367 | /// route information is used for populating browser history in order to |
1368 | /// support the forward and the backward buttons. |
1369 | /// |
1370 | /// When overriding this method, the configuration returned by this getter |
1371 | /// must be able to construct the current app state and build the widget |
1372 | /// with the same configuration in the [build] method if it is passed back |
1373 | /// to the [setNewRoutePath]. Otherwise, the browser backward and forward |
1374 | /// buttons will not work properly. |
1375 | /// |
1376 | /// By default, this getter returns null, which prevents the [Router] from |
1377 | /// reporting the route information. To opt in, a subclass can override this |
1378 | /// getter to return the current configuration. |
1379 | /// |
1380 | /// At most one [Router] can opt in to route information reporting. Typically, |
1381 | /// only the top-most [Router] created by [WidgetsApp.router] should opt for |
1382 | /// route information reporting. |
1383 | /// |
1384 | /// ## State Restoration |
1385 | /// |
1386 | /// This getter is also used by the [Router] to implement state restoration. |
1387 | /// During state serialization, the [Router] will persist the current |
1388 | /// configuration and during state restoration pass it back to the delegate |
1389 | /// by calling [setRestoredRoutePath]. |
1390 | T? get currentConfiguration => null; |
1391 | |
1392 | /// Called by the [Router] to obtain the widget tree that represents the |
1393 | /// current state. |
1394 | /// |
1395 | /// This is called whenever the [Future]s returned by [setInitialRoutePath], |
1396 | /// [setNewRoutePath], or [setRestoredRoutePath] complete as well as when this |
1397 | /// notifies its clients (see the [Listenable] interface, which this interface |
1398 | /// includes). In addition, it may be called at other times. It is important, |
1399 | /// therefore, that the methods above do not update the state that the [build] |
1400 | /// method uses before they complete their respective futures. |
1401 | /// |
1402 | /// Typically this method returns a suitably-configured [Navigator]. If you do |
1403 | /// plan to create a navigator, consider using the |
1404 | /// [PopNavigatorRouterDelegateMixin]. If state restoration is enabled for the |
1405 | /// [Router] using this delegate, consider providing a non-null |
1406 | /// [Navigator.restorationScopeId] to the [Navigator] returned by this method. |
1407 | /// |
1408 | /// This method must not return null. |
1409 | /// |
1410 | /// The `context` is the [Router]'s build context. |
1411 | Widget build(BuildContext context); |
1412 | } |
1413 | |
1414 | /// A route information provider that provides route information for the |
1415 | /// [Router] widget |
1416 | /// |
1417 | /// This provider is responsible for handing the route information through [value] |
1418 | /// getter and notifies listeners, typically the [Router] widget, when a new |
1419 | /// route information is available. |
1420 | /// |
1421 | /// When the router opts for route information reporting (by overriding the |
1422 | /// [RouterDelegate.currentConfiguration] to return non-null), override the |
1423 | /// [routerReportsNewRouteInformation] method to process the route information. |
1424 | /// |
1425 | /// See also: |
1426 | /// |
1427 | /// * [PlatformRouteInformationProvider], which wires up the itself with the |
1428 | /// [WidgetsBindingObserver.didPushRoute] to propagate platform push route |
1429 | /// intent to the [Router] widget, as well as reports new route information |
1430 | /// from the [Router] back to the engine by overriding the |
1431 | /// [routerReportsNewRouteInformation]. |
1432 | abstract class RouteInformationProvider extends ValueListenable<RouteInformation> { |
1433 | /// A callback called when the [Router] widget reports new route information |
1434 | /// |
1435 | /// The subclasses can override this method to update theirs values or trigger |
1436 | /// other side effects. For example, the [PlatformRouteInformationProvider] |
1437 | /// overrides this method to report the route information back to the engine. |
1438 | /// |
1439 | /// The `routeInformation` is the new route information generated by the |
1440 | /// Router rebuild, and it can be the same or different from the |
1441 | /// [value]. |
1442 | /// |
1443 | /// The `type` denotes the [Router]'s intention when it reports this |
1444 | /// `routeInformation`. It is useful when deciding how to update the internal |
1445 | /// state of [RouteInformationProvider] subclass with the `routeInformation`. |
1446 | /// For example, [PlatformRouteInformationProvider] uses this property to |
1447 | /// decide whether to push or replace the browser history entry with the new |
1448 | /// `routeInformation`. |
1449 | /// |
1450 | /// For more information on how [Router] determines a navigation event, see |
1451 | /// the "URL updates for web applications" section in the [Router] |
1452 | /// documentation. |
1453 | void routerReportsNewRouteInformation(RouteInformation routeInformation, {RouteInformationReportingType type = RouteInformationReportingType.none}) {} |
1454 | } |
1455 | |
1456 | /// The route information provider that propagates the platform route information changes. |
1457 | /// |
1458 | /// This provider also reports the new route information from the [Router] widget |
1459 | /// back to engine using message channel method, the |
1460 | /// [SystemNavigator.routeInformationUpdated]. |
1461 | /// |
1462 | /// Each time [SystemNavigator.routeInformationUpdated] is called, the |
1463 | /// [SystemNavigator.selectMultiEntryHistory] method is also called. This |
1464 | /// overrides the initialization behavior of |
1465 | /// [Navigator.reportsRouteUpdateToEngine]. |
1466 | class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier { |
1467 | /// Create a platform route information provider. |
1468 | /// |
1469 | /// Use the [initialRouteInformation] to set the default route information for this |
1470 | /// provider. |
1471 | PlatformRouteInformationProvider({ |
1472 | required RouteInformation initialRouteInformation, |
1473 | }) : _value = initialRouteInformation { |
1474 | if (kFlutterMemoryAllocationsEnabled) { |
1475 | ChangeNotifier.maybeDispatchObjectCreation(this); |
1476 | } |
1477 | } |
1478 | |
1479 | static bool _equals(Uri a, Uri b) { |
1480 | return a.path == b.path |
1481 | && a.fragment == b.fragment |
1482 | && const DeepCollectionEquality.unordered().equals(a.queryParametersAll, b.queryParametersAll); |
1483 | } |
1484 | |
1485 | @override |
1486 | void routerReportsNewRouteInformation(RouteInformation routeInformation, {RouteInformationReportingType type = RouteInformationReportingType.none}) { |
1487 | SystemNavigator.selectMultiEntryHistory(); |
1488 | SystemNavigator.routeInformationUpdated( |
1489 | uri: routeInformation.uri, |
1490 | state: routeInformation.state, |
1491 | replace: switch (type) { |
1492 | RouteInformationReportingType.neglect => true, |
1493 | RouteInformationReportingType.navigate => false, |
1494 | RouteInformationReportingType.none => _equals(_valueInEngine.uri, routeInformation.uri), |
1495 | }, |
1496 | ); |
1497 | _value = routeInformation; |
1498 | _valueInEngine = routeInformation; |
1499 | } |
1500 | |
1501 | @override |
1502 | RouteInformation get value => _value; |
1503 | RouteInformation _value; |
1504 | |
1505 | RouteInformation _valueInEngine = RouteInformation(uri: Uri.parse(WidgetsBinding.instance.platformDispatcher.defaultRouteName)); |
1506 | |
1507 | void _platformReportsNewRouteInformation(RouteInformation routeInformation) { |
1508 | if (_value == routeInformation) { |
1509 | return; |
1510 | } |
1511 | _value = routeInformation; |
1512 | _valueInEngine = routeInformation; |
1513 | notifyListeners(); |
1514 | } |
1515 | |
1516 | @override |
1517 | void addListener(VoidCallback listener) { |
1518 | if (!hasListeners) { |
1519 | WidgetsBinding.instance.addObserver(this); |
1520 | } |
1521 | super.addListener(listener); |
1522 | } |
1523 | |
1524 | @override |
1525 | void removeListener(VoidCallback listener) { |
1526 | super.removeListener(listener); |
1527 | if (!hasListeners) { |
1528 | WidgetsBinding.instance.removeObserver(this); |
1529 | } |
1530 | } |
1531 | |
1532 | @override |
1533 | void dispose() { |
1534 | // In practice, this will rarely be called. We assume that the listeners |
1535 | // will be added and removed in a coherent fashion such that when the object |
1536 | // is no longer being used, there's no listener, and so it will get garbage |
1537 | // collected. |
1538 | if (hasListeners) { |
1539 | WidgetsBinding.instance.removeObserver(this); |
1540 | } |
1541 | super.dispose(); |
1542 | } |
1543 | |
1544 | @override |
1545 | Future<bool> didPushRouteInformation(RouteInformation routeInformation) async { |
1546 | assert(hasListeners); |
1547 | _platformReportsNewRouteInformation(routeInformation); |
1548 | return true; |
1549 | } |
1550 | } |
1551 | |
1552 | /// A mixin that wires [RouterDelegate.popRoute] to the [Navigator] it builds. |
1553 | /// |
1554 | /// This mixin calls [Navigator.maybePop] when it receives an Android back |
1555 | /// button intent through the [RouterDelegate.popRoute]. Using this mixin |
1556 | /// guarantees that the back button still respects pageless routes in the |
1557 | /// navigator. |
1558 | /// |
1559 | /// Only use this mixin if you plan to build a navigator in the |
1560 | /// [RouterDelegate.build]. |
1561 | mixin PopNavigatorRouterDelegateMixin<T> on RouterDelegate<T> { |
1562 | /// The key used for retrieving the current navigator. |
1563 | /// |
1564 | /// When using this mixin, be sure to use this key to create the navigator. |
1565 | GlobalKey<NavigatorState>? get navigatorKey; |
1566 | |
1567 | @override |
1568 | Future<bool> popRoute() { |
1569 | final NavigatorState? navigator = navigatorKey?.currentState; |
1570 | return navigator?.maybePop() ?? SynchronousFuture<bool>(false); |
1571 | } |
1572 | } |
1573 | |
1574 | class _RestorableRouteInformation extends RestorableValue<RouteInformation?> { |
1575 | @override |
1576 | RouteInformation? createDefaultValue() => null; |
1577 | |
1578 | @override |
1579 | void didUpdateValue(RouteInformation? oldValue) { |
1580 | notifyListeners(); |
1581 | } |
1582 | |
1583 | @override |
1584 | RouteInformation? fromPrimitives(Object? data) { |
1585 | if (data == null) { |
1586 | return null; |
1587 | } |
1588 | assert(data is List<Object?> && data.length == 2); |
1589 | final List<Object?> castedData = data as List<Object?>; |
1590 | final String? uri = castedData.first as String?; |
1591 | if (uri == null) { |
1592 | return null; |
1593 | } |
1594 | return RouteInformation(uri: Uri.parse(uri), state: castedData.last); |
1595 | } |
1596 | |
1597 | @override |
1598 | Object? toPrimitives() { |
1599 | return value == null ? null : <Object?>[value!.uri.toString(), value!.state]; |
1600 | } |
1601 | } |
1602 |
Definitions
- RouteInformation
- RouteInformation
- location
- uri
- RouterConfig
- RouterConfig
- Router
- Router
- withConfig
- of
- maybeOf
- navigate
- neglect
- createState
- RouteInformationReportingType
- _RouterState
- restorationId
- initState
- restoreState
- _scheduleRouteInformationReportingTask
- _reportRouteInformation
- _retrieveNewRouteInformation
- _setStateWithExplicitReportStatus
- _maybeNeedToReportRouteInformation
- didChangeDependencies
- didUpdateWidget
- dispose
- _processRouteInformation
- _processParsedRouteInformation
- _handleRouteInformationProviderNotification
- _handleBackButtonDispatcherNotification
- _handleRoutePopped
- _rebuild
- _handleRouterDelegateNotification
- build
- _RouterScope
- _RouterScope
- updateShouldNotify
- _CallbackHookProvider
- hasCallbacks
- addCallback
- removeCallback
- invokeCallback
- BackButtonDispatcher
- hasCallbacks
- invokeCallback
- notifyNextChild
- createChildBackButtonDispatcher
- takePriority
- deferTo
- forget
- RootBackButtonDispatcher
- RootBackButtonDispatcher
- addCallback
- removeCallback
- didPopRoute
- ChildBackButtonDispatcher
- ChildBackButtonDispatcher
- notifiedByParent
- takePriority
- deferTo
- removeCallback
- BackButtonListener
- BackButtonListener
- createState
- _BackButtonListenerState
- didChangeDependencies
- didUpdateWidget
- dispose
- build
- RouteInformationParser
- RouteInformationParser
- parseRouteInformation
- parseRouteInformationWithDependencies
- restoreRouteInformation
- RouterDelegate
- setInitialRoutePath
- setRestoredRoutePath
- setNewRoutePath
- popRoute
- currentConfiguration
- build
- RouteInformationProvider
- routerReportsNewRouteInformation
- PlatformRouteInformationProvider
- PlatformRouteInformationProvider
- _equals
- routerReportsNewRouteInformation
- value
- _platformReportsNewRouteInformation
- addListener
- removeListener
- dispose
- didPushRouteInformation
- PopNavigatorRouterDelegateMixin
- navigatorKey
- popRoute
- _RestorableRouteInformation
- createDefaultValue
- didUpdateValue
- fromPrimitives
Learn more about Flutter for embedded and desktop on industrialflutter.com