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