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';
10library;
11
12import 'dart:async';
13import 'dart:collection';
14
15import 'package:collection/collection.dart';
16import 'package:flutter/foundation.dart';
17import 'package:flutter/scheduler.dart';
18import 'package:flutter/services.dart';
19
20import 'basic.dart';
21import 'binding.dart';
22import 'framework.dart';
23import 'navigator.dart';
24import 'restoration.dart';
25import '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.
49class 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.
132class 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.
355class 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
573typedef _AsyncPassthrough<Q> = Future<Q> Function(Q);
574typedef _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.
583enum 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
599class _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
827class _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.
866class _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.
936abstract 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.
1060class 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].
1093class 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.
1147class 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
1168class _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.
1215abstract 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.
1307abstract 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].
1432abstract 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].
1466class 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].
1561mixin 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
1574class _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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com