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