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';
7import 'dart:convert';
8import 'dart:developer' as developer;
9import 'dart:ui' as ui;
10
11import 'package:flutter/foundation.dart';
12import 'package:flutter/rendering.dart';
13import 'package:flutter/scheduler.dart';
14import 'package:flutter/services.dart';
15
16import 'basic.dart';
17import 'binding.dart';
18import 'focus_manager.dart';
19import 'focus_scope.dart';
20import 'focus_traversal.dart';
21import 'framework.dart';
22import 'heroes.dart';
23import 'notification_listener.dart';
24import 'overlay.dart';
25import 'restoration.dart';
26import 'restoration_properties.dart';
27import 'routes.dart';
28import 'ticker_provider.dart';
29
30// Duration for delay before refocusing in android so that the focus won't be interrupted.
31const Duration _kAndroidRefocusingDelayDuration = Duration(milliseconds: 300);
32
33// Examples can assume:
34// typedef MyAppHome = Placeholder;
35// typedef MyHomePage = Placeholder;
36// typedef MyPage = ListTile; // any const widget with a Widget "title" constructor argument would do
37// late NavigatorState navigator;
38// late BuildContext context;
39
40/// Creates a route for the given route settings.
41///
42/// Used by [Navigator.onGenerateRoute].
43///
44/// See also:
45///
46/// * [Navigator], which is where all the [Route]s end up.
47typedef RouteFactory = Route<dynamic>? Function(RouteSettings settings);
48
49/// Creates a series of one or more routes.
50///
51/// Used by [Navigator.onGenerateInitialRoutes].
52typedef RouteListFactory = List<Route<dynamic>> Function(NavigatorState navigator, String initialRoute);
53
54/// Creates a [Route] that is to be added to a [Navigator].
55///
56/// The route can be configured with the provided `arguments`. The provided
57/// `context` is the `BuildContext` of the [Navigator] to which the route is
58/// added.
59///
60/// Used by the restorable methods of the [Navigator] that add anonymous routes
61/// (e.g. [NavigatorState.restorablePush]). For this use case, the
62/// [RestorableRouteBuilder] must be static function annotated with
63/// `@pragma('vm:entry-point')`. The [Navigator] will call it again during
64/// state restoration to re-create the route.
65typedef RestorableRouteBuilder<T> = Route<T> Function(BuildContext context, Object? arguments);
66
67/// Signature for the [Navigator.popUntil] predicate argument.
68typedef RoutePredicate = bool Function(Route<dynamic> route);
69
70/// Signature for a callback that verifies that it's OK to call [Navigator.pop].
71///
72/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback],
73/// [ModalRoute.removeScopedWillPopCallback], and [WillPopScope].
74@Deprecated(
75 'Use PopInvokedCallback instead. '
76 'This feature was deprecated after v3.12.0-1.0.pre.',
77)
78typedef WillPopCallback = Future<bool> Function();
79
80/// Signature for the [Navigator.onPopPage] callback.
81///
82/// This callback must call [Route.didPop] on the specified route and must
83/// properly update the pages list the next time it is passed into
84/// [Navigator.pages] so that it no longer includes the corresponding [Page].
85/// (Otherwise, the page will be interpreted as a new page to show when the
86/// [Navigator.pages] list is next updated.)
87typedef PopPageCallback = bool Function(Route<dynamic> route, dynamic result);
88
89/// Indicates whether the current route should be popped.
90///
91/// Used as the return value for [Route.willPop].
92///
93/// See also:
94///
95/// * [WillPopScope], a widget that hooks into the route's [Route.willPop]
96/// mechanism.
97enum RoutePopDisposition {
98 /// Pop the route.
99 ///
100 /// If [Route.willPop] or [Route.popDisposition] return [pop] then the back
101 /// button will actually pop the current route.
102 pop,
103
104 /// Do not pop the route.
105 ///
106 /// If [Route.willPop] or [Route.popDisposition] return [doNotPop] then the
107 /// back button will be ignored.
108 doNotPop,
109
110 /// Delegate this to the next level of navigation.
111 ///
112 /// If [Route.willPop] or [Route.popDisposition] return [bubble] then the back
113 /// button will be handled by the [SystemNavigator], which will usually close
114 /// the application.
115 bubble,
116}
117
118/// An abstraction for an entry managed by a [Navigator].
119///
120/// This class defines an abstract interface between the navigator and the
121/// "routes" that are pushed on and popped off the navigator. Most routes have
122/// visual affordances, which they place in the navigators [Overlay] using one
123/// or more [OverlayEntry] objects.
124///
125/// See [Navigator] for more explanation of how to use a [Route] with
126/// navigation, including code examples.
127///
128/// See [MaterialPageRoute] for a route that replaces the entire screen with a
129/// platform-adaptive transition.
130///
131/// A route can belong to a page if the [settings] are a subclass of [Page]. A
132/// page-based route, as opposed to a pageless route, is created from
133/// [Page.createRoute] during [Navigator.pages] updates. The page associated
134/// with this route may change during the lifetime of the route. If the
135/// [Navigator] updates the page of this route, it calls [changedInternalState]
136/// to notify the route that the page has been updated.
137///
138/// The type argument `T` is the route's return type, as used by
139/// [currentResult], [popped], and [didPop]. The type `void` may be used if the
140/// route does not return a value.
141abstract class Route<T> {
142 /// Initialize the [Route].
143 ///
144 /// If the [settings] are not provided, an empty [RouteSettings] object is
145 /// used instead.
146 Route({ RouteSettings? settings }) : _settings = settings ?? const RouteSettings() {
147 if (kFlutterMemoryAllocationsEnabled) {
148 FlutterMemoryAllocations.instance.dispatchObjectCreated(
149 library: 'package:flutter/widgets.dart',
150 className: '$Route<$T>',
151 object: this,
152 );
153 }
154 }
155
156 /// The navigator that the route is in, if any.
157 NavigatorState? get navigator => _navigator;
158 NavigatorState? _navigator;
159
160 /// The settings for this route.
161 ///
162 /// See [RouteSettings] for details.
163 ///
164 /// The settings can change during the route's lifetime. If the settings
165 /// change, the route's overlays will be marked dirty (see
166 /// [changedInternalState]).
167 ///
168 /// If the route is created from a [Page] in the [Navigator.pages] list, then
169 /// this will be a [Page] subclass, and it will be updated each time its
170 /// corresponding [Page] in the [Navigator.pages] has changed. Once the
171 /// [Route] is removed from the history, this value stops updating (and
172 /// remains with its last value).
173 RouteSettings get settings => _settings;
174 RouteSettings _settings;
175
176 /// The restoration scope ID to be used for the [RestorationScope] surrounding
177 /// this route.
178 ///
179 /// The restoration scope ID is null if restoration is currently disabled
180 /// for this route.
181 ///
182 /// If the restoration scope ID changes (e.g. because restoration is enabled
183 /// or disabled) during the life of the route, the [ValueListenable] notifies
184 /// its listeners. As an example, the ID changes to null while the route is
185 /// transitioning off screen, which triggers a notification on this field. At
186 /// that point, the route is considered as no longer present for restoration
187 /// purposes and its state will not be restored.
188 ValueListenable<String?> get restorationScopeId => _restorationScopeId;
189 final ValueNotifier<String?> _restorationScopeId = ValueNotifier<String?>(null);
190
191 void _updateSettings(RouteSettings newSettings) {
192 if (_settings != newSettings) {
193 _settings = newSettings;
194 changedInternalState();
195 }
196 }
197
198 // ignore: use_setters_to_change_properties, (setters can't be private)
199 void _updateRestorationId(String? restorationId) {
200 _restorationScopeId.value = restorationId;
201 }
202
203 /// The overlay entries of this route.
204 ///
205 /// These are typically populated by [install]. The [Navigator] is in charge
206 /// of adding them to and removing them from the [Overlay].
207 ///
208 /// There must be at least one entry in this list after [install] has been
209 /// invoked.
210 ///
211 /// The [Navigator] will take care of keeping the entries together if the
212 /// route is moved in the history.
213 List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
214
215 /// Called when the route is inserted into the navigator.
216 ///
217 /// Uses this to populate [overlayEntries]. There must be at least one entry in
218 /// this list after [install] has been invoked. The [Navigator] will be in charge
219 /// to add them to the [Overlay] or remove them from it by calling
220 /// [OverlayEntry.remove].
221 @protected
222 @mustCallSuper
223 void install() { }
224
225 /// Called after [install] when the route is pushed onto the navigator.
226 ///
227 /// The returned value resolves when the push transition is complete.
228 ///
229 /// The [didAdd] method will be called instead of [didPush] when the route
230 /// immediately appears on screen without any push transition.
231 ///
232 /// The [didChangeNext] and [didChangePrevious] methods are typically called
233 /// immediately after this method is called.
234 @protected
235 @mustCallSuper
236 TickerFuture didPush() {
237 return TickerFuture.complete()..then<void>((void _) {
238 if (navigator?.widget.requestFocus ?? false) {
239 navigator!.focusNode.enclosingScope?.requestFocus();
240 }
241 });
242 }
243
244 /// Called after [install] when the route is added to the navigator.
245 ///
246 /// This method is called instead of [didPush] when the route immediately
247 /// appears on screen without any push transition.
248 ///
249 /// The [didChangeNext] and [didChangePrevious] methods are typically called
250 /// immediately after this method is called.
251 @protected
252 @mustCallSuper
253 void didAdd() {
254 if (navigator?.widget.requestFocus ?? false) {
255 // This TickerFuture serves two purposes. First, we want to make sure that
256 // animations triggered by other operations will finish before focusing
257 // the navigator. Second, navigator.focusNode might acquire more focused
258 // children in Route.install asynchronously. This TickerFuture will wait
259 // for it to finish first.
260 //
261 // The later case can be found when subclasses manage their own focus scopes.
262 // For example, ModalRoute creates a focus scope in its overlay entries. The
263 // focused child can only be attached to navigator after initState which
264 // will be guarded by the asynchronous gap.
265 TickerFuture.complete().then<void>((void _) {
266 // The route can be disposed before the ticker future completes. This can
267 // happen when the navigator is under a TabView that warps from one tab to
268 // another, non-adjacent tab, with an animation. The TabView reorders its
269 // children before and after the warping completes, and that causes its
270 // children to be built and disposed within the same frame. If one of its
271 // children contains a navigator, the routes in that navigator are also
272 // added and disposed within that frame.
273 //
274 // Since the reference to the navigator will be set to null after it is
275 // disposed, we have to do a null-safe operation in case that happens
276 // within the same frame when it is added.
277 navigator?.focusNode.enclosingScope?.requestFocus();
278 });
279 }
280 }
281
282 /// Called after [install] when the route replaced another in the navigator.
283 ///
284 /// The [didChangeNext] and [didChangePrevious] methods are typically called
285 /// immediately after this method is called.
286 @protected
287 @mustCallSuper
288 void didReplace(Route<dynamic>? oldRoute) { }
289
290 /// Returns whether calling [Navigator.maybePop] when this [Route] is current
291 /// ([isCurrent]) should do anything.
292 ///
293 /// [Navigator.maybePop] is usually used instead of [Navigator.pop] to handle
294 /// the system back button.
295 ///
296 /// By default, if a [Route] is the first route in the history (i.e., if
297 /// [isFirst]), it reports that pops should be bubbled
298 /// ([RoutePopDisposition.bubble]). This behavior prevents the user from
299 /// popping the first route off the history and being stranded at a blank
300 /// screen; instead, the larger scope is popped (e.g. the application quits,
301 /// so that the user returns to the previous application).
302 ///
303 /// In other cases, the default behavior is to accept the pop
304 /// ([RoutePopDisposition.pop]).
305 ///
306 /// The third possible value is [RoutePopDisposition.doNotPop], which causes
307 /// the pop request to be ignored entirely.
308 ///
309 /// See also:
310 ///
311 /// * [Form], which provides a [Form.onWillPop] callback that uses this
312 /// mechanism.
313 /// * [WillPopScope], another widget that provides a way to intercept the
314 /// back button.
315 @Deprecated(
316 'Use popDisposition instead. '
317 'This feature was deprecated after v3.12.0-1.0.pre.',
318 )
319 Future<RoutePopDisposition> willPop() async {
320 return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop;
321 }
322
323 /// Returns whether calling [Navigator.maybePop] when this [Route] is current
324 /// ([isCurrent]) should do anything.
325 ///
326 /// [Navigator.maybePop] is usually used instead of [Navigator.pop] to handle
327 /// the system back button, when it hasn't been disabled via
328 /// [SystemNavigator.setFrameworkHandlesBack].
329 ///
330 /// By default, if a [Route] is the first route in the history (i.e., if
331 /// [isFirst]), it reports that pops should be bubbled
332 /// ([RoutePopDisposition.bubble]). This behavior prevents the user from
333 /// popping the first route off the history and being stranded at a blank
334 /// screen; instead, the larger scope is popped (e.g. the application quits,
335 /// so that the user returns to the previous application).
336 ///
337 /// In other cases, the default behavior is to accept the pop
338 /// ([RoutePopDisposition.pop]).
339 ///
340 /// The third possible value is [RoutePopDisposition.doNotPop], which causes
341 /// the pop request to be ignored entirely.
342 ///
343 /// See also:
344 ///
345 /// * [Form], which provides a [Form.canPop] boolean that is similar.
346 /// * [PopScope], a widget that provides a way to intercept the back button.
347 RoutePopDisposition get popDisposition {
348 return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop;
349 }
350
351 /// {@template flutter.widgets.navigator.onPopInvoked}
352 /// Called after a route pop was handled.
353 ///
354 /// Even when the pop is canceled, for example by a [PopScope] widget, this
355 /// will still be called. The `didPop` parameter indicates whether or not the
356 /// back navigation actually happened successfully.
357 /// {@endtemplate}
358 void onPopInvoked(bool didPop) {}
359
360 /// Whether calling [didPop] would return false.
361 bool get willHandlePopInternally => false;
362
363 /// When this route is popped (see [Navigator.pop]) if the result isn't
364 /// specified or if it's null, this value will be used instead.
365 ///
366 /// This fallback is implemented by [didComplete]. This value is used if the
367 /// argument to that method is null.
368 T? get currentResult => null;
369
370 /// A future that completes when this route is popped off the navigator.
371 ///
372 /// The future completes with the value given to [Navigator.pop], if any, or
373 /// else the value of [currentResult]. See [didComplete] for more discussion
374 /// on this topic.
375 Future<T?> get popped => _popCompleter.future;
376 final Completer<T?> _popCompleter = Completer<T?>();
377
378 final Completer<T?> _disposeCompleter = Completer<T?>();
379
380 /// A request was made to pop this route. If the route can handle it
381 /// internally (e.g. because it has its own stack of internal state) then
382 /// return false, otherwise return true (by returning the value of calling
383 /// `super.didPop`). Returning false will prevent the default behavior of
384 /// [NavigatorState.pop].
385 ///
386 /// When this function returns true, the navigator removes this route from
387 /// the history but does not yet call [dispose]. Instead, it is the route's
388 /// responsibility to call [NavigatorState.finalizeRoute], which will in turn
389 /// call [dispose] on the route. This sequence lets the route perform an
390 /// exit animation (or some other visual effect) after being popped but prior
391 /// to being disposed.
392 ///
393 /// This method should call [didComplete] to resolve the [popped] future (and
394 /// this is all that the default implementation does); routes should not wait
395 /// for their exit animation to complete before doing so.
396 ///
397 /// See [popped], [didComplete], and [currentResult] for a discussion of the
398 /// `result` argument.
399 @mustCallSuper
400 bool didPop(T? result) {
401 didComplete(result);
402 return true;
403 }
404
405 /// The route was popped or is otherwise being removed somewhat gracefully.
406 ///
407 /// This is called by [didPop] and in response to
408 /// [NavigatorState.pushReplacement]. If [didPop] was not called, then the
409 /// [NavigatorState.finalizeRoute] method must be called immediately, and no exit
410 /// animation will run.
411 ///
412 /// The [popped] future is completed by this method. The `result` argument
413 /// specifies the value that this future is completed with, unless it is null,
414 /// in which case [currentResult] is used instead.
415 ///
416 /// This should be called before the pop animation, if any, takes place,
417 /// though in some cases the animation may be driven by the user before the
418 /// route is committed to being popped; this can in particular happen with the
419 /// iOS-style back gesture. See [NavigatorState.didStartUserGesture].
420 @protected
421 @mustCallSuper
422 void didComplete(T? result) {
423 _popCompleter.complete(result ?? currentResult);
424 }
425
426 /// The given route, which was above this one, has been popped off the
427 /// navigator.
428 ///
429 /// This route is now the current route ([isCurrent] is now true), and there
430 /// is no next route.
431 @protected
432 @mustCallSuper
433 void didPopNext(Route<dynamic> nextRoute) { }
434
435 /// This route's next route has changed to the given new route.
436 ///
437 /// This is called on a route whenever the next route changes for any reason,
438 /// so long as it is in the history, including when a route is first added to
439 /// a [Navigator] (e.g. by [Navigator.push]), except for cases when
440 /// [didPopNext] would be called.
441 ///
442 /// The `nextRoute` argument will be null if there's no new next route (i.e.
443 /// if [isCurrent] is true).
444 @protected
445 @mustCallSuper
446 void didChangeNext(Route<dynamic>? nextRoute) { }
447
448 /// This route's previous route has changed to the given new route.
449 ///
450 /// This is called on a route whenever the previous route changes for any
451 /// reason, so long as it is in the history, except for immediately after the
452 /// route itself has been pushed (in which case [didPush] or [didReplace] will
453 /// be called instead).
454 ///
455 /// The `previousRoute` argument will be null if there's no previous route
456 /// (i.e. if [isFirst] is true).
457 @protected
458 @mustCallSuper
459 void didChangePrevious(Route<dynamic>? previousRoute) { }
460
461 /// Called whenever the internal state of the route has changed.
462 ///
463 /// This should be called whenever [willHandlePopInternally], [didPop],
464 /// [ModalRoute.offstage], or other internal state of the route changes value.
465 /// It is used by [ModalRoute], for example, to report the new information via
466 /// its inherited widget to any children of the route.
467 ///
468 /// See also:
469 ///
470 /// * [changedExternalState], which is called when the [Navigator] has
471 /// updated in some manner that might affect the routes.
472 @protected
473 @mustCallSuper
474 void changedInternalState() { }
475
476 /// Called whenever the [Navigator] has updated in some manner that might
477 /// affect routes, to indicate that the route may wish to rebuild as well.
478 ///
479 /// This is called by the [Navigator] whenever the
480 /// [NavigatorState]'s [State.widget] changes (as in [State.didUpdateWidget]),
481 /// for example because the [MaterialApp] has been rebuilt. This
482 /// ensures that routes that directly refer to the state of the
483 /// widget that built the [MaterialApp] will be notified when that
484 /// widget rebuilds, since it would otherwise be difficult to notify
485 /// the routes that state they depend on may have changed.
486 ///
487 /// It is also called whenever the [Navigator]'s dependencies change
488 /// (as in [State.didChangeDependencies]). This allows routes to use the
489 /// [Navigator]'s context ([NavigatorState.context]), for example in
490 /// [ModalRoute.barrierColor], and update accordingly.
491 ///
492 /// The [ModalRoute] subclass overrides this to force the barrier
493 /// overlay to rebuild.
494 ///
495 /// See also:
496 ///
497 /// * [changedInternalState], the equivalent but for changes to the internal
498 /// state of the route.
499 @protected
500 @mustCallSuper
501 void changedExternalState() { }
502
503 /// Discards any resources used by the object.
504 ///
505 /// This method should not remove its [overlayEntries] from the [Overlay]. The
506 /// object's owner is in charge of doing that.
507 ///
508 /// After this is called, the object is not in a usable state and should be
509 /// discarded.
510 ///
511 /// This method should only be called by the object's owner; typically the
512 /// [Navigator] owns a route and so will call this method when the route is
513 /// removed, after which the route is no longer referenced by the navigator.
514 @mustCallSuper
515 @protected
516 void dispose() {
517 _navigator = null;
518 _restorationScopeId.dispose();
519 _disposeCompleter.complete();
520 if (kFlutterMemoryAllocationsEnabled) {
521 FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
522 }
523 }
524
525 /// Whether this route is the top-most route on the navigator.
526 ///
527 /// If this is true, then [isActive] is also true.
528 bool get isCurrent {
529 if (_navigator == null) {
530 return false;
531 }
532 final _RouteEntry? currentRouteEntry = _navigator!._lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
533 if (currentRouteEntry == null) {
534 return false;
535 }
536 return currentRouteEntry.route == this;
537 }
538
539 /// Whether this route is the bottom-most active route on the navigator.
540 ///
541 /// If [isFirst] and [isCurrent] are both true then this is the only route on
542 /// the navigator (and [isActive] will also be true).
543 bool get isFirst {
544 if (_navigator == null) {
545 return false;
546 }
547 final _RouteEntry? currentRouteEntry = _navigator!._firstRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
548 if (currentRouteEntry == null) {
549 return false;
550 }
551 return currentRouteEntry.route == this;
552 }
553
554 /// Whether there is at least one active route underneath this route.
555 @protected
556 bool get hasActiveRouteBelow {
557 if (_navigator == null) {
558 return false;
559 }
560 for (final _RouteEntry entry in _navigator!._history) {
561 if (entry.route == this) {
562 return false;
563 }
564 if (_RouteEntry.isPresentPredicate(entry)) {
565 return true;
566 }
567 }
568 return false;
569 }
570
571 /// Whether this route is on the navigator.
572 ///
573 /// If the route is not only active, but also the current route (the top-most
574 /// route), then [isCurrent] will also be true. If it is the first route (the
575 /// bottom-most route), then [isFirst] will also be true.
576 ///
577 /// If a higher route is entirely opaque, then the route will be active but not
578 /// rendered. It is even possible for the route to be active but for the stateful
579 /// widgets within the route to not be instantiated. See [ModalRoute.maintainState].
580 bool get isActive {
581 if (_navigator == null) {
582 return false;
583 }
584 return _navigator!._firstRouteEntryWhereOrNull(_RouteEntry.isRoutePredicate(this))?.isPresent ?? false;
585 }
586}
587
588/// Data that might be useful in constructing a [Route].
589@immutable
590class RouteSettings {
591 /// Creates data used to construct routes.
592 const RouteSettings({
593 this.name,
594 this.arguments,
595 });
596
597 /// The name of the route (e.g., "/settings").
598 ///
599 /// If null, the route is anonymous.
600 final String? name;
601
602 /// The arguments passed to this route.
603 ///
604 /// May be used when building the route, e.g. in [Navigator.onGenerateRoute].
605 final Object? arguments;
606
607 @override
608 String toString() => '${objectRuntimeType(this, 'RouteSettings')}(${name == null ? 'none' : '"$name"'}, $arguments)';
609}
610
611/// Describes the configuration of a [Route].
612///
613/// The type argument `T` is the corresponding [Route]'s return type, as
614/// used by [Route.currentResult], [Route.popped], and [Route.didPop].
615///
616/// See also:
617///
618/// * [Navigator.pages], which accepts a list of [Page]s and updates its routes
619/// history.
620abstract class Page<T> extends RouteSettings {
621 /// Creates a page and initializes [key] for subclasses.
622 const Page({
623 this.key,
624 super.name,
625 super.arguments,
626 this.restorationId,
627 });
628
629 /// The key associated with this page.
630 ///
631 /// This key will be used for comparing pages in [canUpdate].
632 final LocalKey? key;
633
634 /// Restoration ID to save and restore the state of the [Route] configured by
635 /// this page.
636 ///
637 /// If no restoration ID is provided, the [Route] will not restore its state.
638 ///
639 /// See also:
640 ///
641 /// * [RestorationManager], which explains how state restoration works in
642 /// Flutter.
643 final String? restorationId;
644
645 /// Whether this page can be updated with the [other] page.
646 ///
647 /// Two pages are consider updatable if they have same the [runtimeType] and
648 /// [key].
649 bool canUpdate(Page<dynamic> other) {
650 return other.runtimeType == runtimeType &&
651 other.key == key;
652 }
653
654 /// Creates the [Route] that corresponds to this page.
655 ///
656 /// The created [Route] must have its [Route.settings] property set to this [Page].
657 @factory
658 Route<T> createRoute(BuildContext context);
659
660 @override
661 String toString() => '${objectRuntimeType(this, 'Page')}("$name", $key, $arguments)';
662}
663
664/// An interface for observing the behavior of a [Navigator].
665class NavigatorObserver {
666 /// The navigator that the observer is observing, if any.
667 NavigatorState? get navigator => _navigators[this];
668
669 // Expando mapping instances of NavigatorObserver to their associated
670 // NavigatorState (or `null`, if there is no associated NavigatorState). The
671 // reason we don't use a private instance field of type
672 // `NavigatorState?` is because as part of implementing
673 // https://github.com/dart-lang/language/issues/2020, it will soon become a
674 // runtime error to invoke a private member that is mocked in another
675 // library. By using an expando rather than an instance field, we ensure
676 // that a mocked NavigatorObserver can still properly keep track of its
677 // associated NavigatorState.
678 static final Expando<NavigatorState> _navigators = Expando<NavigatorState>();
679
680 /// The [Navigator] pushed `route`.
681 ///
682 /// The route immediately below that one, and thus the previously active
683 /// route, is `previousRoute`.
684 void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { }
685
686 /// The [Navigator] popped `route`.
687 ///
688 /// The route immediately below that one, and thus the newly active
689 /// route, is `previousRoute`.
690 void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { }
691
692 /// The [Navigator] removed `route`.
693 ///
694 /// If only one route is being removed, then the route immediately below
695 /// that one, if any, is `previousRoute`.
696 ///
697 /// If multiple routes are being removed, then the route below the
698 /// bottommost route being removed, if any, is `previousRoute`, and this
699 /// method will be called once for each removed route, from the topmost route
700 /// to the bottommost route.
701 void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { }
702
703 /// The [Navigator] replaced `oldRoute` with `newRoute`.
704 void didReplace({ Route<dynamic>? newRoute, Route<dynamic>? oldRoute }) { }
705
706 /// The [Navigator]'s routes are being moved by a user gesture.
707 ///
708 /// For example, this is called when an iOS back gesture starts, and is used
709 /// to disabled hero animations during such interactions.
710 void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) { }
711
712 /// User gesture is no longer controlling the [Navigator].
713 ///
714 /// Paired with an earlier call to [didStartUserGesture].
715 void didStopUserGesture() { }
716}
717
718/// An inherited widget to host a hero controller.
719///
720/// The hosted hero controller will be picked up by the navigator in the
721/// [child] subtree. Once a navigator picks up this controller, the navigator
722/// will bar any navigator below its subtree from receiving this controller.
723///
724/// The hero controller inside the [HeroControllerScope] can only subscribe to
725/// one navigator at a time. An assertion will be thrown if the hero controller
726/// subscribes to more than one navigators. This can happen when there are
727/// multiple navigators under the same [HeroControllerScope] in parallel.
728class HeroControllerScope extends InheritedWidget {
729 /// Creates a widget to host the input [controller].
730 const HeroControllerScope({
731 super.key,
732 required HeroController this.controller,
733 required super.child,
734 });
735
736 /// Creates a widget to prevent the subtree from receiving the hero controller
737 /// above.
738 const HeroControllerScope.none({
739 super.key,
740 required super.child,
741 }) : controller = null;
742
743 /// The hero controller that is hosted inside this widget.
744 final HeroController? controller;
745
746 /// Retrieves the [HeroController] from the closest [HeroControllerScope]
747 /// ancestor, or null if none exists.
748 ///
749 /// Calling this method will create a dependency on the closest
750 /// [HeroControllerScope] in the [context], if there is one.
751 ///
752 /// See also:
753 ///
754 /// * [HeroControllerScope.of], which is similar to this method, but asserts
755 /// if no [HeroControllerScope] ancestor is found.
756 static HeroController? maybeOf(BuildContext context) {
757 final HeroControllerScope? host = context.dependOnInheritedWidgetOfExactType<HeroControllerScope>();
758 return host?.controller;
759 }
760
761 /// Retrieves the [HeroController] from the closest [HeroControllerScope]
762 /// ancestor.
763 ///
764 /// If no ancestor is found, this method will assert in debug mode, and throw
765 /// an exception in release mode.
766 ///
767 /// Calling this method will create a dependency on the closest
768 /// [HeroControllerScope] in the [context].
769 ///
770 /// See also:
771 ///
772 /// * [HeroControllerScope.maybeOf], which is similar to this method, but
773 /// returns null if no [HeroControllerScope] ancestor is found.
774 static HeroController of(BuildContext context) {
775 final HeroController? controller = maybeOf(context);
776 assert(() {
777 if (controller == null) {
778 throw FlutterError(
779 'HeroControllerScope.of() was called with a context that does not contain a '
780 'HeroControllerScope widget.\n'
781 'No HeroControllerScope widget ancestor could be found starting from the '
782 'context that was passed to HeroControllerScope.of(). This can happen '
783 'because you are using a widget that looks for a HeroControllerScope '
784 'ancestor, but no such ancestor exists.\n'
785 'The context used was:\n'
786 ' $context',
787 );
788 }
789 return true;
790 }());
791 return controller!;
792 }
793
794 @override
795 bool updateShouldNotify(HeroControllerScope oldWidget) {
796 return oldWidget.controller != controller;
797 }
798}
799
800/// A [Route] wrapper interface that can be staged for [TransitionDelegate] to
801/// decide how its underlying [Route] should transition on or off screen.
802abstract class RouteTransitionRecord {
803 /// Retrieves the wrapped [Route].
804 Route<dynamic> get route;
805
806 /// Whether this route is waiting for the decision on how to enter the screen.
807 ///
808 /// If this property is true, this route requires an explicit decision on how
809 /// to transition into the screen. Such a decision should be made in the
810 /// [TransitionDelegate.resolve].
811 bool get isWaitingForEnteringDecision;
812
813 /// Whether this route is waiting for the decision on how to exit the screen.
814 ///
815 /// If this property is true, this route requires an explicit decision on how
816 /// to transition off the screen. Such a decision should be made in the
817 /// [TransitionDelegate.resolve].
818 bool get isWaitingForExitingDecision;
819
820 /// Marks the [route] to be pushed with transition.
821 ///
822 /// During [TransitionDelegate.resolve], this can be called on an entering
823 /// route (where [RouteTransitionRecord.isWaitingForEnteringDecision] is true) in indicate that the
824 /// route should be pushed onto the [Navigator] with an animated transition.
825 void markForPush();
826
827 /// Marks the [route] to be added without transition.
828 ///
829 /// During [TransitionDelegate.resolve], this can be called on an entering
830 /// route (where [RouteTransitionRecord.isWaitingForEnteringDecision] is true) in indicate that the
831 /// route should be added onto the [Navigator] without an animated transition.
832 void markForAdd();
833
834 /// Marks the [route] to be popped with transition.
835 ///
836 /// During [TransitionDelegate.resolve], this can be called on an exiting
837 /// route to indicate that the route should be popped off the [Navigator] with
838 /// an animated transition.
839 void markForPop([dynamic result]);
840
841 /// Marks the [route] to be completed without transition.
842 ///
843 /// During [TransitionDelegate.resolve], this can be called on an exiting
844 /// route to indicate that the route should be completed with the provided
845 /// result and removed from the [Navigator] without an animated transition.
846 void markForComplete([dynamic result]);
847
848 /// Marks the [route] to be removed without transition.
849 ///
850 /// During [TransitionDelegate.resolve], this can be called on an exiting
851 /// route to indicate that the route should be removed from the [Navigator]
852 /// without completing and without an animated transition.
853 void markForRemove();
854}
855
856/// The delegate that decides how pages added and removed from [Navigator.pages]
857/// transition in or out of the screen.
858///
859/// This abstract class implements the API to be called by [Navigator] when it
860/// requires explicit decisions on how the routes transition on or off the screen.
861///
862/// To make route transition decisions, subclass must implement [resolve].
863///
864/// {@tool snippet}
865/// The following example demonstrates how to implement a subclass that always
866/// removes or adds routes without animated transitions and puts the removed
867/// routes at the top of the list.
868///
869/// ```dart
870/// class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
871/// @override
872/// Iterable<RouteTransitionRecord> resolve({
873/// required List<RouteTransitionRecord> newPageRouteHistory,
874/// required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
875/// required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
876/// }) {
877/// final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
878///
879/// for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
880/// if (pageRoute.isWaitingForEnteringDecision) {
881/// pageRoute.markForAdd();
882/// }
883/// results.add(pageRoute);
884///
885/// }
886/// for (final RouteTransitionRecord exitingPageRoute in locationToExitingPageRoute.values) {
887/// if (exitingPageRoute.isWaitingForExitingDecision) {
888/// exitingPageRoute.markForRemove();
889/// final List<RouteTransitionRecord>? pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
890/// if (pagelessRoutes != null) {
891/// for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
892/// pagelessRoute.markForRemove();
893/// }
894/// }
895/// }
896/// results.add(exitingPageRoute);
897///
898/// }
899/// return results;
900/// }
901/// }
902///
903/// ```
904/// {@end-tool}
905///
906/// See also:
907///
908/// * [Navigator.transitionDelegate], which uses this class to make route
909/// transition decisions.
910/// * [DefaultTransitionDelegate], which implements the default way to decide
911/// how routes transition in or out of the screen.
912abstract class TransitionDelegate<T> {
913 /// Creates a delegate and enables subclass to create a constant class.
914 const TransitionDelegate();
915
916 Iterable<RouteTransitionRecord> _transition({
917 required List<RouteTransitionRecord> newPageRouteHistory,
918 required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
919 required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
920 }) {
921 final Iterable<RouteTransitionRecord> results = resolve(
922 newPageRouteHistory: newPageRouteHistory,
923 locationToExitingPageRoute: locationToExitingPageRoute,
924 pageRouteToPagelessRoutes: pageRouteToPagelessRoutes,
925 );
926 // Verifies the integrity after the decisions have been made.
927 //
928 // Here are the rules:
929 // - All the entering routes in newPageRouteHistory must either be pushed or
930 // added.
931 // - All the exiting routes in locationToExitingPageRoute must either be
932 // popped, completed or removed.
933 // - All the pageless routes that belong to exiting routes must either be
934 // popped, completed or removed.
935 // - All the entering routes in the result must preserve the same order as
936 // the entering routes in newPageRouteHistory, and the result must contain
937 // all exiting routes.
938 // ex:
939 //
940 // newPageRouteHistory = [A, B, C]
941 //
942 // locationToExitingPageRoute = {A -> D, C -> E}
943 //
944 // results = [A, B ,C ,D ,E] is valid
945 // results = [D, A, B ,C ,E] is also valid because exiting route can be
946 // inserted in any place
947 //
948 // results = [B, A, C ,D ,E] is invalid because B must be after A.
949 // results = [A, B, C ,E] is invalid because results must include D.
950 assert(() {
951 final List<RouteTransitionRecord> resultsToVerify = results.toList(growable: false);
952 final Set<RouteTransitionRecord> exitingPageRoutes = locationToExitingPageRoute.values.toSet();
953 // Firstly, verifies all exiting routes have been marked.
954 for (final RouteTransitionRecord exitingPageRoute in exitingPageRoutes) {
955 assert(!exitingPageRoute.isWaitingForExitingDecision);
956 if (pageRouteToPagelessRoutes.containsKey(exitingPageRoute)) {
957 for (final RouteTransitionRecord pagelessRoute in pageRouteToPagelessRoutes[exitingPageRoute]!) {
958 assert(!pagelessRoute.isWaitingForExitingDecision);
959 }
960 }
961 }
962 // Secondly, verifies the order of results matches the newPageRouteHistory
963 // and contains all the exiting routes.
964 int indexOfNextRouteInNewHistory = 0;
965
966 for (final _RouteEntry routeEntry in resultsToVerify.cast<_RouteEntry>()) {
967 assert(!routeEntry.isWaitingForEnteringDecision && !routeEntry.isWaitingForExitingDecision);
968 if (
969 indexOfNextRouteInNewHistory >= newPageRouteHistory.length ||
970 routeEntry != newPageRouteHistory[indexOfNextRouteInNewHistory]
971 ) {
972 assert(exitingPageRoutes.contains(routeEntry));
973 exitingPageRoutes.remove(routeEntry);
974 } else {
975 indexOfNextRouteInNewHistory += 1;
976 }
977 }
978
979 assert(
980 indexOfNextRouteInNewHistory == newPageRouteHistory.length &&
981 exitingPageRoutes.isEmpty,
982 'The merged result from the $runtimeType.resolve does not include all '
983 'required routes. Do you remember to merge all exiting routes?',
984 );
985 return true;
986 }());
987
988 return results;
989 }
990
991 /// A method that will be called by the [Navigator] to decide how routes
992 /// transition in or out of the screen when [Navigator.pages] is updated.
993 ///
994 /// The `newPageRouteHistory` list contains all page-based routes in the order
995 /// that will be on the [Navigator]'s history stack after this update
996 /// completes. If a route in `newPageRouteHistory` has its
997 /// [RouteTransitionRecord.isWaitingForEnteringDecision] set to true, this
998 /// route requires explicit decision on how it should transition onto the
999 /// Navigator. To make a decision, call [RouteTransitionRecord.markForPush] or
1000 /// [RouteTransitionRecord.markForAdd].
1001 ///
1002 /// The `locationToExitingPageRoute` contains the pages-based routes that
1003 /// are removed from the routes history after page update. This map records
1004 /// page-based routes to be removed with the location of the route in the
1005 /// original route history before the update. The keys are the locations
1006 /// represented by the page-based routes that are directly below the removed
1007 /// routes, and the value are the page-based routes to be removed. The
1008 /// location is null if the route to be removed is the bottom most route. If
1009 /// a route in `locationToExitingPageRoute` has its
1010 /// [RouteTransitionRecord.isWaitingForExitingDecision] set to true, this
1011 /// route requires explicit decision on how it should transition off the
1012 /// Navigator. To make a decision for a removed route, call
1013 /// [RouteTransitionRecord.markForPop],
1014 /// [RouteTransitionRecord.markForComplete] or
1015 /// [RouteTransitionRecord.markForRemove]. It is possible that decisions are
1016 /// not required for routes in the `locationToExitingPageRoute`. This can
1017 /// happen if the routes have already been popped in earlier page updates and
1018 /// are still waiting for popping animations to finish. In such case, those
1019 /// routes are still included in the `locationToExitingPageRoute` with their
1020 /// [RouteTransitionRecord.isWaitingForExitingDecision] set to false and no
1021 /// decisions are required.
1022 ///
1023 /// The `pageRouteToPagelessRoutes` records the page-based routes and their
1024 /// associated pageless routes. If a page-based route is waiting for exiting
1025 /// decision, its associated pageless routes also require explicit decisions
1026 /// on how to transition off the screen.
1027 ///
1028 /// Once all the decisions have been made, this method must merge the removed
1029 /// routes (whether or not they require decisions) and the
1030 /// `newPageRouteHistory` and return the merged result. The order in the
1031 /// result will be the order the [Navigator] uses for updating the route
1032 /// history. The return list must preserve the same order of routes in
1033 /// `newPageRouteHistory`. The removed routes, however, can be inserted into
1034 /// the return list freely as long as all of them are included.
1035 ///
1036 /// For example, consider the following case.
1037 ///
1038 /// `newPageRouteHistory = [A, B, C]`
1039 ///
1040 /// `locationToExitingPageRoute = {A -> D, C -> E}`
1041 ///
1042 /// The following outputs are valid.
1043 ///
1044 /// `result = [A, B ,C ,D ,E]` is valid.
1045 /// `result = [D, A, B ,C ,E]` is also valid because exiting route can be
1046 /// inserted in any place.
1047 ///
1048 /// The following outputs are invalid.
1049 ///
1050 /// `result = [B, A, C ,D ,E]` is invalid because B must be after A.
1051 /// `result = [A, B, C ,E]` is invalid because results must include D.
1052 ///
1053 /// See also:
1054 ///
1055 /// * [RouteTransitionRecord.markForPush], which makes route enter the screen
1056 /// with an animated transition.
1057 /// * [RouteTransitionRecord.markForAdd], which makes route enter the screen
1058 /// without an animated transition.
1059 /// * [RouteTransitionRecord.markForPop], which makes route exit the screen
1060 /// with an animated transition.
1061 /// * [RouteTransitionRecord.markForRemove], which does not complete the
1062 /// route and makes it exit the screen without an animated transition.
1063 /// * [RouteTransitionRecord.markForComplete], which completes the route and
1064 /// makes it exit the screen without an animated transition.
1065 /// * [DefaultTransitionDelegate.resolve], which implements the default way
1066 /// to decide how routes transition in or out of the screen.
1067 Iterable<RouteTransitionRecord> resolve({
1068 required List<RouteTransitionRecord> newPageRouteHistory,
1069 required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
1070 required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
1071 });
1072}
1073
1074/// The default implementation of [TransitionDelegate] that the [Navigator] will
1075/// use if its [Navigator.transitionDelegate] is not specified.
1076///
1077/// This transition delegate follows two rules. Firstly, all the entering routes
1078/// are placed on top of the exiting routes if they are at the same location.
1079/// Secondly, the top most route will always transition with an animated transition.
1080/// All the other routes below will either be completed with
1081/// [Route.currentResult] or added without an animated transition.
1082class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
1083 /// Creates a default transition delegate.
1084 const DefaultTransitionDelegate() : super();
1085
1086 @override
1087 Iterable<RouteTransitionRecord> resolve({
1088 required List<RouteTransitionRecord> newPageRouteHistory,
1089 required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
1090 required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
1091 }) {
1092 final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
1093 // This method will handle the exiting route and its corresponding pageless
1094 // route at this location. It will also recursively check if there is any
1095 // other exiting routes above it and handle them accordingly.
1096 void handleExitingRoute(RouteTransitionRecord? location, bool isLast) {
1097 final RouteTransitionRecord? exitingPageRoute = locationToExitingPageRoute[location];
1098 if (exitingPageRoute == null) {
1099 return;
1100 }
1101 if (exitingPageRoute.isWaitingForExitingDecision) {
1102 final bool hasPagelessRoute = pageRouteToPagelessRoutes.containsKey(exitingPageRoute);
1103 final bool isLastExitingPageRoute = isLast && !locationToExitingPageRoute.containsKey(exitingPageRoute);
1104 if (isLastExitingPageRoute && !hasPagelessRoute) {
1105 exitingPageRoute.markForPop(exitingPageRoute.route.currentResult);
1106 } else {
1107 exitingPageRoute.markForComplete(exitingPageRoute.route.currentResult);
1108 }
1109 if (hasPagelessRoute) {
1110 final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute]!;
1111 for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
1112 // It is possible that a pageless route that belongs to an exiting
1113 // page-based route does not require exiting decision. This can
1114 // happen if the page list is updated right after a Navigator.pop.
1115 if (pagelessRoute.isWaitingForExitingDecision) {
1116 if (isLastExitingPageRoute && pagelessRoute == pagelessRoutes.last) {
1117 pagelessRoute.markForPop(pagelessRoute.route.currentResult);
1118 } else {
1119 pagelessRoute.markForComplete(pagelessRoute.route.currentResult);
1120 }
1121 }
1122 }
1123 }
1124 }
1125 results.add(exitingPageRoute);
1126
1127 // It is possible there is another exiting route above this exitingPageRoute.
1128 handleExitingRoute(exitingPageRoute, isLast);
1129 }
1130
1131 // Handles exiting route in the beginning of list.
1132 handleExitingRoute(null, newPageRouteHistory.isEmpty);
1133
1134 for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
1135 final bool isLastIteration = newPageRouteHistory.last == pageRoute;
1136 if (pageRoute.isWaitingForEnteringDecision) {
1137 if (!locationToExitingPageRoute.containsKey(pageRoute) && isLastIteration) {
1138 pageRoute.markForPush();
1139 } else {
1140 pageRoute.markForAdd();
1141 }
1142 }
1143 results.add(pageRoute);
1144 handleExitingRoute(pageRoute, isLastIteration);
1145 }
1146 return results;
1147 }
1148}
1149
1150/// The default value of [Navigator.routeTraversalEdgeBehavior].
1151///
1152/// {@macro flutter.widgets.navigator.routeTraversalEdgeBehavior}
1153const TraversalEdgeBehavior kDefaultRouteTraversalEdgeBehavior = TraversalEdgeBehavior.parentScope;
1154
1155/// A widget that manages a set of child widgets with a stack discipline.
1156///
1157/// Many apps have a navigator near the top of their widget hierarchy in order
1158/// to display their logical history using an [Overlay] with the most recently
1159/// visited pages visually on top of the older pages. Using this pattern lets
1160/// the navigator visually transition from one page to another by moving the widgets
1161/// around in the overlay. Similarly, the navigator can be used to show a dialog
1162/// by positioning the dialog widget above the current page.
1163///
1164/// ## Using the Pages API
1165///
1166/// The [Navigator] will convert its [Navigator.pages] into a stack of [Route]s
1167/// if it is provided. A change in [Navigator.pages] will trigger an update to
1168/// the stack of [Route]s. The [Navigator] will update its routes to match the
1169/// new configuration of its [Navigator.pages]. To use this API, one can create
1170/// a [Page] subclass and defines a list of [Page]s for [Navigator.pages]. A
1171/// [Navigator.onPopPage] callback is also required to properly clean up the
1172/// input pages in case of a pop.
1173///
1174/// By Default, the [Navigator] will use [DefaultTransitionDelegate] to decide
1175/// how routes transition in or out of the screen. To customize it, define a
1176/// [TransitionDelegate] subclass and provide it to the
1177/// [Navigator.transitionDelegate].
1178///
1179/// For more information on using the pages API, see the [Router] widget.
1180///
1181/// ## Using the Navigator API
1182///
1183/// Mobile apps typically reveal their contents via full-screen elements
1184/// called "screens" or "pages". In Flutter these elements are called
1185/// routes and they're managed by a [Navigator] widget. The navigator
1186/// manages a stack of [Route] objects and provides two ways for managing
1187/// the stack, the declarative API [Navigator.pages] or imperative API
1188/// [Navigator.push] and [Navigator.pop].
1189///
1190/// When your user interface fits this paradigm of a stack, where the user
1191/// should be able to _navigate_ back to an earlier element in the stack,
1192/// the use of routes and the Navigator is appropriate. On certain platforms,
1193/// such as Android, the system UI will provide a back button (outside the
1194/// bounds of your application) that will allow the user to navigate back
1195/// to earlier routes in your application's stack. On platforms that don't
1196/// have this build-in navigation mechanism, the use of an [AppBar] (typically
1197/// used in the [Scaffold.appBar] property) can automatically add a back
1198/// button for user navigation.
1199///
1200/// ### Displaying a full-screen route
1201///
1202/// Although you can create a navigator directly, it's most common to use the
1203/// navigator created by the `Router` which itself is created and configured by
1204/// a [WidgetsApp] or a [MaterialApp] widget. You can refer to that navigator
1205/// with [Navigator.of].
1206///
1207/// A [MaterialApp] is the simplest way to set things up. The [MaterialApp]'s
1208/// home becomes the route at the bottom of the [Navigator]'s stack. It is what
1209/// you see when the app is launched.
1210///
1211/// ```dart
1212/// void main() {
1213/// runApp(const MaterialApp(home: MyAppHome()));
1214/// }
1215/// ```
1216///
1217/// To push a new route on the stack you can create an instance of
1218/// [MaterialPageRoute] with a builder function that creates whatever you
1219/// want to appear on the screen. For example:
1220///
1221/// ```dart
1222/// Navigator.push(context, MaterialPageRoute<void>(
1223/// builder: (BuildContext context) {
1224/// return Scaffold(
1225/// appBar: AppBar(title: const Text('My Page')),
1226/// body: Center(
1227/// child: TextButton(
1228/// child: const Text('POP'),
1229/// onPressed: () {
1230/// Navigator.pop(context);
1231/// },
1232/// ),
1233/// ),
1234/// );
1235/// },
1236/// ));
1237/// ```
1238///
1239/// The route defines its widget with a builder function instead of a
1240/// child widget because it will be built and rebuilt in different
1241/// contexts depending on when it's pushed and popped.
1242///
1243/// As you can see, the new route can be popped, revealing the app's home
1244/// page, with the Navigator's pop method:
1245///
1246/// ```dart
1247/// Navigator.pop(context);
1248/// ```
1249///
1250/// It usually isn't necessary to provide a widget that pops the Navigator
1251/// in a route with a [Scaffold] because the Scaffold automatically adds a
1252/// 'back' button to its AppBar. Pressing the back button causes
1253/// [Navigator.pop] to be called. On Android, pressing the system back
1254/// button does the same thing.
1255///
1256/// ### Using named navigator routes
1257///
1258/// Mobile apps often manage a large number of routes and it's often
1259/// easiest to refer to them by name. Route names, by convention,
1260/// use a path-like structure (for example, '/a/b/c').
1261/// The app's home page route is named '/' by default.
1262///
1263/// The [MaterialApp] can be created
1264/// with a [Map<String, WidgetBuilder>] which maps from a route's name to
1265/// a builder function that will create it. The [MaterialApp] uses this
1266/// map to create a value for its navigator's [onGenerateRoute] callback.
1267///
1268/// ```dart
1269/// void main() {
1270/// runApp(MaterialApp(
1271/// home: const MyAppHome(), // becomes the route named '/'
1272/// routes: <String, WidgetBuilder> {
1273/// '/a': (BuildContext context) => const MyPage(title: Text('page A')),
1274/// '/b': (BuildContext context) => const MyPage(title: Text('page B')),
1275/// '/c': (BuildContext context) => const MyPage(title: Text('page C')),
1276/// },
1277/// ));
1278/// }
1279/// ```
1280///
1281/// To show a route by name:
1282///
1283/// ```dart
1284/// Navigator.pushNamed(context, '/b');
1285/// ```
1286///
1287/// ### Routes can return a value
1288///
1289/// When a route is pushed to ask the user for a value, the value can be
1290/// returned via the [pop] method's result parameter.
1291///
1292/// Methods that push a route return a [Future]. The Future resolves when the
1293/// route is popped and the [Future]'s value is the [pop] method's `result`
1294/// parameter.
1295///
1296/// For example if we wanted to ask the user to press 'OK' to confirm an
1297/// operation we could `await` the result of [Navigator.push]:
1298///
1299/// ```dart
1300/// bool? value = await Navigator.push(context, MaterialPageRoute<bool>(
1301/// builder: (BuildContext context) {
1302/// return Center(
1303/// child: GestureDetector(
1304/// child: const Text('OK'),
1305/// onTap: () { Navigator.pop(context, true); }
1306/// ),
1307/// );
1308/// }
1309/// ));
1310/// ```
1311///
1312/// If the user presses 'OK' then value will be true. If the user backs
1313/// out of the route, for example by pressing the Scaffold's back button,
1314/// the value will be null.
1315///
1316/// When a route is used to return a value, the route's type parameter must
1317/// match the type of [pop]'s result. That's why we've used
1318/// `MaterialPageRoute<bool>` instead of `MaterialPageRoute<void>` or just
1319/// `MaterialPageRoute`. (If you prefer to not specify the types, though, that's
1320/// fine too.)
1321///
1322/// ### Popup routes
1323///
1324/// Routes don't have to obscure the entire screen. [PopupRoute]s cover the
1325/// screen with a [ModalRoute.barrierColor] that can be only partially opaque to
1326/// allow the current screen to show through. Popup routes are "modal" because
1327/// they block input to the widgets below.
1328///
1329/// There are functions which create and show popup routes. For
1330/// example: [showDialog], [showMenu], and [showModalBottomSheet]. These
1331/// functions return their pushed route's Future as described above.
1332/// Callers can await the returned value to take an action when the
1333/// route is popped, or to discover the route's value.
1334///
1335/// There are also widgets which create popup routes, like [PopupMenuButton] and
1336/// [DropdownButton]. These widgets create internal subclasses of PopupRoute
1337/// and use the Navigator's push and pop methods to show and dismiss them.
1338///
1339/// ### Custom routes
1340///
1341/// You can create your own subclass of one of the widget library route classes
1342/// like [PopupRoute], [ModalRoute], or [PageRoute], to control the animated
1343/// transition employed to show the route, the color and behavior of the route's
1344/// modal barrier, and other aspects of the route.
1345///
1346/// The [PageRouteBuilder] class makes it possible to define a custom route
1347/// in terms of callbacks. Here's an example that rotates and fades its child
1348/// when the route appears or disappears. This route does not obscure the entire
1349/// screen because it specifies `opaque: false`, just as a popup route does.
1350///
1351/// ```dart
1352/// Navigator.push(context, PageRouteBuilder<void>(
1353/// opaque: false,
1354/// pageBuilder: (BuildContext context, _, __) {
1355/// return const Center(child: Text('My PageRoute'));
1356/// },
1357/// transitionsBuilder: (___, Animation<double> animation, ____, Widget child) {
1358/// return FadeTransition(
1359/// opacity: animation,
1360/// child: RotationTransition(
1361/// turns: Tween<double>(begin: 0.5, end: 1.0).animate(animation),
1362/// child: child,
1363/// ),
1364/// );
1365/// }
1366/// ));
1367/// ```
1368///
1369/// The page route is built in two parts, the "page" and the
1370/// "transitions". The page becomes a descendant of the child passed to
1371/// the `transitionsBuilder` function. Typically the page is only built once,
1372/// because it doesn't depend on its animation parameters (elided with `_`
1373/// and `__` in this example). The transition is built on every frame
1374/// for its duration.
1375///
1376/// (In this example, `void` is used as the return type for the route, because
1377/// it does not return a value.)
1378///
1379/// ### Nesting Navigators
1380///
1381/// An app can use more than one [Navigator]. Nesting one [Navigator] below
1382/// another [Navigator] can be used to create an "inner journey" such as tabbed
1383/// navigation, user registration, store checkout, or other independent journeys
1384/// that represent a subsection of your overall application.
1385///
1386/// #### Example
1387///
1388/// It is standard practice for iOS apps to use tabbed navigation where each
1389/// tab maintains its own navigation history. Therefore, each tab has its own
1390/// [Navigator], creating a kind of "parallel navigation."
1391///
1392/// In addition to the parallel navigation of the tabs, it is still possible to
1393/// launch full-screen pages that completely cover the tabs. For example: an
1394/// on-boarding flow, or an alert dialog. Therefore, there must exist a "root"
1395/// [Navigator] that sits above the tab navigation. As a result, each of the
1396/// tab's [Navigator]s are actually nested [Navigator]s sitting below a single
1397/// root [Navigator].
1398///
1399/// In practice, the nested [Navigator]s for tabbed navigation sit in the
1400/// [WidgetsApp] and [CupertinoTabView] widgets and do not need to be explicitly
1401/// created or managed.
1402///
1403/// {@tool sample}
1404/// The following example demonstrates how a nested [Navigator] can be used to
1405/// present a standalone user registration journey.
1406///
1407/// Even though this example uses two [Navigator]s to demonstrate nested
1408/// [Navigator]s, a similar result is possible using only a single [Navigator].
1409///
1410/// Run this example with `flutter run --route=/signup` to start it with
1411/// the signup flow instead of on the home page.
1412///
1413/// ** See code in examples/api/lib/widgets/navigator/navigator.0.dart **
1414/// {@end-tool}
1415///
1416/// [Navigator.of] operates on the nearest ancestor [Navigator] from the given
1417/// [BuildContext]. Be sure to provide a [BuildContext] below the intended
1418/// [Navigator], especially in large `build` methods where nested [Navigator]s
1419/// are created. The [Builder] widget can be used to access a [BuildContext] at
1420/// a desired location in the widget subtree.
1421///
1422/// ### Finding the enclosing route
1423///
1424/// In the common case of a modal route, the enclosing route can be obtained
1425/// from inside a build method using [ModalRoute.of]. To determine if the
1426/// enclosing route is the active route (e.g. so that controls can be dimmed
1427/// when the route is not active), the [Route.isCurrent] property can be checked
1428/// on the returned route.
1429///
1430/// ## State Restoration
1431///
1432/// If provided with a [restorationScopeId] and when surrounded by a valid
1433/// [RestorationScope] the [Navigator] will restore its state by recreating
1434/// the current history stack of [Route]s during state restoration and by
1435/// restoring the internal state of those [Route]s. However, not all [Route]s
1436/// on the stack can be restored:
1437///
1438/// * [Page]-based routes restore their state if [Page.restorationId] is
1439/// provided.
1440/// * [Route]s added with the classic imperative API ([push], [pushNamed], and
1441/// friends) can never restore their state.
1442/// * A [Route] added with the restorable imperative API ([restorablePush],
1443/// [restorablePushNamed], and all other imperative methods with "restorable"
1444/// in their name) restores its state if all routes below it up to and
1445/// including the first [Page]-based route below it are restored. If there
1446/// is no [Page]-based route below it, it only restores its state if all
1447/// routes below it restore theirs.
1448///
1449/// If a [Route] is deemed restorable, the [Navigator] will set its
1450/// [Route.restorationScopeId] to a non-null value. Routes can use that ID to
1451/// store and restore their own state. As an example, the [ModalRoute] will
1452/// use this ID to create a [RestorationScope] for its content widgets.
1453class Navigator extends StatefulWidget {
1454 /// Creates a widget that maintains a stack-based history of child widgets.
1455 ///
1456 /// If the [pages] is not empty, the [onPopPage] must not be null.
1457 const Navigator({
1458 super.key,
1459 this.pages = const <Page<dynamic>>[],
1460 this.onPopPage,
1461 this.initialRoute,
1462 this.onGenerateInitialRoutes = Navigator.defaultGenerateInitialRoutes,
1463 this.onGenerateRoute,
1464 this.onUnknownRoute,
1465 this.transitionDelegate = const DefaultTransitionDelegate<dynamic>(),
1466 this.reportsRouteUpdateToEngine = false,
1467 this.clipBehavior = Clip.hardEdge,
1468 this.observers = const <NavigatorObserver>[],
1469 this.requestFocus = true,
1470 this.restorationScopeId,
1471 this.routeTraversalEdgeBehavior = kDefaultRouteTraversalEdgeBehavior,
1472 });
1473
1474 /// The list of pages with which to populate the history.
1475 ///
1476 /// Pages are turned into routes using [Page.createRoute] in a manner
1477 /// analogous to how [Widget]s are turned into [Element]s (and [State]s or
1478 /// [RenderObject]s) using [Widget.createElement] (and
1479 /// [StatefulWidget.createState] or [RenderObjectWidget.createRenderObject]).
1480 ///
1481 /// When this list is updated, the new list is compared to the previous
1482 /// list and the set of routes is updated accordingly.
1483 ///
1484 /// Some [Route]s do not correspond to [Page] objects, namely, those that are
1485 /// added to the history using the [Navigator] API ([push] and friends). A
1486 /// [Route] that does not correspond to a [Page] object is called a pageless
1487 /// route and is tied to the [Route] that _does_ correspond to a [Page] object
1488 /// that is below it in the history.
1489 ///
1490 /// Pages that are added or removed may be animated as controlled by the
1491 /// [transitionDelegate]. If a page is removed that had other pageless routes
1492 /// pushed on top of it using [push] and friends, those pageless routes are
1493 /// also removed with or without animation as determined by the
1494 /// [transitionDelegate].
1495 ///
1496 /// To use this API, an [onPopPage] callback must also be provided to properly
1497 /// clean up this list if a page has been popped.
1498 ///
1499 /// If [initialRoute] is non-null when the widget is first created, then
1500 /// [onGenerateInitialRoutes] is used to generate routes that are above those
1501 /// corresponding to [pages] in the initial history.
1502 final List<Page<dynamic>> pages;
1503
1504 /// Called when [pop] is invoked but the current [Route] corresponds to a
1505 /// [Page] found in the [pages] list.
1506 ///
1507 /// The `result` argument is the value with which the route is to complete
1508 /// (e.g. the value returned from a dialog).
1509 ///
1510 /// This callback is responsible for calling [Route.didPop] and returning
1511 /// whether this pop is successful.
1512 ///
1513 /// The [Navigator] widget should be rebuilt with a [pages] list that does not
1514 /// contain the [Page] for the given [Route]. The next time the [pages] list
1515 /// is updated, if the [Page] corresponding to this [Route] is still present,
1516 /// it will be interpreted as a new route to display.
1517 final PopPageCallback? onPopPage;
1518
1519 /// The delegate used for deciding how routes transition in or off the screen
1520 /// during the [pages] updates.
1521 ///
1522 /// Defaults to [DefaultTransitionDelegate].
1523 final TransitionDelegate<dynamic> transitionDelegate;
1524
1525 /// The name of the first route to show.
1526 ///
1527 /// Defaults to [Navigator.defaultRouteName].
1528 ///
1529 /// The value is interpreted according to [onGenerateInitialRoutes], which
1530 /// defaults to [defaultGenerateInitialRoutes].
1531 ///
1532 /// Changing the [initialRoute] will have no effect, as it only controls the
1533 /// _initial_ route. To change the route while the application is running, use
1534 /// the static functions on this class, such as [push] or [replace].
1535 final String? initialRoute;
1536
1537 /// Called to generate a route for a given [RouteSettings].
1538 final RouteFactory? onGenerateRoute;
1539
1540 /// Called when [onGenerateRoute] fails to generate a route.
1541 ///
1542 /// This callback is typically used for error handling. For example, this
1543 /// callback might always generate a "not found" page that describes the route
1544 /// that wasn't found.
1545 ///
1546 /// Unknown routes can arise either from errors in the app or from external
1547 /// requests to push routes, such as from Android intents.
1548 final RouteFactory? onUnknownRoute;
1549
1550 /// A list of observers for this navigator.
1551 final List<NavigatorObserver> observers;
1552
1553 /// Restoration ID to save and restore the state of the navigator, including
1554 /// its history.
1555 ///
1556 /// {@template flutter.widgets.navigator.restorationScopeId}
1557 /// If a restoration ID is provided, the navigator will persist its internal
1558 /// state (including the route history as well as the restorable state of the
1559 /// routes) and restore it during state restoration.
1560 ///
1561 /// If no restoration ID is provided, the route history stack will not be
1562 /// restored and state restoration is disabled for the individual routes as
1563 /// well.
1564 ///
1565 /// The state is persisted in a [RestorationBucket] claimed from
1566 /// the surrounding [RestorationScope] using the provided restoration ID.
1567 /// Within that bucket, the [Navigator] also creates a new [RestorationScope]
1568 /// for its children (the [Route]s).
1569 ///
1570 /// See also:
1571 ///
1572 /// * [RestorationManager], which explains how state restoration works in
1573 /// Flutter.
1574 /// * [RestorationMixin], which contains a runnable code sample showcasing
1575 /// state restoration in Flutter.
1576 /// * [Navigator], which explains under the heading "state restoration"
1577 /// how and under what conditions the navigator restores its state.
1578 /// * [Navigator.restorablePush], which includes an example showcasing how
1579 /// to push a restorable route unto the navigator.
1580 /// {@endtemplate}
1581 final String? restorationScopeId;
1582
1583 /// Controls the transfer of focus beyond the first and the last items of a
1584 /// focus scope that defines focus traversal of widgets within a route.
1585 ///
1586 /// {@template flutter.widgets.navigator.routeTraversalEdgeBehavior}
1587 /// The focus inside routes installed in the top of the app affects how
1588 /// the app behaves with respect to the platform content surrounding it.
1589 /// For example, on the web, an app is at a minimum surrounded by browser UI,
1590 /// such as the address bar, browser tabs, and more. The user should be able
1591 /// to reach browser UI using normal focus shortcuts. Similarly, if the app
1592 /// is embedded within an `<iframe>` or inside a custom element, it should
1593 /// be able to participate in the overall focus traversal, including elements
1594 /// not rendered by Flutter.
1595 /// {@endtemplate}
1596 final TraversalEdgeBehavior routeTraversalEdgeBehavior;
1597
1598 /// The name for the default route of the application.
1599 ///
1600 /// See also:
1601 ///
1602 /// * [dart:ui.PlatformDispatcher.defaultRouteName], which reflects the route that the
1603 /// application was started with.
1604 static const String defaultRouteName = '/';
1605
1606 /// Called when the widget is created to generate the initial list of [Route]
1607 /// objects if [initialRoute] is not null.
1608 ///
1609 /// Defaults to [defaultGenerateInitialRoutes].
1610 ///
1611 /// The [NavigatorState] and [initialRoute] will be passed to the callback.
1612 /// The callback must return a list of [Route] objects with which the history
1613 /// will be primed.
1614 ///
1615 /// When parsing the initialRoute, if there's any chance that the it may
1616 /// contain complex characters, it's best to use the
1617 /// [characters](https://pub.dev/packages/characters) API. This will ensure
1618 /// that extended grapheme clusters and surrogate pairs are treated as single
1619 /// characters by the code, the same way that they appear to the user. For
1620 /// example, the string "👨‍👩‍👦" appears to the user as a single
1621 /// character and `string.characters.length` intuitively returns 1. On the
1622 /// other hand, `string.length` returns 8, and `string.runes.length` returns
1623 /// 5!
1624 final RouteListFactory onGenerateInitialRoutes;
1625
1626 /// Whether this navigator should report route update message back to the
1627 /// engine when the top-most route changes.
1628 ///
1629 /// If the property is set to true, this navigator automatically sends the
1630 /// route update message to the engine when it detects top-most route changes.
1631 /// The messages are used by the web engine to update the browser URL bar.
1632 ///
1633 /// If the property is set to true when the [Navigator] is first created,
1634 /// single-entry history mode is requested using
1635 /// [SystemNavigator.selectSingleEntryHistory]. This means this property
1636 /// should not be used at the same time as [PlatformRouteInformationProvider]
1637 /// is used with a [Router] (including when used with [MaterialApp.router],
1638 /// for example).
1639 ///
1640 /// If there are multiple navigators in the widget tree, at most one of them
1641 /// can set this property to true (typically, the top-most one created from
1642 /// the [WidgetsApp]). Otherwise, the web engine may receive multiple route
1643 /// update messages from different navigators and fail to update the URL
1644 /// bar.
1645 ///
1646 /// Defaults to false.
1647 final bool reportsRouteUpdateToEngine;
1648
1649 /// {@macro flutter.material.Material.clipBehavior}
1650 ///
1651 /// In cases where clipping is not desired, consider setting this property to
1652 /// [Clip.none].
1653 ///
1654 /// Defaults to [Clip.hardEdge].
1655 final Clip clipBehavior;
1656
1657 /// Whether or not the navigator and it's new topmost route should request focus
1658 /// when the new route is pushed onto the navigator.
1659 ///
1660 /// Defaults to true.
1661 final bool requestFocus;
1662
1663 /// Push a named route onto the navigator that most tightly encloses the given
1664 /// context.
1665 ///
1666 /// {@template flutter.widgets.navigator.pushNamed}
1667 /// The route name will be passed to the [Navigator.onGenerateRoute]
1668 /// callback. The returned route will be pushed into the navigator.
1669 ///
1670 /// The new route and the previous route (if any) are notified (see
1671 /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
1672 /// [Navigator.observers], they will be notified as well (see
1673 /// [NavigatorObserver.didPush]).
1674 ///
1675 /// Ongoing gestures within the current route are canceled when a new route is
1676 /// pushed.
1677 ///
1678 /// The `T` type argument is the type of the return value of the route.
1679 ///
1680 /// To use [pushNamed], an [Navigator.onGenerateRoute] callback must be
1681 /// provided,
1682 /// {@endtemplate}
1683 ///
1684 /// {@template flutter.widgets.navigator.pushNamed.returnValue}
1685 /// Returns a [Future] that completes to the `result` value passed to [pop]
1686 /// when the pushed route is popped off the navigator.
1687 /// {@endtemplate}
1688 ///
1689 /// {@template flutter.widgets.Navigator.pushNamed}
1690 /// The provided `arguments` are passed to the pushed route via
1691 /// [RouteSettings.arguments]. Any object can be passed as `arguments` (e.g. a
1692 /// [String], [int], or an instance of a custom `MyRouteArguments` class).
1693 /// Often, a [Map] is used to pass key-value pairs.
1694 ///
1695 /// The `arguments` may be used in [Navigator.onGenerateRoute] or
1696 /// [Navigator.onUnknownRoute] to construct the route.
1697 /// {@endtemplate}
1698 ///
1699 /// {@tool snippet}
1700 ///
1701 /// Typical usage is as follows:
1702 ///
1703 /// ```dart
1704 /// void _didPushButton() {
1705 /// Navigator.pushNamed(context, '/settings');
1706 /// }
1707 /// ```
1708 /// {@end-tool}
1709 ///
1710 /// {@tool snippet}
1711 ///
1712 /// The following example shows how to pass additional `arguments` to the
1713 /// route:
1714 ///
1715 /// ```dart
1716 /// void _showBerlinWeather() {
1717 /// Navigator.pushNamed(
1718 /// context,
1719 /// '/weather',
1720 /// arguments: <String, String>{
1721 /// 'city': 'Berlin',
1722 /// 'country': 'Germany',
1723 /// },
1724 /// );
1725 /// }
1726 /// ```
1727 /// {@end-tool}
1728 ///
1729 /// {@tool snippet}
1730 ///
1731 /// The following example shows how to pass a custom Object to the route:
1732 ///
1733 /// ```dart
1734 /// class WeatherRouteArguments {
1735 /// WeatherRouteArguments({ required this.city, required this.country });
1736 /// final String city;
1737 /// final String country;
1738 ///
1739 /// bool get isGermanCapital {
1740 /// return country == 'Germany' && city == 'Berlin';
1741 /// }
1742 /// }
1743 ///
1744 /// void _showWeather() {
1745 /// Navigator.pushNamed(
1746 /// context,
1747 /// '/weather',
1748 /// arguments: WeatherRouteArguments(city: 'Berlin', country: 'Germany'),
1749 /// );
1750 /// }
1751 /// ```
1752 /// {@end-tool}
1753 ///
1754 /// See also:
1755 ///
1756 /// * [restorablePushNamed], which pushes a route that can be restored
1757 /// during state restoration.
1758 @optionalTypeArgs
1759 static Future<T?> pushNamed<T extends Object?>(
1760 BuildContext context,
1761 String routeName, {
1762 Object? arguments,
1763 }) {
1764 return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
1765 }
1766
1767 /// Push a named route onto the navigator that most tightly encloses the given
1768 /// context.
1769 ///
1770 /// {@template flutter.widgets.navigator.restorablePushNamed}
1771 /// Unlike [Route]s pushed via [pushNamed], [Route]s pushed with this method
1772 /// are restored during state restoration according to the rules outlined
1773 /// in the "State Restoration" section of [Navigator].
1774 /// {@endtemplate}
1775 ///
1776 /// {@macro flutter.widgets.navigator.pushNamed}
1777 ///
1778 /// {@template flutter.widgets.Navigator.restorablePushNamed.arguments}
1779 /// The provided `arguments` are passed to the pushed route via
1780 /// [RouteSettings.arguments]. Any object that is serializable via the
1781 /// [StandardMessageCodec] can be passed as `arguments`. Often, a Map is used
1782 /// to pass key-value pairs.
1783 ///
1784 /// The arguments may be used in [Navigator.onGenerateRoute] or
1785 /// [Navigator.onUnknownRoute] to construct the route.
1786 /// {@endtemplate}
1787 ///
1788 /// {@template flutter.widgets.Navigator.restorablePushNamed.returnValue}
1789 /// The method returns an opaque ID for the pushed route that can be used by
1790 /// the [RestorableRouteFuture] to gain access to the actual [Route] object
1791 /// added to the navigator and its return value. You can ignore the return
1792 /// value of this method, if you do not care about the route object or the
1793 /// route's return value.
1794 /// {@endtemplate}
1795 ///
1796 /// {@tool snippet}
1797 ///
1798 /// Typical usage is as follows:
1799 ///
1800 /// ```dart
1801 /// void _showParisWeather() {
1802 /// Navigator.restorablePushNamed(
1803 /// context,
1804 /// '/weather',
1805 /// arguments: <String, String>{
1806 /// 'city': 'Paris',
1807 /// 'country': 'France',
1808 /// },
1809 /// );
1810 /// }
1811 /// ```
1812 /// {@end-tool}
1813 @optionalTypeArgs
1814 static String restorablePushNamed<T extends Object?>(
1815 BuildContext context,
1816 String routeName, {
1817 Object? arguments,
1818 }) {
1819 return Navigator.of(context).restorablePushNamed<T>(routeName, arguments: arguments);
1820 }
1821
1822 /// Replace the current route of the navigator that most tightly encloses the
1823 /// given context by pushing the route named [routeName] and then disposing
1824 /// the previous route once the new route has finished animating in.
1825 ///
1826 /// {@template flutter.widgets.navigator.pushReplacementNamed}
1827 /// If non-null, `result` will be used as the result of the route that is
1828 /// removed; the future that had been returned from pushing that old route
1829 /// will complete with `result`. Routes such as dialogs or popup menus
1830 /// typically use this mechanism to return the value selected by the user to
1831 /// the widget that created their route. The type of `result`, if provided,
1832 /// must match the type argument of the class of the old route (`TO`).
1833 ///
1834 /// The route name will be passed to the [Navigator.onGenerateRoute]
1835 /// callback. The returned route will be pushed into the navigator.
1836 ///
1837 /// The new route and the route below the removed route are notified (see
1838 /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
1839 /// [Navigator.observers], they will be notified as well (see
1840 /// [NavigatorObserver.didReplace]). The removed route is notified once the
1841 /// new route has finished animating (see [Route.didComplete]). The removed
1842 /// route's exit animation is not run (see [popAndPushNamed] for a variant
1843 /// that does animated the removed route).
1844 ///
1845 /// Ongoing gestures within the current route are canceled when a new route is
1846 /// pushed.
1847 ///
1848 /// The `T` type argument is the type of the return value of the new route,
1849 /// and `TO` is the type of the return value of the old route.
1850 ///
1851 /// To use [pushReplacementNamed], a [Navigator.onGenerateRoute] callback must
1852 /// be provided.
1853 /// {@endtemplate}
1854 ///
1855 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
1856 ///
1857 /// {@macro flutter.widgets.Navigator.pushNamed}
1858 ///
1859 /// {@tool snippet}
1860 ///
1861 /// Typical usage is as follows:
1862 ///
1863 /// ```dart
1864 /// void _switchToBrightness() {
1865 /// Navigator.pushReplacementNamed(context, '/settings/brightness');
1866 /// }
1867 /// ```
1868 /// {@end-tool}
1869 ///
1870 /// See also:
1871 ///
1872 /// * [restorablePushReplacementNamed], which pushes a replacement route that
1873 /// can be restored during state restoration.
1874 @optionalTypeArgs
1875 static Future<T?> pushReplacementNamed<T extends Object?, TO extends Object?>(
1876 BuildContext context,
1877 String routeName, {
1878 TO? result,
1879 Object? arguments,
1880 }) {
1881 return Navigator.of(context).pushReplacementNamed<T, TO>(routeName, arguments: arguments, result: result);
1882 }
1883
1884 /// Replace the current route of the navigator that most tightly encloses the
1885 /// given context by pushing the route named [routeName] and then disposing
1886 /// the previous route once the new route has finished animating in.
1887 ///
1888 /// {@template flutter.widgets.navigator.restorablePushReplacementNamed}
1889 /// Unlike [Route]s pushed via [pushReplacementNamed], [Route]s pushed with
1890 /// this method are restored during state restoration according to the rules
1891 /// outlined in the "State Restoration" section of [Navigator].
1892 /// {@endtemplate}
1893 ///
1894 /// {@macro flutter.widgets.navigator.pushReplacementNamed}
1895 ///
1896 /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
1897 ///
1898 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
1899 ///
1900 /// {@tool snippet}
1901 ///
1902 /// Typical usage is as follows:
1903 ///
1904 /// ```dart
1905 /// void _switchToAudioVolume() {
1906 /// Navigator.restorablePushReplacementNamed(context, '/settings/volume');
1907 /// }
1908 /// ```
1909 /// {@end-tool}
1910 @optionalTypeArgs
1911 static String restorablePushReplacementNamed<T extends Object?, TO extends Object?>(
1912 BuildContext context,
1913 String routeName, {
1914 TO? result,
1915 Object? arguments,
1916 }) {
1917 return Navigator.of(context).restorablePushReplacementNamed<T, TO>(routeName, arguments: arguments, result: result);
1918 }
1919
1920 /// Pop the current route off the navigator that most tightly encloses the
1921 /// given context and push a named route in its place.
1922 ///
1923 /// {@template flutter.widgets.navigator.popAndPushNamed}
1924 /// The popping of the previous route is handled as per [pop].
1925 ///
1926 /// The new route's name will be passed to the [Navigator.onGenerateRoute]
1927 /// callback. The returned route will be pushed into the navigator.
1928 ///
1929 /// The new route, the old route, and the route below the old route (if any)
1930 /// are all notified (see [Route.didPop], [Route.didComplete],
1931 /// [Route.didPopNext], [Route.didPush], and [Route.didChangeNext]). If the
1932 /// [Navigator] has any [Navigator.observers], they will be notified as well
1933 /// (see [NavigatorObserver.didPop] and [NavigatorObserver.didPush]). The
1934 /// animations for the pop and the push are performed simultaneously, so the
1935 /// route below may be briefly visible even if both the old route and the new
1936 /// route are opaque (see [TransitionRoute.opaque]).
1937 ///
1938 /// Ongoing gestures within the current route are canceled when a new route is
1939 /// pushed.
1940 ///
1941 /// The `T` type argument is the type of the return value of the new route,
1942 /// and `TO` is the return value type of the old route.
1943 ///
1944 /// To use [popAndPushNamed], a [Navigator.onGenerateRoute] callback must be provided.
1945 /// {@endtemplate}
1946 ///
1947 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
1948 ///
1949 /// {@macro flutter.widgets.Navigator.pushNamed}
1950 ///
1951 /// {@tool snippet}
1952 ///
1953 /// Typical usage is as follows:
1954 ///
1955 /// ```dart
1956 /// void _selectAccessibility() {
1957 /// Navigator.popAndPushNamed(context, '/settings/accessibility');
1958 /// }
1959 /// ```
1960 /// {@end-tool}
1961 ///
1962 /// See also:
1963 ///
1964 /// * [restorablePopAndPushNamed], which pushes a new route that can be
1965 /// restored during state restoration.
1966 @optionalTypeArgs
1967 static Future<T?> popAndPushNamed<T extends Object?, TO extends Object?>(
1968 BuildContext context,
1969 String routeName, {
1970 TO? result,
1971 Object? arguments,
1972 }) {
1973 return Navigator.of(context).popAndPushNamed<T, TO>(routeName, arguments: arguments, result: result);
1974 }
1975
1976 /// Pop the current route off the navigator that most tightly encloses the
1977 /// given context and push a named route in its place.
1978 ///
1979 /// {@template flutter.widgets.navigator.restorablePopAndPushNamed}
1980 /// Unlike [Route]s pushed via [popAndPushNamed], [Route]s pushed with
1981 /// this method are restored during state restoration according to the rules
1982 /// outlined in the "State Restoration" section of [Navigator].
1983 /// {@endtemplate}
1984 ///
1985 /// {@macro flutter.widgets.navigator.popAndPushNamed}
1986 ///
1987 /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
1988 ///
1989 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
1990 ///
1991 /// {@tool snippet}
1992 ///
1993 /// Typical usage is as follows:
1994 ///
1995 /// ```dart
1996 /// void _selectNetwork() {
1997 /// Navigator.restorablePopAndPushNamed(context, '/settings/network');
1998 /// }
1999 /// ```
2000 /// {@end-tool}
2001 @optionalTypeArgs
2002 static String restorablePopAndPushNamed<T extends Object?, TO extends Object?>(
2003 BuildContext context,
2004 String routeName, {
2005 TO? result,
2006 Object? arguments,
2007 }) {
2008 return Navigator.of(context).restorablePopAndPushNamed<T, TO>(routeName, arguments: arguments, result: result);
2009 }
2010
2011 /// Push the route with the given name onto the navigator that most tightly
2012 /// encloses the given context, and then remove all the previous routes until
2013 /// the `predicate` returns true.
2014 ///
2015 /// {@template flutter.widgets.navigator.pushNamedAndRemoveUntil}
2016 /// The predicate may be applied to the same route more than once if
2017 /// [Route.willHandlePopInternally] is true.
2018 ///
2019 /// To remove routes until a route with a certain name, use the
2020 /// [RoutePredicate] returned from [ModalRoute.withName].
2021 ///
2022 /// To remove all the routes below the pushed route, use a [RoutePredicate]
2023 /// that always returns false (e.g. `(Route<dynamic> route) => false`).
2024 ///
2025 /// The removed routes are removed without being completed, so this method
2026 /// does not take a return value argument.
2027 ///
2028 /// The new route's name (`routeName`) will be passed to the
2029 /// [Navigator.onGenerateRoute] callback. The returned route will be pushed
2030 /// into the navigator.
2031 ///
2032 /// The new route and the route below the bottommost removed route (which
2033 /// becomes the route below the new route) are notified (see [Route.didPush]
2034 /// and [Route.didChangeNext]). If the [Navigator] has any
2035 /// [Navigator.observers], they will be notified as well (see
2036 /// [NavigatorObserver.didPush] and [NavigatorObserver.didRemove]). The
2037 /// removed routes are disposed, without being notified, once the new route
2038 /// has finished animating. The futures that had been returned from pushing
2039 /// those routes will not complete.
2040 ///
2041 /// Ongoing gestures within the current route are canceled when a new route is
2042 /// pushed.
2043 ///
2044 /// The `T` type argument is the type of the return value of the new route.
2045 ///
2046 /// To use [pushNamedAndRemoveUntil], an [Navigator.onGenerateRoute] callback
2047 /// must be provided.
2048 /// {@endtemplate}
2049 ///
2050 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
2051 ///
2052 /// {@macro flutter.widgets.Navigator.pushNamed}
2053 ///
2054 /// {@tool snippet}
2055 ///
2056 /// Typical usage is as follows:
2057 ///
2058 /// ```dart
2059 /// void _resetToCalendar() {
2060 /// Navigator.pushNamedAndRemoveUntil(context, '/calendar', ModalRoute.withName('/'));
2061 /// }
2062 /// ```
2063 /// {@end-tool}
2064 ///
2065 /// See also:
2066 ///
2067 /// * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
2068 /// be restored during state restoration.
2069 @optionalTypeArgs
2070 static Future<T?> pushNamedAndRemoveUntil<T extends Object?>(
2071 BuildContext context,
2072 String newRouteName,
2073 RoutePredicate predicate, {
2074 Object? arguments,
2075 }) {
2076 return Navigator.of(context).pushNamedAndRemoveUntil<T>(newRouteName, predicate, arguments: arguments);
2077 }
2078
2079 /// Push the route with the given name onto the navigator that most tightly
2080 /// encloses the given context, and then remove all the previous routes until
2081 /// the `predicate` returns true.
2082 ///
2083 /// {@template flutter.widgets.navigator.restorablePushNamedAndRemoveUntil}
2084 /// Unlike [Route]s pushed via [pushNamedAndRemoveUntil], [Route]s pushed with
2085 /// this method are restored during state restoration according to the rules
2086 /// outlined in the "State Restoration" section of [Navigator].
2087 /// {@endtemplate}
2088 ///
2089 /// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
2090 ///
2091 /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
2092 ///
2093 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
2094 ///
2095 /// {@tool snippet}
2096 ///
2097 /// Typical usage is as follows:
2098 ///
2099 /// ```dart
2100 /// void _resetToOverview() {
2101 /// Navigator.restorablePushNamedAndRemoveUntil(context, '/overview', ModalRoute.withName('/'));
2102 /// }
2103 /// ```
2104 /// {@end-tool}
2105 @optionalTypeArgs
2106 static String restorablePushNamedAndRemoveUntil<T extends Object?>(
2107 BuildContext context,
2108 String newRouteName,
2109 RoutePredicate predicate, {
2110 Object? arguments,
2111 }) {
2112 return Navigator.of(context).restorablePushNamedAndRemoveUntil<T>(newRouteName, predicate, arguments: arguments);
2113 }
2114
2115 /// Push the given route onto the navigator that most tightly encloses the
2116 /// given context.
2117 ///
2118 /// {@template flutter.widgets.navigator.push}
2119 /// The new route and the previous route (if any) are notified (see
2120 /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
2121 /// [Navigator.observers], they will be notified as well (see
2122 /// [NavigatorObserver.didPush]).
2123 ///
2124 /// Ongoing gestures within the current route are canceled when a new route is
2125 /// pushed.
2126 ///
2127 /// The `T` type argument is the type of the return value of the route.
2128 /// {@endtemplate}
2129 ///
2130 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
2131 ///
2132 /// {@tool snippet}
2133 ///
2134 /// Typical usage is as follows:
2135 ///
2136 /// ```dart
2137 /// void _openMyPage() {
2138 /// Navigator.push<void>(
2139 /// context,
2140 /// MaterialPageRoute<void>(
2141 /// builder: (BuildContext context) => const MyPage(),
2142 /// ),
2143 /// );
2144 /// }
2145 /// ```
2146 /// {@end-tool}
2147 ///
2148 /// See also:
2149 ///
2150 /// * [restorablePush], which pushes a route that can be restored during
2151 /// state restoration.
2152 @optionalTypeArgs
2153 static Future<T?> push<T extends Object?>(BuildContext context, Route<T> route) {
2154 return Navigator.of(context).push(route);
2155 }
2156
2157 /// Push a new route onto the navigator that most tightly encloses the
2158 /// given context.
2159 ///
2160 /// {@template flutter.widgets.navigator.restorablePush}
2161 /// Unlike [Route]s pushed via [push], [Route]s pushed with this method are
2162 /// restored during state restoration according to the rules outlined in the
2163 /// "State Restoration" section of [Navigator].
2164 /// {@endtemplate}
2165 ///
2166 /// {@macro flutter.widgets.navigator.push}
2167 ///
2168 /// {@template flutter.widgets.Navigator.restorablePush}
2169 /// The method takes a [RestorableRouteBuilder] as argument, which must be a
2170 /// _static_ function annotated with `@pragma('vm:entry-point')`. It must
2171 /// instantiate and return a new [Route] object that will be added to the
2172 /// navigator. The provided `arguments` object is passed to the
2173 /// `routeBuilder`. The navigator calls the static `routeBuilder` function
2174 /// again during state restoration to re-create the route object.
2175 ///
2176 /// Any object that is serializable via the [StandardMessageCodec] can be
2177 /// passed as `arguments`. Often, a Map is used to pass key-value pairs.
2178 /// {@endtemplate}
2179 ///
2180 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
2181 ///
2182 /// {@tool dartpad}
2183 /// Typical usage is as follows:
2184 ///
2185 /// ** See code in examples/api/lib/widgets/navigator/navigator.restorable_push.0.dart **
2186 /// {@end-tool}
2187 @optionalTypeArgs
2188 static String restorablePush<T extends Object?>(BuildContext context, RestorableRouteBuilder<T> routeBuilder, {Object? arguments}) {
2189 return Navigator.of(context).restorablePush(routeBuilder, arguments: arguments);
2190 }
2191
2192 /// Replace the current route of the navigator that most tightly encloses the
2193 /// given context by pushing the given route and then disposing the previous
2194 /// route once the new route has finished animating in.
2195 ///
2196 /// {@template flutter.widgets.navigator.pushReplacement}
2197 /// If non-null, `result` will be used as the result of the route that is
2198 /// removed; the future that had been returned from pushing that old route will
2199 /// complete with `result`. Routes such as dialogs or popup menus typically
2200 /// use this mechanism to return the value selected by the user to the widget
2201 /// that created their route. The type of `result`, if provided, must match
2202 /// the type argument of the class of the old route (`TO`).
2203 ///
2204 /// The new route and the route below the removed route are notified (see
2205 /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
2206 /// [Navigator.observers], they will be notified as well (see
2207 /// [NavigatorObserver.didReplace]). The removed route is notified once the
2208 /// new route has finished animating (see [Route.didComplete]).
2209 ///
2210 /// Ongoing gestures within the current route are canceled when a new route is
2211 /// pushed.
2212 ///
2213 /// The `T` type argument is the type of the return value of the new route,
2214 /// and `TO` is the type of the return value of the old route.
2215 /// {@endtemplate}
2216 ///
2217 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
2218 ///
2219 /// {@tool snippet}
2220 ///
2221 /// Typical usage is as follows:
2222 ///
2223 /// ```dart
2224 /// void _completeLogin() {
2225 /// Navigator.pushReplacement<void, void>(
2226 /// context,
2227 /// MaterialPageRoute<void>(
2228 /// builder: (BuildContext context) => const MyHomePage(),
2229 /// ),
2230 /// );
2231 /// }
2232 /// ```
2233 /// {@end-tool}
2234 ///
2235 /// See also:
2236 ///
2237 /// * [restorablePushReplacement], which pushes a replacement route that can
2238 /// be restored during state restoration.
2239 @optionalTypeArgs
2240 static Future<T?> pushReplacement<T extends Object?, TO extends Object?>(BuildContext context, Route<T> newRoute, { TO? result }) {
2241 return Navigator.of(context).pushReplacement<T, TO>(newRoute, result: result);
2242 }
2243
2244 /// Replace the current route of the navigator that most tightly encloses the
2245 /// given context by pushing a new route and then disposing the previous
2246 /// route once the new route has finished animating in.
2247 ///
2248 /// {@template flutter.widgets.navigator.restorablePushReplacement}
2249 /// Unlike [Route]s pushed via [pushReplacement], [Route]s pushed with this
2250 /// method are restored during state restoration according to the rules
2251 /// outlined in the "State Restoration" section of [Navigator].
2252 /// {@endtemplate}
2253 ///
2254 /// {@macro flutter.widgets.navigator.pushReplacement}
2255 ///
2256 /// {@macro flutter.widgets.Navigator.restorablePush}
2257 ///
2258 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
2259 ///
2260 /// {@tool dartpad}
2261 /// Typical usage is as follows:
2262 ///
2263 /// ** See code in examples/api/lib/widgets/navigator/navigator.restorable_push_replacement.0.dart **
2264 /// {@end-tool}
2265 @optionalTypeArgs
2266 static String restorablePushReplacement<T extends Object?, TO extends Object?>(BuildContext context, RestorableRouteBuilder<T> routeBuilder, { TO? result, Object? arguments }) {
2267 return Navigator.of(context).restorablePushReplacement<T, TO>(routeBuilder, result: result, arguments: arguments);
2268 }
2269
2270 /// Push the given route onto the navigator that most tightly encloses the
2271 /// given context, and then remove all the previous routes until the
2272 /// `predicate` returns true.
2273 ///
2274 /// {@template flutter.widgets.navigator.pushAndRemoveUntil}
2275 /// The predicate may be applied to the same route more than once if
2276 /// [Route.willHandlePopInternally] is true.
2277 ///
2278 /// To remove routes until a route with a certain name, use the
2279 /// [RoutePredicate] returned from [ModalRoute.withName].
2280 ///
2281 /// To remove all the routes below the pushed route, use a [RoutePredicate]
2282 /// that always returns false (e.g. `(Route<dynamic> route) => false`).
2283 ///
2284 /// The removed routes are removed without being completed, so this method
2285 /// does not take a return value argument.
2286 ///
2287 /// The newly pushed route and its preceding route are notified for
2288 /// [Route.didPush]. After removal, the new route and its new preceding route,
2289 /// (the route below the bottommost removed route) are notified through
2290 /// [Route.didChangeNext]). If the [Navigator] has any [Navigator.observers],
2291 /// they will be notified as well (see [NavigatorObserver.didPush] and
2292 /// [NavigatorObserver.didRemove]). The removed routes are disposed of and
2293 /// notified, once the new route has finished animating. The futures that had
2294 /// been returned from pushing those routes will not complete.
2295 ///
2296 /// Ongoing gestures within the current route are canceled when a new route is
2297 /// pushed.
2298 ///
2299 /// The `T` type argument is the type of the return value of the new route.
2300 /// {@endtemplate}
2301 ///
2302 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
2303 ///
2304 /// {@tool snippet}
2305 ///
2306 /// Typical usage is as follows:
2307 ///
2308 /// ```dart
2309 /// void _finishAccountCreation() {
2310 /// Navigator.pushAndRemoveUntil<void>(
2311 /// context,
2312 /// MaterialPageRoute<void>(builder: (BuildContext context) => const MyHomePage()),
2313 /// ModalRoute.withName('/'),
2314 /// );
2315 /// }
2316 /// ```
2317 /// {@end-tool}
2318 ///
2319 /// See also:
2320 ///
2321 /// * [restorablePushAndRemoveUntil], which pushes a route that can be
2322 /// restored during state restoration.
2323 @optionalTypeArgs
2324 static Future<T?> pushAndRemoveUntil<T extends Object?>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) {
2325 return Navigator.of(context).pushAndRemoveUntil<T>(newRoute, predicate);
2326 }
2327
2328 /// Push a new route onto the navigator that most tightly encloses the
2329 /// given context, and then remove all the previous routes until the
2330 /// `predicate` returns true.
2331 ///
2332 /// {@template flutter.widgets.navigator.restorablePushAndRemoveUntil}
2333 /// Unlike [Route]s pushed via [pushAndRemoveUntil], [Route]s pushed with this
2334 /// method are restored during state restoration according to the rules
2335 /// outlined in the "State Restoration" section of [Navigator].
2336 /// {@endtemplate}
2337 ///
2338 /// {@macro flutter.widgets.navigator.pushAndRemoveUntil}
2339 ///
2340 /// {@macro flutter.widgets.Navigator.restorablePush}
2341 ///
2342 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
2343 ///
2344 /// {@tool dartpad}
2345 /// Typical usage is as follows:
2346 ///
2347 /// ** See code in examples/api/lib/widgets/navigator/navigator.restorable_push_and_remove_until.0.dart **
2348 /// {@end-tool}
2349 @optionalTypeArgs
2350 static String restorablePushAndRemoveUntil<T extends Object?>(BuildContext context, RestorableRouteBuilder<T> newRouteBuilder, RoutePredicate predicate, {Object? arguments}) {
2351 return Navigator.of(context).restorablePushAndRemoveUntil<T>(newRouteBuilder, predicate, arguments: arguments);
2352 }
2353
2354 /// Replaces a route on the navigator that most tightly encloses the given
2355 /// context with a new route.
2356 ///
2357 /// {@template flutter.widgets.navigator.replace}
2358 /// The old route must not be currently visible, as this method skips the
2359 /// animations and therefore the removal would be jarring if it was visible.
2360 /// To replace the top-most route, consider [pushReplacement] instead, which
2361 /// _does_ animate the new route, and delays removing the old route until the
2362 /// new route has finished animating.
2363 ///
2364 /// The removed route is removed without being completed, so this method does
2365 /// not take a return value argument.
2366 ///
2367 /// The new route, the route below the new route (if any), and the route above
2368 /// the new route, are all notified (see [Route.didReplace],
2369 /// [Route.didChangeNext], and [Route.didChangePrevious]). If the [Navigator]
2370 /// has any [Navigator.observers], they will be notified as well (see
2371 /// [NavigatorObserver.didReplace]). The removed route is disposed without
2372 /// being notified. The future that had been returned from pushing that routes
2373 /// will not complete.
2374 ///
2375 /// This can be useful in combination with [removeRouteBelow] when building a
2376 /// non-linear user experience.
2377 ///
2378 /// The `T` type argument is the type of the return value of the new route.
2379 /// {@endtemplate}
2380 ///
2381 /// See also:
2382 ///
2383 /// * [replaceRouteBelow], which is the same but identifies the route to be
2384 /// removed by reference to the route above it, rather than directly.
2385 /// * [restorableReplace], which adds a replacement route that can be
2386 /// restored during state restoration.
2387 @optionalTypeArgs
2388 static void replace<T extends Object?>(BuildContext context, { required Route<dynamic> oldRoute, required Route<T> newRoute }) {
2389 return Navigator.of(context).replace<T>(oldRoute: oldRoute, newRoute: newRoute);
2390 }
2391
2392 /// Replaces a route on the navigator that most tightly encloses the given
2393 /// context with a new route.
2394 ///
2395 /// {@template flutter.widgets.navigator.restorableReplace}
2396 /// Unlike [Route]s added via [replace], [Route]s added with this method are
2397 /// restored during state restoration according to the rules outlined in the
2398 /// "State Restoration" section of [Navigator].
2399 /// {@endtemplate}
2400 ///
2401 /// {@macro flutter.widgets.navigator.replace}
2402 ///
2403 /// {@macro flutter.widgets.Navigator.restorablePush}
2404 ///
2405 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
2406 @optionalTypeArgs
2407 static String restorableReplace<T extends Object?>(BuildContext context, { required Route<dynamic> oldRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
2408 return Navigator.of(context).restorableReplace<T>(oldRoute: oldRoute, newRouteBuilder: newRouteBuilder, arguments: arguments);
2409 }
2410
2411 /// Replaces a route on the navigator that most tightly encloses the given
2412 /// context with a new route. The route to be replaced is the one below the
2413 /// given `anchorRoute`.
2414 ///
2415 /// {@template flutter.widgets.navigator.replaceRouteBelow}
2416 /// The old route must not be current visible, as this method skips the
2417 /// animations and therefore the removal would be jarring if it was visible.
2418 /// To replace the top-most route, consider [pushReplacement] instead, which
2419 /// _does_ animate the new route, and delays removing the old route until the
2420 /// new route has finished animating.
2421 ///
2422 /// The removed route is removed without being completed, so this method does
2423 /// not take a return value argument.
2424 ///
2425 /// The new route, the route below the new route (if any), and the route above
2426 /// the new route, are all notified (see [Route.didReplace],
2427 /// [Route.didChangeNext], and [Route.didChangePrevious]). If the [Navigator]
2428 /// has any [Navigator.observers], they will be notified as well (see
2429 /// [NavigatorObserver.didReplace]). The removed route is disposed without
2430 /// being notified. The future that had been returned from pushing that routes
2431 /// will not complete.
2432 ///
2433 /// The `T` type argument is the type of the return value of the new route.
2434 /// {@endtemplate}
2435 ///
2436 /// See also:
2437 ///
2438 /// * [replace], which is the same but identifies the route to be removed
2439 /// directly.
2440 /// * [restorableReplaceRouteBelow], which adds a replacement route that can
2441 /// be restored during state restoration.
2442 @optionalTypeArgs
2443 static void replaceRouteBelow<T extends Object?>(BuildContext context, { required Route<dynamic> anchorRoute, required Route<T> newRoute }) {
2444 return Navigator.of(context).replaceRouteBelow<T>(anchorRoute: anchorRoute, newRoute: newRoute);
2445 }
2446
2447 /// Replaces a route on the navigator that most tightly encloses the given
2448 /// context with a new route. The route to be replaced is the one below the
2449 /// given `anchorRoute`.
2450 ///
2451 /// {@template flutter.widgets.navigator.restorableReplaceRouteBelow}
2452 /// Unlike [Route]s added via [restorableReplaceRouteBelow], [Route]s added
2453 /// with this method are restored during state restoration according to the
2454 /// rules outlined in the "State Restoration" section of [Navigator].
2455 /// {@endtemplate}
2456 ///
2457 /// {@macro flutter.widgets.navigator.replaceRouteBelow}
2458 ///
2459 /// {@macro flutter.widgets.Navigator.restorablePush}
2460 ///
2461 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
2462 @optionalTypeArgs
2463 static String restorableReplaceRouteBelow<T extends Object?>(BuildContext context, { required Route<dynamic> anchorRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
2464 return Navigator.of(context).restorableReplaceRouteBelow<T>(anchorRoute: anchorRoute, newRouteBuilder: newRouteBuilder, arguments: arguments);
2465 }
2466
2467 /// Whether the navigator that most tightly encloses the given context can be
2468 /// popped.
2469 ///
2470 /// {@template flutter.widgets.navigator.canPop}
2471 /// The initial route cannot be popped off the navigator, which implies that
2472 /// this function returns true only if popping the navigator would not remove
2473 /// the initial route.
2474 ///
2475 /// If there is no [Navigator] in scope, returns false.
2476 ///
2477 /// Does not consider anything that might externally prevent popping, such as
2478 /// [PopEntry].
2479 /// {@endtemplate}
2480 ///
2481 /// See also:
2482 ///
2483 /// * [Route.isFirst], which returns true for routes for which [canPop]
2484 /// returns false.
2485 static bool canPop(BuildContext context) {
2486 final NavigatorState? navigator = Navigator.maybeOf(context);
2487 return navigator != null && navigator.canPop();
2488 }
2489
2490 /// Consults the current route's [Route.popDisposition] getter or
2491 /// [Route.willPop] method, and acts accordingly, potentially popping the
2492 /// route as a result; returns whether the pop request should be considered
2493 /// handled.
2494 ///
2495 /// {@template flutter.widgets.navigator.maybePop}
2496 /// If the [RoutePopDisposition] is [RoutePopDisposition.pop], then the [pop]
2497 /// method is called, and this method returns true, indicating that it handled
2498 /// the pop request.
2499 ///
2500 /// If the [RoutePopDisposition] is [RoutePopDisposition.doNotPop], then this
2501 /// method returns true, but does not do anything beyond that.
2502 ///
2503 /// If the [RoutePopDisposition] is [RoutePopDisposition.bubble], then this
2504 /// method returns false, and the caller is responsible for sending the
2505 /// request to the containing scope (e.g. by closing the application).
2506 ///
2507 /// This method is typically called for a user-initiated [pop]. For example on
2508 /// Android it's called by the binding for the system's back button.
2509 ///
2510 /// The `T` type argument is the type of the return value of the current
2511 /// route. (Typically this isn't known; consider specifying `dynamic` or
2512 /// `Null`.)
2513 /// {@endtemplate}
2514 ///
2515 /// See also:
2516 ///
2517 /// * [Form], which provides an `onWillPop` callback that enables the form
2518 /// to veto a [pop] initiated by the app's back button.
2519 /// * [ModalRoute], which provides a `scopedWillPopCallback` that can be used
2520 /// to define the route's `willPop` method.
2521 @optionalTypeArgs
2522 static Future<bool> maybePop<T extends Object?>(BuildContext context, [ T? result ]) {
2523 return Navigator.of(context).maybePop<T>(result);
2524 }
2525
2526 /// Pop the top-most route off the navigator that most tightly encloses the
2527 /// given context.
2528 ///
2529 /// {@template flutter.widgets.navigator.pop}
2530 /// The current route's [Route.didPop] method is called first. If that method
2531 /// returns false, then the route remains in the [Navigator]'s history (the
2532 /// route is expected to have popped some internal state; see e.g.
2533 /// [LocalHistoryRoute]). Otherwise, the rest of this description applies.
2534 ///
2535 /// If non-null, `result` will be used as the result of the route that is
2536 /// popped; the future that had been returned from pushing the popped route
2537 /// will complete with `result`. Routes such as dialogs or popup menus
2538 /// typically use this mechanism to return the value selected by the user to
2539 /// the widget that created their route. The type of `result`, if provided,
2540 /// must match the type argument of the class of the popped route (`T`).
2541 ///
2542 /// The popped route and the route below it are notified (see [Route.didPop],
2543 /// [Route.didComplete], and [Route.didPopNext]). If the [Navigator] has any
2544 /// [Navigator.observers], they will be notified as well (see
2545 /// [NavigatorObserver.didPop]).
2546 ///
2547 /// The `T` type argument is the type of the return value of the popped route.
2548 ///
2549 /// The type of `result`, if provided, must match the type argument of the
2550 /// class of the popped route (`T`).
2551 /// {@endtemplate}
2552 ///
2553 /// {@tool snippet}
2554 ///
2555 /// Typical usage for closing a route is as follows:
2556 ///
2557 /// ```dart
2558 /// void _close() {
2559 /// Navigator.pop(context);
2560 /// }
2561 /// ```
2562 /// {@end-tool}
2563 ///
2564 /// A dialog box might be closed with a result:
2565 ///
2566 /// ```dart
2567 /// void _accept() {
2568 /// Navigator.pop(context, true); // dialog returns true
2569 /// }
2570 /// ```
2571 @optionalTypeArgs
2572 static void pop<T extends Object?>(BuildContext context, [ T? result ]) {
2573 Navigator.of(context).pop<T>(result);
2574 }
2575
2576 /// Calls [pop] repeatedly on the navigator that most tightly encloses the
2577 /// given context until the predicate returns true.
2578 ///
2579 /// {@template flutter.widgets.navigator.popUntil}
2580 /// The predicate may be applied to the same route more than once if
2581 /// [Route.willHandlePopInternally] is true.
2582 ///
2583 /// To pop until a route with a certain name, use the [RoutePredicate]
2584 /// returned from [ModalRoute.withName].
2585 ///
2586 /// The routes are closed with null as their `return` value.
2587 ///
2588 /// See [pop] for more details of the semantics of popping a route.
2589 /// {@endtemplate}
2590 ///
2591 /// {@tool snippet}
2592 ///
2593 /// Typical usage is as follows:
2594 ///
2595 /// ```dart
2596 /// void _logout() {
2597 /// Navigator.popUntil(context, ModalRoute.withName('/login'));
2598 /// }
2599 /// ```
2600 /// {@end-tool}
2601 static void popUntil(BuildContext context, RoutePredicate predicate) {
2602 Navigator.of(context).popUntil(predicate);
2603 }
2604
2605 /// Immediately remove `route` from the navigator that most tightly encloses
2606 /// the given context, and [Route.dispose] it.
2607 ///
2608 /// {@template flutter.widgets.navigator.removeRoute}
2609 /// The removed route is removed without being completed, so this method does
2610 /// not take a return value argument. No animations are run as a result of
2611 /// this method call.
2612 ///
2613 /// The routes below and above the removed route are notified (see
2614 /// [Route.didChangeNext] and [Route.didChangePrevious]). If the [Navigator]
2615 /// has any [Navigator.observers], they will be notified as well (see
2616 /// [NavigatorObserver.didRemove]). The removed route is disposed without
2617 /// being notified. The future that had been returned from pushing that routes
2618 /// will not complete.
2619 ///
2620 /// The given `route` must be in the history; this method will throw an
2621 /// exception if it is not.
2622 ///
2623 /// Ongoing gestures within the current route are canceled.
2624 /// {@endtemplate}
2625 ///
2626 /// This method is used, for example, to instantly dismiss dropdown menus that
2627 /// are up when the screen's orientation changes.
2628 static void removeRoute(BuildContext context, Route<dynamic> route) {
2629 return Navigator.of(context).removeRoute(route);
2630 }
2631
2632 /// Immediately remove a route from the navigator that most tightly encloses
2633 /// the given context, and [Route.dispose] it. The route to be removed is the
2634 /// one below the given `anchorRoute`.
2635 ///
2636 /// {@template flutter.widgets.navigator.removeRouteBelow}
2637 /// The removed route is removed without being completed, so this method does
2638 /// not take a return value argument. No animations are run as a result of
2639 /// this method call.
2640 ///
2641 /// The routes below and above the removed route are notified (see
2642 /// [Route.didChangeNext] and [Route.didChangePrevious]). If the [Navigator]
2643 /// has any [Navigator.observers], they will be notified as well (see
2644 /// [NavigatorObserver.didRemove]). The removed route is disposed without
2645 /// being notified. The future that had been returned from pushing that routes
2646 /// will not complete.
2647 ///
2648 /// The given `anchorRoute` must be in the history and must have a route below
2649 /// it; this method will throw an exception if it is not or does not.
2650 ///
2651 /// Ongoing gestures within the current route are canceled.
2652 /// {@endtemplate}
2653 static void removeRouteBelow(BuildContext context, Route<dynamic> anchorRoute) {
2654 return Navigator.of(context).removeRouteBelow(anchorRoute);
2655 }
2656
2657 /// The state from the closest instance of this class that encloses the given
2658 /// context.
2659 ///
2660 /// Typical usage is as follows:
2661 ///
2662 /// ```dart
2663 /// Navigator.of(context)
2664 /// ..pop()
2665 /// ..pop()
2666 /// ..pushNamed('/settings');
2667 /// ```
2668 ///
2669 /// If `rootNavigator` is set to true, the state from the furthest instance of
2670 /// this class is given instead. Useful for pushing contents above all
2671 /// subsequent instances of [Navigator].
2672 ///
2673 /// If there is no [Navigator] in the give `context`, this function will throw
2674 /// a [FlutterError] in debug mode, and an exception in release mode.
2675 ///
2676 /// This method can be expensive (it walks the element tree).
2677 static NavigatorState of(
2678 BuildContext context, {
2679 bool rootNavigator = false,
2680 }) {
2681 // Handles the case where the input context is a navigator element.
2682 NavigatorState? navigator;
2683 if (context is StatefulElement && context.state is NavigatorState) {
2684 navigator = context.state as NavigatorState;
2685 }
2686 if (rootNavigator) {
2687 navigator = context.findRootAncestorStateOfType<NavigatorState>() ?? navigator;
2688 } else {
2689 navigator = navigator ?? context.findAncestorStateOfType<NavigatorState>();
2690 }
2691
2692 assert(() {
2693 if (navigator == null) {
2694 throw FlutterError(
2695 'Navigator operation requested with a context that does not include a Navigator.\n'
2696 'The context used to push or pop routes from the Navigator must be that of a '
2697 'widget that is a descendant of a Navigator widget.',
2698 );
2699 }
2700 return true;
2701 }());
2702 return navigator!;
2703 }
2704
2705 /// The state from the closest instance of this class that encloses the given
2706 /// context, if any.
2707 ///
2708 /// Typical usage is as follows:
2709 ///
2710 /// ```dart
2711 /// NavigatorState? navigatorState = Navigator.maybeOf(context);
2712 /// if (navigatorState != null) {
2713 /// navigatorState
2714 /// ..pop()
2715 /// ..pop()
2716 /// ..pushNamed('/settings');
2717 /// }
2718 /// ```
2719 ///
2720 /// If `rootNavigator` is set to true, the state from the furthest instance of
2721 /// this class is given instead. Useful for pushing contents above all
2722 /// subsequent instances of [Navigator].
2723 ///
2724 /// Will return null if there is no ancestor [Navigator] in the `context`.
2725 ///
2726 /// This method can be expensive (it walks the element tree).
2727 static NavigatorState? maybeOf(
2728 BuildContext context, {
2729 bool rootNavigator = false,
2730 }) {
2731 // Handles the case where the input context is a navigator element.
2732 NavigatorState? navigator;
2733 if (context is StatefulElement && context.state is NavigatorState) {
2734 navigator = context.state as NavigatorState;
2735 }
2736 if (rootNavigator) {
2737 navigator = context.findRootAncestorStateOfType<NavigatorState>() ?? navigator;
2738 } else {
2739 navigator = navigator ?? context.findAncestorStateOfType<NavigatorState>();
2740 }
2741 return navigator;
2742 }
2743
2744 /// Turn a route name into a set of [Route] objects.
2745 ///
2746 /// This is the default value of [onGenerateInitialRoutes], which is used if
2747 /// [initialRoute] is not null.
2748 ///
2749 /// If this string starts with a `/` character and has multiple `/` characters
2750 /// in it, then the string is split on those characters and substrings from
2751 /// the start of the string up to each such character are, in turn, used as
2752 /// routes to push.
2753 ///
2754 /// For example, if the route `/stocks/HOOLI` was used as the [initialRoute],
2755 /// then the [Navigator] would push the following routes on startup: `/`,
2756 /// `/stocks`, `/stocks/HOOLI`. This enables deep linking while allowing the
2757 /// application to maintain a predictable route history.
2758 static List<Route<dynamic>> defaultGenerateInitialRoutes(NavigatorState navigator, String initialRouteName) {
2759 final List<Route<dynamic>?> result = <Route<dynamic>?>[];
2760 if (initialRouteName.startsWith('/') && initialRouteName.length > 1) {
2761 initialRouteName = initialRouteName.substring(1); // strip leading '/'
2762 assert(Navigator.defaultRouteName == '/');
2763 List<String>? debugRouteNames;
2764 assert(() {
2765 debugRouteNames = <String>[ Navigator.defaultRouteName ];
2766 return true;
2767 }());
2768 result.add(navigator._routeNamed<dynamic>(Navigator.defaultRouteName, arguments: null, allowNull: true));
2769 final List<String> routeParts = initialRouteName.split('/');
2770 if (initialRouteName.isNotEmpty) {
2771 String routeName = '';
2772 for (final String part in routeParts) {
2773 routeName += '/$part';
2774 assert(() {
2775 debugRouteNames!.add(routeName);
2776 return true;
2777 }());
2778 result.add(navigator._routeNamed<dynamic>(routeName, arguments: null, allowNull: true));
2779 }
2780 }
2781 if (result.last == null) {
2782 assert(() {
2783 FlutterError.reportError(
2784 FlutterErrorDetails(
2785 exception:
2786 'Could not navigate to initial route.\n'
2787 'The requested route name was: "/$initialRouteName"\n'
2788 'There was no corresponding route in the app, and therefore the initial route specified will be '
2789 'ignored and "${Navigator.defaultRouteName}" will be used instead.',
2790 ),
2791 );
2792 return true;
2793 }());
2794 for (final Route<dynamic>? route in result) {
2795 route?.dispose();
2796 }
2797 result.clear();
2798 }
2799 } else if (initialRouteName != Navigator.defaultRouteName) {
2800 // If initialRouteName wasn't '/', then we try to get it with allowNull:true, so that if that fails,
2801 // we fall back to '/' (without allowNull:true, see below).
2802 result.add(navigator._routeNamed<dynamic>(initialRouteName, arguments: null, allowNull: true));
2803 }
2804 // Null route might be a result of gap in initialRouteName
2805 //
2806 // For example, routes = ['A', 'A/B/C'], and initialRouteName = 'A/B/C'
2807 // This should result in result = ['A', null,'A/B/C'] where 'A/B' produces
2808 // the null. In this case, we want to filter out the null and return
2809 // result = ['A', 'A/B/C'].
2810 result.removeWhere((Route<dynamic>? route) => route == null);
2811 if (result.isEmpty) {
2812 result.add(navigator._routeNamed<dynamic>(Navigator.defaultRouteName, arguments: null));
2813 }
2814 return result.cast<Route<dynamic>>();
2815 }
2816
2817 @override
2818 NavigatorState createState() => NavigatorState();
2819}
2820
2821// The _RouteLifecycle state machine (only goes down):
2822//
2823// [creation of a _RouteEntry]
2824// |
2825// +
2826// |\
2827// | \
2828// | staging
2829// | /
2830// |/
2831// +-+----------+--+-------+
2832// / | | |
2833// / | | |
2834// / | | |
2835// / | | |
2836// / | | |
2837// pushReplace push* add* replace*
2838// \ | | |
2839// \ | | /
2840// +--pushing# adding /
2841// \ / /
2842// \ / /
2843// idle--+-----+
2844// / \
2845// / +------+
2846// / | |
2847// / | complete*
2848// | | /
2849// pop* remove*
2850// / \
2851// / removing#
2852// popping# |
2853// | |
2854// [finalizeRoute] |
2855// \ |
2856// dispose*
2857// |
2858// disposing
2859// |
2860// disposed
2861// |
2862// |
2863// [_RouteEntry garbage collected]
2864// (terminal state)
2865//
2866// * These states are transient; as soon as _flushHistoryUpdates is run the
2867// route entry will exit that state.
2868// # These states await futures or other events, then transition automatically.
2869enum _RouteLifecycle {
2870 staging, // we will wait for transition delegate to decide what to do with this route.
2871 //
2872 // routes that are present:
2873 //
2874 add, // we'll want to run install, didAdd, etc; a route created by onGenerateInitialRoutes or by the initial widget.pages
2875 adding, // we'll waiting for the future from didPush of top-most route to complete
2876 // routes that are ready for transition.
2877 push, // we'll want to run install, didPush, etc; a route added via push() and friends
2878 pushReplace, // we'll want to run install, didPush, etc; a route added via pushReplace() and friends
2879 pushing, // we're waiting for the future from didPush to complete
2880 replace, // we'll want to run install, didReplace, etc; a route added via replace() and friends
2881 idle, // route is being harmless
2882 //
2883 // routes that are not present:
2884 //
2885 // routes that should be included in route announcement and should still listen to transition changes.
2886 pop, // we'll want to call didPop
2887 complete, // we'll want to call didComplete,
2888 remove, // we'll want to run didReplace/didRemove etc
2889 // routes should not be included in route announcement but should still listen to transition changes.
2890 popping, // we're waiting for the route to call finalizeRoute to switch to dispose
2891 removing, // we are waiting for subsequent routes to be done animating, then will switch to dispose
2892 // routes that are completely removed from the navigator and overlay.
2893 dispose, // we will dispose the route momentarily
2894 disposing, // The entry is waiting for its widget subtree to be disposed
2895 // first. It is stored in _entryWaitingForSubTreeDisposal while
2896 // awaiting that.
2897 disposed, // we have disposed the route
2898}
2899
2900typedef _RouteEntryPredicate = bool Function(_RouteEntry entry);
2901
2902class _NotAnnounced extends Route<void> {
2903 // A placeholder for the lastAnnouncedPreviousRoute, the
2904 // lastAnnouncedPoppedNextRoute, and the lastAnnouncedNextRoute before any
2905 // change has been announced.
2906}
2907
2908class _RouteEntry extends RouteTransitionRecord {
2909 _RouteEntry(
2910 this.route, {
2911 required _RouteLifecycle initialState,
2912 required this.pageBased,
2913 this.restorationInformation,
2914 }) : assert(!pageBased || route.settings is Page),
2915 assert(
2916 initialState == _RouteLifecycle.staging ||
2917 initialState == _RouteLifecycle.add ||
2918 initialState == _RouteLifecycle.push ||
2919 initialState == _RouteLifecycle.pushReplace ||
2920 initialState == _RouteLifecycle.replace,
2921 ),
2922 currentState = initialState {
2923 // TODO(polina-c): stop duplicating code across disposables
2924 // https://github.com/flutter/flutter/issues/137435
2925 if (kFlutterMemoryAllocationsEnabled) {
2926 FlutterMemoryAllocations.instance.dispatchObjectCreated(
2927 library: 'package:flutter/widgets.dart',
2928 className: '$_RouteEntry',
2929 object: this,
2930 );
2931 }
2932 }
2933
2934 @override
2935 final Route<dynamic> route;
2936 final _RestorationInformation? restorationInformation;
2937 final bool pageBased;
2938
2939 /// The limit this route entry will attempt to pop in the case of route being
2940 /// remove as a result of a page update.
2941 static const int kDebugPopAttemptLimit = 100;
2942
2943 static final Route<dynamic> notAnnounced = _NotAnnounced();
2944
2945 _RouteLifecycle currentState;
2946 Route<dynamic>? lastAnnouncedPreviousRoute = notAnnounced; // last argument to Route.didChangePrevious
2947 WeakReference<Route<dynamic>> lastAnnouncedPoppedNextRoute = WeakReference<Route<dynamic>>(notAnnounced); // last argument to Route.didPopNext
2948 Route<dynamic>? lastAnnouncedNextRoute = notAnnounced; // last argument to Route.didChangeNext
2949 int? lastFocusNode; // The last focused semantic node for the route entry.
2950
2951 /// Restoration ID to be used for the encapsulating route when restoration is
2952 /// enabled for it or null if restoration cannot be enabled for it.
2953 String? get restorationId {
2954 // User-provided restoration ids of Pages are prefixed with 'p+'. Generated
2955 // ids for pageless routes are prefixed with 'r+' to avoid clashes.
2956 if (pageBased) {
2957 final Page<Object?> page = route.settings as Page<Object?>;
2958 return page.restorationId != null ? 'p+${page.restorationId}' : null;
2959 }
2960 if (restorationInformation != null) {
2961 return 'r+${restorationInformation!.restorationScopeId}';
2962 }
2963 return null;
2964 }
2965
2966 bool canUpdateFrom(Page<dynamic> page) {
2967 if (!willBePresent) {
2968 return false;
2969 }
2970 if (!pageBased) {
2971 return false;
2972 }
2973 final Page<dynamic> routePage = route.settings as Page<dynamic>;
2974 return page.canUpdate(routePage);
2975 }
2976
2977 void handleAdd({ required NavigatorState navigator, required Route<dynamic>? previousPresent }) {
2978 assert(currentState == _RouteLifecycle.add);
2979 assert(navigator._debugLocked);
2980 assert(route._navigator == null);
2981 route._navigator = navigator;
2982 route.install();
2983 assert(route.overlayEntries.isNotEmpty);
2984 currentState = _RouteLifecycle.adding;
2985 navigator._observedRouteAdditions.add(
2986 _NavigatorPushObservation(route, previousPresent),
2987 );
2988 }
2989
2990 void handlePush({ required NavigatorState navigator, required bool isNewFirst, required Route<dynamic>? previous, required Route<dynamic>? previousPresent }) {
2991 assert(currentState == _RouteLifecycle.push || currentState == _RouteLifecycle.pushReplace || currentState == _RouteLifecycle.replace);
2992 assert(navigator._debugLocked);
2993 assert(
2994 route._navigator == null,
2995 'The pushed route has already been used. When pushing a route, a new '
2996 'Route object must be provided.',
2997 );
2998 final _RouteLifecycle previousState = currentState;
2999 route._navigator = navigator;
3000 route.install();
3001 assert(route.overlayEntries.isNotEmpty);
3002 if (currentState == _RouteLifecycle.push || currentState == _RouteLifecycle.pushReplace) {
3003 final TickerFuture routeFuture = route.didPush();
3004 currentState = _RouteLifecycle.pushing;
3005 routeFuture.whenCompleteOrCancel(() {
3006 if (currentState == _RouteLifecycle.pushing) {
3007 currentState = _RouteLifecycle.idle;
3008 assert(!navigator._debugLocked);
3009 assert(() { navigator._debugLocked = true; return true; }());
3010 navigator._flushHistoryUpdates();
3011 assert(() { navigator._debugLocked = false; return true; }());
3012 }
3013 });
3014 } else {
3015 assert(currentState == _RouteLifecycle.replace);
3016 route.didReplace(previous);
3017 currentState = _RouteLifecycle.idle;
3018 }
3019 if (isNewFirst) {
3020 route.didChangeNext(null);
3021 }
3022
3023 if (previousState == _RouteLifecycle.replace || previousState == _RouteLifecycle.pushReplace) {
3024 navigator._observedRouteAdditions.add(
3025 _NavigatorReplaceObservation(route, previousPresent),
3026 );
3027 } else {
3028 assert(previousState == _RouteLifecycle.push);
3029 navigator._observedRouteAdditions.add(
3030 _NavigatorPushObservation(route, previousPresent),
3031 );
3032 }
3033 }
3034
3035 void handleDidPopNext(Route<dynamic> poppedRoute) {
3036 route.didPopNext(poppedRoute);
3037 lastAnnouncedPoppedNextRoute = WeakReference<Route<dynamic>>(poppedRoute);
3038 if (lastFocusNode != null) {
3039 // Move focus back to the last focused node.
3040 poppedRoute._disposeCompleter.future.then((dynamic result) async {
3041 switch (defaultTargetPlatform) {
3042 case TargetPlatform.android:
3043 // In the Android platform, we have to wait for the system refocus to complete before
3044 // sending the refocus message. Otherwise, the refocus message will be ignored.
3045 // TODO(hangyujin): update this logic if Android provide a better way to do so.
3046 final int? reFocusNode = lastFocusNode;
3047 await Future<void>.delayed(_kAndroidRefocusingDelayDuration);
3048 SystemChannels.accessibility.send(const FocusSemanticEvent().toMap(nodeId: reFocusNode));
3049 case TargetPlatform.iOS:
3050 SystemChannels.accessibility.send(const FocusSemanticEvent().toMap(nodeId: lastFocusNode));
3051 case _:
3052 break ;
3053 }
3054 });
3055 }
3056 }
3057
3058 /// Process the to-be-popped route.
3059 ///
3060 /// A route can be marked for pop by transition delegate or Navigator.pop,
3061 /// this method actually pops the route by calling Route.didPop.
3062 ///
3063 /// Returns true if the route is popped; otherwise, returns false if the route
3064 /// refuses to be popped.
3065 bool handlePop({ required NavigatorState navigator, required Route<dynamic>? previousPresent }) {
3066 assert(navigator._debugLocked);
3067 assert(route._navigator == navigator);
3068 currentState = _RouteLifecycle.popping;
3069 if (route._popCompleter.isCompleted) {
3070 // This is a page-based route popped through the Navigator.pop. The
3071 // didPop should have been called. No further action is needed.
3072 assert(pageBased);
3073 assert(pendingResult == null);
3074 return true;
3075 }
3076 if (!route.didPop(pendingResult)) {
3077 currentState = _RouteLifecycle.idle;
3078 return false;
3079 }
3080 pendingResult = null;
3081 return true;
3082 }
3083
3084 void handleComplete() {
3085 route.didComplete(pendingResult);
3086 pendingResult = null;
3087 assert(route._popCompleter.isCompleted); // implies didComplete was called
3088 currentState = _RouteLifecycle.remove;
3089 }
3090
3091 void handleRemoval({ required NavigatorState navigator, required Route<dynamic>? previousPresent }) {
3092 assert(navigator._debugLocked);
3093 assert(route._navigator == navigator);
3094 currentState = _RouteLifecycle.removing;
3095 if (_reportRemovalToObserver) {
3096 navigator._observedRouteDeletions.add(
3097 _NavigatorRemoveObservation(route, previousPresent),
3098 );
3099 }
3100 }
3101
3102 void didAdd({ required NavigatorState navigator, required bool isNewFirst}) {
3103 route.didAdd();
3104 currentState = _RouteLifecycle.idle;
3105 if (isNewFirst) {
3106 route.didChangeNext(null);
3107 }
3108 }
3109
3110 Object? pendingResult;
3111
3112 void pop<T>(T? result) {
3113 assert(isPresent);
3114 pendingResult = result;
3115 currentState = _RouteLifecycle.pop;
3116 route.onPopInvoked(true);
3117 }
3118
3119 bool _reportRemovalToObserver = true;
3120
3121 // Route is removed without being completed.
3122 void remove({ bool isReplaced = false }) {
3123 assert(
3124 !pageBased || isWaitingForExitingDecision,
3125 'A page-based route cannot be completed using imperative api, provide a '
3126 'new list without the corresponding Page to Navigator.pages instead. ',
3127 );
3128 if (currentState.index >= _RouteLifecycle.remove.index) {
3129 return;
3130 }
3131 assert(isPresent);
3132 _reportRemovalToObserver = !isReplaced;
3133 currentState = _RouteLifecycle.remove;
3134 }
3135
3136 // Route completes with `result` and is removed.
3137 void complete<T>(T result, { bool isReplaced = false }) {
3138 assert(
3139 !pageBased || isWaitingForExitingDecision,
3140 'A page-based route cannot be completed using imperative api, provide a '
3141 'new list without the corresponding Page to Navigator.pages instead. ',
3142 );
3143 if (currentState.index >= _RouteLifecycle.remove.index) {
3144 return;
3145 }
3146 assert(isPresent);
3147 _reportRemovalToObserver = !isReplaced;
3148 pendingResult = result;
3149 currentState = _RouteLifecycle.complete;
3150 }
3151
3152 void finalize() {
3153 assert(currentState.index < _RouteLifecycle.dispose.index);
3154 currentState = _RouteLifecycle.dispose;
3155 }
3156
3157 /// Disposes this route entry and its [route] immediately.
3158 ///
3159 /// This method does not wait for the widget subtree of the [route] to unmount
3160 /// before disposing.
3161 void forcedDispose() {
3162 assert(currentState.index < _RouteLifecycle.disposed.index);
3163 // TODO(polina-c): stop duplicating code across disposables
3164 // https://github.com/flutter/flutter/issues/137435
3165 if (kFlutterMemoryAllocationsEnabled) {
3166 FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
3167 }
3168 currentState = _RouteLifecycle.disposed;
3169 route.dispose();
3170 }
3171
3172 /// Disposes this route entry and its [route].
3173 ///
3174 /// This method waits for the widget subtree of the [route] to unmount before
3175 /// disposing. If subtree is already unmounted, this method calls
3176 /// [forcedDispose] immediately.
3177 ///
3178 /// Use [forcedDispose] if the [route] need to be disposed immediately.
3179 void dispose() {
3180 assert(currentState.index < _RouteLifecycle.disposing.index);
3181 currentState = _RouteLifecycle.disposing;
3182
3183 // If the overlay entries are still mounted, widgets in the route's subtree
3184 // may still reference resources from the route and we delay disposal of
3185 // the route until the overlay entries are no longer mounted.
3186 // Since the overlay entry is the root of the route's subtree it will only
3187 // get unmounted after every other widget in the subtree has been unmounted.
3188
3189 final Iterable<OverlayEntry> mountedEntries = route.overlayEntries.where((OverlayEntry e) => e.mounted);
3190
3191 if (mountedEntries.isEmpty) {
3192 forcedDispose();
3193 return;
3194 }
3195
3196 int mounted = mountedEntries.length;
3197 assert(mounted > 0);
3198 final NavigatorState navigator = route._navigator!;
3199 navigator._entryWaitingForSubTreeDisposal.add(this);
3200 for (final OverlayEntry entry in mountedEntries) {
3201 late VoidCallback listener;
3202 listener = () {
3203 assert(mounted > 0);
3204 assert(!entry.mounted);
3205 mounted--;
3206 entry.removeListener(listener);
3207 if (mounted == 0) {
3208 assert(route.overlayEntries.every((OverlayEntry e) => !e.mounted));
3209 // This is a listener callback of one of the overlayEntries in this
3210 // route. Disposing the route also disposes its overlayEntries and
3211 // violates the rule that a change notifier can't be disposed during
3212 // its notifying callback.
3213 //
3214 // Use a microtask to ensure the overlayEntries have finished
3215 // notifying their listeners before disposing.
3216 return scheduleMicrotask(() {
3217 if (!navigator._entryWaitingForSubTreeDisposal.remove(this)) {
3218 // This route must have been destroyed as a result of navigator
3219 // force dispose.
3220 assert(route._navigator == null && !navigator.mounted);
3221 return;
3222 }
3223 assert(currentState == _RouteLifecycle.disposing);
3224 forcedDispose();
3225 });
3226 }
3227 };
3228 entry.addListener(listener);
3229 }
3230 }
3231
3232 bool get willBePresent {
3233 return currentState.index <= _RouteLifecycle.idle.index &&
3234 currentState.index >= _RouteLifecycle.add.index;
3235 }
3236
3237 bool get isPresent {
3238 return currentState.index <= _RouteLifecycle.remove.index &&
3239 currentState.index >= _RouteLifecycle.add.index;
3240 }
3241
3242 bool get isPresentForRestoration => currentState.index <= _RouteLifecycle.idle.index;
3243
3244 bool get suitableForAnnouncement {
3245 return currentState.index <= _RouteLifecycle.removing.index &&
3246 currentState.index >= _RouteLifecycle.push.index;
3247 }
3248
3249 bool get suitableForTransitionAnimation {
3250 return currentState.index <= _RouteLifecycle.remove.index &&
3251 currentState.index >= _RouteLifecycle.push.index;
3252 }
3253
3254 bool shouldAnnounceChangeToNext(Route<dynamic>? nextRoute) {
3255 assert(nextRoute != lastAnnouncedNextRoute);
3256 // Do not announce if `next` changes from a just popped route to null. We
3257 // already announced this change by calling didPopNext.
3258 return !(
3259 nextRoute == null &&
3260 lastAnnouncedPoppedNextRoute.target == lastAnnouncedNextRoute
3261 );
3262 }
3263
3264 static bool isPresentPredicate(_RouteEntry entry) => entry.isPresent;
3265 static bool suitableForTransitionAnimationPredicate(_RouteEntry entry) => entry.suitableForTransitionAnimation;
3266 static bool willBePresentPredicate(_RouteEntry entry) => entry.willBePresent;
3267
3268 static _RouteEntryPredicate isRoutePredicate(Route<dynamic> route) {
3269 return (_RouteEntry entry) => entry.route == route;
3270 }
3271
3272 @override
3273 bool get isWaitingForEnteringDecision => currentState == _RouteLifecycle.staging;
3274
3275 @override
3276 bool get isWaitingForExitingDecision => _isWaitingForExitingDecision;
3277 bool _isWaitingForExitingDecision = false;
3278
3279 void markNeedsExitingDecision() => _isWaitingForExitingDecision = true;
3280
3281 @override
3282 void markForPush() {
3283 assert(
3284 isWaitingForEnteringDecision && !isWaitingForExitingDecision,
3285 'This route cannot be marked for push. Either a decision has already been '
3286 'made or it does not require an explicit decision on how to transition in.',
3287 );
3288 currentState = _RouteLifecycle.push;
3289 }
3290
3291 @override
3292 void markForAdd() {
3293 assert(
3294 isWaitingForEnteringDecision && !isWaitingForExitingDecision,
3295 'This route cannot be marked for add. Either a decision has already been '
3296 'made or it does not require an explicit decision on how to transition in.',
3297 );
3298 currentState = _RouteLifecycle.add;
3299 }
3300
3301 @override
3302 void markForPop([dynamic result]) {
3303 assert(
3304 !isWaitingForEnteringDecision && isWaitingForExitingDecision && isPresent,
3305 'This route cannot be marked for pop. Either a decision has already been '
3306 'made or it does not require an explicit decision on how to transition out.',
3307 );
3308 // Remove state that prevents a pop, e.g. LocalHistoryEntry[s].
3309 int attempt = 0;
3310 while (route.willHandlePopInternally) {
3311 assert(
3312 () {
3313 attempt += 1;
3314 return attempt < kDebugPopAttemptLimit;
3315 }(),
3316 'Attempted to pop $route $kDebugPopAttemptLimit times, but still failed',
3317 );
3318 final bool popResult = route.didPop(result);
3319 assert(!popResult);
3320
3321 }
3322 pop<dynamic>(result);
3323 _isWaitingForExitingDecision = false;
3324 }
3325
3326 @override
3327 void markForComplete([dynamic result]) {
3328 assert(
3329 !isWaitingForEnteringDecision && isWaitingForExitingDecision && isPresent,
3330 'This route cannot be marked for complete. Either a decision has already '
3331 'been made or it does not require an explicit decision on how to transition '
3332 'out.',
3333 );
3334 complete<dynamic>(result);
3335 _isWaitingForExitingDecision = false;
3336 }
3337
3338 @override
3339 void markForRemove() {
3340 assert(
3341 !isWaitingForEnteringDecision && isWaitingForExitingDecision && isPresent,
3342 'This route cannot be marked for remove. Either a decision has already '
3343 'been made or it does not require an explicit decision on how to transition '
3344 'out.',
3345 );
3346 remove();
3347 _isWaitingForExitingDecision = false;
3348 }
3349
3350 bool get restorationEnabled => route.restorationScopeId.value != null;
3351 set restorationEnabled(bool value) {
3352 assert(!value || restorationId != null);
3353 route._updateRestorationId(value ? restorationId : null);
3354 }
3355}
3356
3357abstract class _NavigatorObservation {
3358 _NavigatorObservation(
3359 this.primaryRoute,
3360 this.secondaryRoute,
3361 );
3362 final Route<dynamic> primaryRoute;
3363 final Route<dynamic>? secondaryRoute;
3364
3365 void notify(NavigatorObserver observer);
3366}
3367
3368class _NavigatorPushObservation extends _NavigatorObservation {
3369 _NavigatorPushObservation(
3370 super.primaryRoute,
3371 super.secondaryRoute,
3372 );
3373
3374 @override
3375 void notify(NavigatorObserver observer) {
3376 observer.didPush(primaryRoute, secondaryRoute);
3377 }
3378}
3379
3380class _NavigatorPopObservation extends _NavigatorObservation {
3381 _NavigatorPopObservation(
3382 super.primaryRoute,
3383 super.secondaryRoute,
3384 );
3385
3386 @override
3387 void notify(NavigatorObserver observer) {
3388 observer.didPop(primaryRoute, secondaryRoute);
3389 }
3390}
3391
3392class _NavigatorRemoveObservation extends _NavigatorObservation {
3393 _NavigatorRemoveObservation(
3394 super.primaryRoute,
3395 super.secondaryRoute,
3396 );
3397
3398 @override
3399 void notify(NavigatorObserver observer) {
3400 observer.didRemove(primaryRoute, secondaryRoute);
3401 }
3402}
3403
3404class _NavigatorReplaceObservation extends _NavigatorObservation {
3405 _NavigatorReplaceObservation(
3406 super.primaryRoute,
3407 super.secondaryRoute,
3408 );
3409
3410 @override
3411 void notify(NavigatorObserver observer) {
3412 observer.didReplace(newRoute: primaryRoute, oldRoute: secondaryRoute);
3413 }
3414}
3415
3416typedef _IndexWhereCallback = bool Function(_RouteEntry element);
3417
3418/// A collection of _RouteEntries representing a navigation history.
3419///
3420/// Acts as a ChangeNotifier and notifies after its List of _RouteEntries is
3421/// mutated.
3422class _History extends Iterable<_RouteEntry> with ChangeNotifier {
3423 /// Creates an instance of [_History].
3424 _History() {
3425 if (kFlutterMemoryAllocationsEnabled) {
3426 ChangeNotifier.maybeDispatchObjectCreation(this);
3427 }
3428 }
3429
3430 final List<_RouteEntry> _value = <_RouteEntry>[];
3431
3432 int indexWhere(_IndexWhereCallback test, [int start = 0]) {
3433 return _value.indexWhere(test, start);
3434 }
3435
3436 void add(_RouteEntry element) {
3437 _value.add(element);
3438 notifyListeners();
3439 }
3440
3441 void addAll(Iterable<_RouteEntry> elements) {
3442 _value.addAll(elements);
3443 if (elements.isNotEmpty) {
3444 notifyListeners();
3445 }
3446 }
3447
3448 void clear() {
3449 final bool valueWasEmpty = _value.isEmpty;
3450 _value.clear();
3451 if (!valueWasEmpty) {
3452 notifyListeners();
3453 }
3454 }
3455
3456 void insert(int index, _RouteEntry element) {
3457 _value.insert(index, element);
3458 notifyListeners();
3459 }
3460
3461 _RouteEntry removeAt(int index) {
3462 final _RouteEntry entry = _value.removeAt(index);
3463 notifyListeners();
3464 return entry;
3465 }
3466
3467 _RouteEntry removeLast() {
3468 final _RouteEntry entry = _value.removeLast();
3469 notifyListeners();
3470 return entry;
3471 }
3472
3473 _RouteEntry operator [](int index) {
3474 return _value[index];
3475 }
3476
3477 @override
3478 Iterator<_RouteEntry> get iterator {
3479 return _value.iterator;
3480 }
3481
3482 @override
3483 String toString() {
3484 return _value.toString();
3485 }
3486}
3487
3488/// The state for a [Navigator] widget.
3489///
3490/// A reference to this class can be obtained by calling [Navigator.of].
3491class NavigatorState extends State<Navigator> with TickerProviderStateMixin, RestorationMixin {
3492 late GlobalKey<OverlayState> _overlayKey;
3493 final _History _history = _History();
3494
3495 /// A set for entries that are waiting to dispose until their subtrees are
3496 /// disposed.
3497 ///
3498 /// These entries are not considered to be in the _history and will usually
3499 /// remove themselves from this set once they can dispose.
3500 ///
3501 /// The navigator keep track of these entries so that, in case the navigator
3502 /// itself is disposed, it can dispose these entries immediately.
3503 final Set<_RouteEntry> _entryWaitingForSubTreeDisposal = <_RouteEntry>{};
3504 final _HistoryProperty _serializableHistory = _HistoryProperty();
3505 final Queue<_NavigatorObservation> _observedRouteAdditions = Queue<_NavigatorObservation>();
3506 final Queue<_NavigatorObservation> _observedRouteDeletions = Queue<_NavigatorObservation>();
3507
3508 /// The [FocusNode] for the [Focus] that encloses the routes.
3509 final FocusNode focusNode = FocusNode(debugLabel: 'Navigator');
3510
3511 bool _debugLocked = false; // used to prevent re-entrant calls to push, pop, and friends
3512
3513 HeroController? _heroControllerFromScope;
3514
3515 late List<NavigatorObserver> _effectiveObservers;
3516
3517 bool get _usingPagesAPI => widget.pages != const <Page<dynamic>>[];
3518
3519 void _handleHistoryChanged() {
3520 final bool navigatorCanPop = canPop();
3521 late final bool routeBlocksPop;
3522 if (!navigatorCanPop) {
3523 final _RouteEntry? lastEntry = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
3524 routeBlocksPop = lastEntry != null
3525 && lastEntry.route.popDisposition == RoutePopDisposition.doNotPop;
3526 } else {
3527 routeBlocksPop = false;
3528 }
3529 final NavigationNotification notification = NavigationNotification(
3530 canHandlePop: navigatorCanPop || routeBlocksPop,
3531 );
3532 // Avoid dispatching a notification in the middle of a build.
3533 switch (SchedulerBinding.instance.schedulerPhase) {
3534 case SchedulerPhase.postFrameCallbacks:
3535 notification.dispatch(context);
3536 case SchedulerPhase.idle:
3537 case SchedulerPhase.midFrameMicrotasks:
3538 case SchedulerPhase.persistentCallbacks:
3539 case SchedulerPhase.transientCallbacks:
3540 SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
3541 if (!mounted) {
3542 return;
3543 }
3544 notification.dispatch(context);
3545 }, debugLabel: 'Navigator.dispatchNotification');
3546 }
3547 }
3548
3549 @override
3550 void initState() {
3551 super.initState();
3552 assert(() {
3553 if (_usingPagesAPI) {
3554 if (widget.pages.isEmpty) {
3555 FlutterError.reportError(
3556 FlutterErrorDetails(
3557 exception: FlutterError(
3558 'The Navigator.pages must not be empty to use the '
3559 'Navigator.pages API',
3560 ),
3561 library: 'widget library',
3562 stack: StackTrace.current,
3563 ),
3564 );
3565 } else if (widget.onPopPage == null) {
3566 FlutterError.reportError(
3567 FlutterErrorDetails(
3568 exception: FlutterError(
3569 'The Navigator.onPopPage must be provided to use the '
3570 'Navigator.pages API',
3571 ),
3572 library: 'widget library',
3573 stack: StackTrace.current,
3574 ),
3575 );
3576 }
3577 }
3578 return true;
3579 }());
3580 for (final NavigatorObserver observer in widget.observers) {
3581 assert(observer.navigator == null);
3582 NavigatorObserver._navigators[observer] = this;
3583 }
3584 _effectiveObservers = widget.observers;
3585
3586 // We have to manually extract the inherited widget in initState because
3587 // the current context is not fully initialized.
3588 final HeroControllerScope? heroControllerScope = context
3589 .getElementForInheritedWidgetOfExactType<HeroControllerScope>()
3590 ?.widget as HeroControllerScope?;
3591 _updateHeroController(heroControllerScope?.controller);
3592
3593 if (widget.reportsRouteUpdateToEngine) {
3594 SystemNavigator.selectSingleEntryHistory();
3595 }
3596
3597 ServicesBinding.instance.accessibilityFocus.addListener(_recordLastFocus);
3598 _history.addListener(_handleHistoryChanged);
3599 }
3600
3601 // Record the last focused node in route entry.
3602 void _recordLastFocus(){
3603 final _RouteEntry? entry = _history.where(_RouteEntry.isPresentPredicate).lastOrNull;
3604 entry?.lastFocusNode = ServicesBinding.instance.accessibilityFocus.value;
3605 }
3606
3607 // Use [_nextPagelessRestorationScopeId] to get the next id.
3608 final RestorableNum<int> _rawNextPagelessRestorationScopeId = RestorableNum<int>(0);
3609
3610 int get _nextPagelessRestorationScopeId => _rawNextPagelessRestorationScopeId.value++;
3611
3612 @override
3613 void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
3614 registerForRestoration(_rawNextPagelessRestorationScopeId, 'id');
3615 registerForRestoration(_serializableHistory, 'history');
3616
3617 // Delete everything in the old history and clear the overlay.
3618 _forcedDisposeAllRouteEntries();
3619 assert(_history.isEmpty);
3620 _overlayKey = GlobalKey<OverlayState>();
3621
3622 // Populate the new history from restoration data.
3623 _history.addAll(_serializableHistory.restoreEntriesForPage(null, this));
3624 for (final Page<dynamic> page in widget.pages) {
3625 final _RouteEntry entry = _RouteEntry(
3626 page.createRoute(context),
3627 pageBased: true,
3628 initialState: _RouteLifecycle.add,
3629 );
3630 assert(
3631 entry.route.settings == page,
3632 'The settings getter of a page-based Route must return a Page object. '
3633 'Please set the settings to the Page in the Page.createRoute method.',
3634 );
3635 _history.add(entry);
3636 _history.addAll(_serializableHistory.restoreEntriesForPage(entry, this));
3637 }
3638
3639 // If there was nothing to restore, we need to process the initial route.
3640 if (!_serializableHistory.hasData) {
3641 String? initialRoute = widget.initialRoute;
3642 if (widget.pages.isEmpty) {
3643 initialRoute = initialRoute ?? Navigator.defaultRouteName;
3644 }
3645 if (initialRoute != null) {
3646 _history.addAll(
3647 widget.onGenerateInitialRoutes(
3648 this,
3649 widget.initialRoute ?? Navigator.defaultRouteName,
3650 ).map((Route<dynamic> route) => _RouteEntry(
3651 route,
3652 pageBased: false,
3653 initialState: _RouteLifecycle.add,
3654 restorationInformation: route.settings.name != null
3655 ? _RestorationInformation.named(
3656 name: route.settings.name!,
3657 arguments: null,
3658 restorationScopeId: _nextPagelessRestorationScopeId,
3659 )
3660 : null,
3661 ),
3662 ),
3663 );
3664 }
3665 }
3666
3667 assert(
3668 _history.isNotEmpty,
3669 'All routes returned by onGenerateInitialRoutes are not restorable. '
3670 'Please make sure that all routes returned by onGenerateInitialRoutes '
3671 'have their RouteSettings defined with names that are defined in the '
3672 "app's routes table.",
3673 );
3674 assert(!_debugLocked);
3675 assert(() { _debugLocked = true; return true; }());
3676 _flushHistoryUpdates();
3677 assert(() { _debugLocked = false; return true; }());
3678 }
3679
3680 @override
3681 void didToggleBucket(RestorationBucket? oldBucket) {
3682 super.didToggleBucket(oldBucket);
3683 if (bucket != null) {
3684 _serializableHistory.update(_history);
3685 } else {
3686 _serializableHistory.clear();
3687 }
3688 }
3689 @override
3690 String? get restorationId => widget.restorationScopeId;
3691
3692 @override
3693 void didChangeDependencies() {
3694 super.didChangeDependencies();
3695 _updateHeroController(HeroControllerScope.maybeOf(context));
3696 for (final _RouteEntry entry in _history) {
3697 entry.route.changedExternalState();
3698 }
3699 }
3700
3701 /// Dispose all lingering router entries immediately.
3702 void _forcedDisposeAllRouteEntries() {
3703 _entryWaitingForSubTreeDisposal.removeWhere((_RouteEntry entry) {
3704 entry.forcedDispose();
3705 return true;
3706 });
3707 while (_history.isNotEmpty) {
3708 _disposeRouteEntry(_history.removeLast(), graceful: false);
3709 }
3710 }
3711
3712 static void _disposeRouteEntry(_RouteEntry entry, {required bool graceful}) {
3713 for (final OverlayEntry overlayEntry in entry.route.overlayEntries) {
3714 overlayEntry.remove();
3715 }
3716 if (graceful) {
3717 entry.dispose();
3718 } else {
3719 entry.forcedDispose();
3720 }
3721 }
3722
3723 void _updateHeroController(HeroController? newHeroController) {
3724 if (_heroControllerFromScope != newHeroController) {
3725 if (newHeroController != null) {
3726 // Makes sure the same hero controller is not shared between two navigators.
3727 assert(() {
3728 // It is possible that the hero controller subscribes to an existing
3729 // navigator. We are fine as long as that navigator gives up the hero
3730 // controller at the end of the build.
3731 if (newHeroController.navigator != null) {
3732 final NavigatorState previousOwner = newHeroController.navigator!;
3733 ServicesBinding.instance.addPostFrameCallback((Duration timestamp) {
3734 // We only check if this navigator still owns the hero controller.
3735 if (_heroControllerFromScope == newHeroController) {
3736 final bool hasHeroControllerOwnerShip = _heroControllerFromScope!.navigator == this;
3737 if (!hasHeroControllerOwnerShip ||
3738 previousOwner._heroControllerFromScope == newHeroController) {
3739 final NavigatorState otherOwner = hasHeroControllerOwnerShip
3740 ? previousOwner
3741 : _heroControllerFromScope!.navigator!;
3742 FlutterError.reportError(
3743 FlutterErrorDetails(
3744 exception: FlutterError(
3745 'A HeroController can not be shared by multiple Navigators. '
3746 'The Navigators that share the same HeroController are:\n'
3747 '- $this\n'
3748 '- $otherOwner\n'
3749 'Please create a HeroControllerScope for each Navigator or '
3750 'use a HeroControllerScope.none to prevent subtree from '
3751 'receiving a HeroController.',
3752 ),
3753 library: 'widget library',
3754 stack: StackTrace.current,
3755 ),
3756 );
3757 }
3758 }
3759 }, debugLabel: 'Navigator.checkHeroControllerOwnership');
3760 }
3761 return true;
3762 }());
3763 NavigatorObserver._navigators[newHeroController] = this;
3764 }
3765 // Only unsubscribe the hero controller when it is currently subscribe to
3766 // this navigator.
3767 if (_heroControllerFromScope?.navigator == this) {
3768 NavigatorObserver._navigators[_heroControllerFromScope!] = null;
3769 }
3770 _heroControllerFromScope = newHeroController;
3771 _updateEffectiveObservers();
3772 }
3773 }
3774
3775 void _updateEffectiveObservers() {
3776 if (_heroControllerFromScope != null) {
3777 _effectiveObservers = widget.observers + <NavigatorObserver>[_heroControllerFromScope!];
3778 } else {
3779 _effectiveObservers = widget.observers;
3780 }
3781 }
3782
3783 @override
3784 void didUpdateWidget(Navigator oldWidget) {
3785 super.didUpdateWidget(oldWidget);
3786 assert(() {
3787 if (_usingPagesAPI) {
3788 // This navigator uses page API.
3789 if (widget.pages.isEmpty) {
3790 FlutterError.reportError(
3791 FlutterErrorDetails(
3792 exception: FlutterError(
3793 'The Navigator.pages must not be empty to use the '
3794 'Navigator.pages API',
3795 ),
3796 library: 'widget library',
3797 stack: StackTrace.current,
3798 ),
3799 );
3800 } else if (widget.onPopPage == null) {
3801 FlutterError.reportError(
3802 FlutterErrorDetails(
3803 exception: FlutterError(
3804 'The Navigator.onPopPage must be provided to use the '
3805 'Navigator.pages API',
3806 ),
3807 library: 'widget library',
3808 stack: StackTrace.current,
3809 ),
3810 );
3811 }
3812 }
3813 return true;
3814 }());
3815 if (oldWidget.observers != widget.observers) {
3816 for (final NavigatorObserver observer in oldWidget.observers) {
3817 NavigatorObserver._navigators[observer] = null;
3818 }
3819 for (final NavigatorObserver observer in widget.observers) {
3820 assert(observer.navigator == null);
3821 NavigatorObserver._navigators[observer] = this;
3822 }
3823 _updateEffectiveObservers();
3824 }
3825 if (oldWidget.pages != widget.pages && !restorePending) {
3826 assert(() {
3827 if (widget.pages.isEmpty) {
3828 FlutterError.reportError(
3829 FlutterErrorDetails(
3830 exception: FlutterError(
3831 'The Navigator.pages must not be empty to use the '
3832 'Navigator.pages API',
3833 ),
3834 library: 'widget library',
3835 stack: StackTrace.current,
3836 ),
3837 );
3838 }
3839 return true;
3840 }());
3841 _updatePages();
3842 }
3843
3844 for (final _RouteEntry entry in _history) {
3845 entry.route.changedExternalState();
3846 }
3847 }
3848
3849 void _debugCheckDuplicatedPageKeys() {
3850 assert(() {
3851 final Set<Key> keyReservation = <Key>{};
3852 for (final Page<dynamic> page in widget.pages) {
3853 final LocalKey? key = page.key;
3854 if (key != null) {
3855 assert(!keyReservation.contains(key));
3856 keyReservation.add(key);
3857 }
3858 }
3859 return true;
3860 }());
3861 }
3862
3863 @override
3864 void deactivate() {
3865 for (final NavigatorObserver observer in _effectiveObservers) {
3866 NavigatorObserver._navigators[observer] = null;
3867 }
3868 super.deactivate();
3869 }
3870
3871 @override
3872 void activate() {
3873 super.activate();
3874 for (final NavigatorObserver observer in _effectiveObservers) {
3875 assert(observer.navigator == null);
3876 NavigatorObserver._navigators[observer] = this;
3877 }
3878 }
3879
3880 @override
3881 void dispose() {
3882 assert(!_debugLocked);
3883 assert(() {
3884 _debugLocked = true;
3885 return true;
3886 }());
3887 assert(() {
3888 for (final NavigatorObserver observer in _effectiveObservers) {
3889 assert(observer.navigator != this);
3890 }
3891 return true;
3892 }());
3893 _updateHeroController(null);
3894 focusNode.dispose();
3895 _forcedDisposeAllRouteEntries();
3896 _rawNextPagelessRestorationScopeId.dispose();
3897 _serializableHistory.dispose();
3898 userGestureInProgressNotifier.dispose();
3899 ServicesBinding.instance.accessibilityFocus.removeListener(_recordLastFocus);
3900 _history.removeListener(_handleHistoryChanged);
3901 _history.dispose();
3902 super.dispose();
3903 // don't unlock, so that the object becomes unusable
3904 assert(_debugLocked);
3905 }
3906
3907 /// The overlay this navigator uses for its visual presentation.
3908 OverlayState? get overlay => _overlayKey.currentState;
3909
3910 Iterable<OverlayEntry> get _allRouteOverlayEntries {
3911 return <OverlayEntry>[
3912 for (final _RouteEntry entry in _history)
3913 ...entry.route.overlayEntries,
3914 ];
3915 }
3916
3917 String? _lastAnnouncedRouteName;
3918
3919 bool _debugUpdatingPage = false;
3920 void _updatePages() {
3921 assert(() {
3922 assert(!_debugUpdatingPage);
3923 _debugCheckDuplicatedPageKeys();
3924 _debugUpdatingPage = true;
3925 return true;
3926 }());
3927
3928 // This attempts to diff the new pages list (widget.pages) with
3929 // the old _RouteEntry(s) list (_history), and produces a new list of
3930 // _RouteEntry(s) to be the new list of _history. This method roughly
3931 // follows the same outline of RenderObjectElement.updateChildren.
3932 //
3933 // The cases it tries to optimize for are:
3934 // - the old list is empty
3935 // - All the pages in the new list can match the page-based routes in the old
3936 // list, and their orders are the same.
3937 // - there is an insertion or removal of one or more page-based route in
3938 // only one place in the list
3939 // If a page-based route with a key is in both lists, it will be synced.
3940 // Page-based routes without keys might be synced but there is no guarantee.
3941
3942 // The general approach is to sync the entire new list backwards, as follows:
3943 // 1. Walk the lists from the bottom, syncing nodes, and record pageless routes,
3944 // until you no longer have matching nodes.
3945 // 2. Walk the lists from the top, without syncing nodes, until you no
3946 // longer have matching nodes. We'll sync these nodes at the end. We
3947 // don't sync them now because we want to sync all the nodes in order
3948 // from beginning to end.
3949 // At this point we narrowed the old and new lists to the point
3950 // where the nodes no longer match.
3951 // 3. Walk the narrowed part of the old list to get the list of
3952 // keys.
3953 // 4. Walk the narrowed part of the new list forwards:
3954 // * Create a new _RouteEntry for non-keyed items and record them for
3955 // transitionDelegate.
3956 // * Sync keyed items with the source if it exists.
3957 // 5. Walk the narrowed part of the old list again to records the
3958 // _RouteEntry(s), as well as pageless routes, needed to be removed for
3959 // transitionDelegate.
3960 // 5. Walk the top of the list again, syncing the nodes and recording
3961 // pageless routes.
3962 // 6. Use transitionDelegate for explicit decisions on how _RouteEntry(s)
3963 // transition in or off the screens.
3964 // 7. Fill pageless routes back into the new history.
3965
3966 bool needsExplicitDecision = false;
3967 int newPagesBottom = 0;
3968 int oldEntriesBottom = 0;
3969 int newPagesTop = widget.pages.length - 1;
3970 int oldEntriesTop = _history.length - 1;
3971
3972 final List<_RouteEntry> newHistory = <_RouteEntry>[];
3973 final Map<_RouteEntry?, List<_RouteEntry>> pageRouteToPagelessRoutes = <_RouteEntry?, List<_RouteEntry>>{};
3974
3975 // Updates the bottom of the list.
3976 _RouteEntry? previousOldPageRouteEntry;
3977 while (oldEntriesBottom <= oldEntriesTop) {
3978 final _RouteEntry oldEntry = _history[oldEntriesBottom];
3979 assert(oldEntry.currentState != _RouteLifecycle.disposed);
3980 // Records pageless route. The bottom most pageless routes will be
3981 // stored in key = null.
3982 if (!oldEntry.pageBased) {
3983 final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes.putIfAbsent(
3984 previousOldPageRouteEntry,
3985 () => <_RouteEntry>[],
3986 );
3987 pagelessRoutes.add(oldEntry);
3988 oldEntriesBottom += 1;
3989 continue;
3990 }
3991 if (newPagesBottom > newPagesTop) {
3992 break;
3993 }
3994 final Page<dynamic> newPage = widget.pages[newPagesBottom];
3995 if (!oldEntry.canUpdateFrom(newPage)) {
3996 break;
3997 }
3998 previousOldPageRouteEntry = oldEntry;
3999 oldEntry.route._updateSettings(newPage);
4000 newHistory.add(oldEntry);
4001 newPagesBottom += 1;
4002 oldEntriesBottom += 1;
4003 }
4004
4005 final List<_RouteEntry> unattachedPagelessRoutes=<_RouteEntry>[];
4006 // Scans the top of the list until we found a page-based route that cannot be
4007 // updated.
4008 while ((oldEntriesBottom <= oldEntriesTop) && (newPagesBottom <= newPagesTop)) {
4009 final _RouteEntry oldEntry = _history[oldEntriesTop];
4010 assert(oldEntry.currentState != _RouteLifecycle.disposed);
4011 if (!oldEntry.pageBased) {
4012 unattachedPagelessRoutes.add(oldEntry);
4013 oldEntriesTop -= 1;
4014 continue;
4015 }
4016 final Page<dynamic> newPage = widget.pages[newPagesTop];
4017 if (!oldEntry.canUpdateFrom(newPage)) {
4018 break;
4019 }
4020
4021 // We found the page for all the consecutive pageless routes below. Attach these
4022 // pageless routes to the page.
4023 if (unattachedPagelessRoutes.isNotEmpty) {
4024 pageRouteToPagelessRoutes.putIfAbsent(
4025 oldEntry,
4026 () => List<_RouteEntry>.from(unattachedPagelessRoutes),
4027 );
4028 unattachedPagelessRoutes.clear();
4029 }
4030
4031 oldEntriesTop -= 1;
4032 newPagesTop -= 1;
4033 }
4034 // Reverts the pageless routes that cannot be updated.
4035 oldEntriesTop += unattachedPagelessRoutes.length;
4036
4037 // Scans middle of the old entries and records the page key to old entry map.
4038 int oldEntriesBottomToScan = oldEntriesBottom;
4039 final Map<LocalKey, _RouteEntry> pageKeyToOldEntry = <LocalKey, _RouteEntry>{};
4040 // This set contains entries that are transitioning out but are still in
4041 // the route stack.
4042 final Set<_RouteEntry> phantomEntries = <_RouteEntry>{};
4043 while (oldEntriesBottomToScan <= oldEntriesTop) {
4044 final _RouteEntry oldEntry = _history[oldEntriesBottomToScan];
4045 oldEntriesBottomToScan += 1;
4046 assert(
4047 oldEntry.currentState != _RouteLifecycle.disposed,
4048 );
4049 // Pageless routes will be recorded when we update the middle of the old
4050 // list.
4051 if (!oldEntry.pageBased) {
4052 continue;
4053 }
4054
4055 final Page<dynamic> page = oldEntry.route.settings as Page<dynamic>;
4056 if (page.key == null) {
4057 continue;
4058 }
4059
4060 if (!oldEntry.willBePresent) {
4061 phantomEntries.add(oldEntry);
4062 continue;
4063 }
4064 assert(!pageKeyToOldEntry.containsKey(page.key));
4065 pageKeyToOldEntry[page.key!] = oldEntry;
4066 }
4067
4068 // Updates the middle of the list.
4069 while (newPagesBottom <= newPagesTop) {
4070 final Page<dynamic> nextPage = widget.pages[newPagesBottom];
4071 newPagesBottom += 1;
4072 if (
4073 nextPage.key == null ||
4074 !pageKeyToOldEntry.containsKey(nextPage.key) ||
4075 !pageKeyToOldEntry[nextPage.key]!.canUpdateFrom(nextPage)
4076 ) {
4077 // There is no matching key in the old history, we need to create a new
4078 // route and wait for the transition delegate to decide how to add
4079 // it into the history.
4080 final _RouteEntry newEntry = _RouteEntry(
4081 nextPage.createRoute(context),
4082 pageBased: true,
4083 initialState: _RouteLifecycle.staging,
4084 );
4085 needsExplicitDecision = true;
4086 assert(
4087 newEntry.route.settings == nextPage,
4088 'The settings getter of a page-based Route must return a Page object. '
4089 'Please set the settings to the Page in the Page.createRoute method.',
4090 );
4091 newHistory.add(newEntry);
4092 } else {
4093 // Removes the key from pageKeyToOldEntry to indicate it is taken.
4094 final _RouteEntry matchingEntry = pageKeyToOldEntry.remove(nextPage.key)!;
4095 assert(matchingEntry.canUpdateFrom(nextPage));
4096 matchingEntry.route._updateSettings(nextPage);
4097 newHistory.add(matchingEntry);
4098 }
4099 }
4100
4101 // Any remaining old routes that do not have a match will need to be removed.
4102 final Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute = <RouteTransitionRecord?, RouteTransitionRecord>{};
4103 while (oldEntriesBottom <= oldEntriesTop) {
4104 final _RouteEntry potentialEntryToRemove = _history[oldEntriesBottom];
4105 oldEntriesBottom += 1;
4106
4107 if (!potentialEntryToRemove.pageBased) {
4108 assert(previousOldPageRouteEntry != null);
4109 final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes
4110 .putIfAbsent(
4111 previousOldPageRouteEntry,
4112 () => <_RouteEntry>[],
4113 );
4114 pagelessRoutes.add(potentialEntryToRemove);
4115 if (previousOldPageRouteEntry!.isWaitingForExitingDecision && potentialEntryToRemove.willBePresent) {
4116 potentialEntryToRemove.markNeedsExitingDecision();
4117 }
4118 continue;
4119 }
4120
4121 final Page<dynamic> potentialPageToRemove = potentialEntryToRemove.route.settings as Page<dynamic>;
4122 // Marks for transition delegate to remove if this old page does not have
4123 // a key, was not taken during updating the middle of new page, or is
4124 // already transitioning out.
4125 if (potentialPageToRemove.key == null ||
4126 pageKeyToOldEntry.containsKey(potentialPageToRemove.key) ||
4127 phantomEntries.contains(potentialEntryToRemove)) {
4128 locationToExitingPageRoute[previousOldPageRouteEntry] = potentialEntryToRemove;
4129 // We only need a decision if it has not already been popped.
4130 if (potentialEntryToRemove.willBePresent) {
4131 potentialEntryToRemove.markNeedsExitingDecision();
4132 }
4133 }
4134 previousOldPageRouteEntry = potentialEntryToRemove;
4135 }
4136
4137 // We've scanned the whole list.
4138 assert(oldEntriesBottom == oldEntriesTop + 1);
4139 assert(newPagesBottom == newPagesTop + 1);
4140 newPagesTop = widget.pages.length - 1;
4141 oldEntriesTop = _history.length - 1;
4142 // Verifies we either reach the bottom or the oldEntriesBottom must be updatable
4143 // by newPagesBottom.
4144 assert(() {
4145 if (oldEntriesBottom <= oldEntriesTop) {
4146 return newPagesBottom <= newPagesTop &&
4147 _history[oldEntriesBottom].pageBased &&
4148 _history[oldEntriesBottom].canUpdateFrom(widget.pages[newPagesBottom]);
4149 } else {
4150 return newPagesBottom > newPagesTop;
4151 }
4152 }());
4153
4154 // Updates the top of the list.
4155 while ((oldEntriesBottom <= oldEntriesTop) && (newPagesBottom <= newPagesTop)) {
4156 final _RouteEntry oldEntry = _history[oldEntriesBottom];
4157 assert(oldEntry.currentState != _RouteLifecycle.disposed);
4158 if (!oldEntry.pageBased) {
4159 assert(previousOldPageRouteEntry != null);
4160 final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes
4161 .putIfAbsent(
4162 previousOldPageRouteEntry,
4163 () => <_RouteEntry>[],
4164 );
4165 pagelessRoutes.add(oldEntry);
4166 continue;
4167 }
4168 previousOldPageRouteEntry = oldEntry;
4169 final Page<dynamic> newPage = widget.pages[newPagesBottom];
4170 assert(oldEntry.canUpdateFrom(newPage));
4171 oldEntry.route._updateSettings(newPage);
4172 newHistory.add(oldEntry);
4173 oldEntriesBottom += 1;
4174 newPagesBottom += 1;
4175 }
4176
4177 // Finally, uses transition delegate to make explicit decision if needed.
4178 needsExplicitDecision = needsExplicitDecision || locationToExitingPageRoute.isNotEmpty;
4179 Iterable<_RouteEntry> results = newHistory;
4180 if (needsExplicitDecision) {
4181 results = widget.transitionDelegate._transition(
4182 newPageRouteHistory: newHistory,
4183 locationToExitingPageRoute: locationToExitingPageRoute,
4184 pageRouteToPagelessRoutes: pageRouteToPagelessRoutes,
4185 ).cast<_RouteEntry>();
4186 }
4187 _history.clear();
4188 // Adds the leading pageless routes if there is any.
4189 if (pageRouteToPagelessRoutes.containsKey(null)) {
4190 _history.addAll(pageRouteToPagelessRoutes[null]!);
4191 }
4192 for (final _RouteEntry result in results) {
4193 _history.add(result);
4194 if (pageRouteToPagelessRoutes.containsKey(result)) {
4195 _history.addAll(pageRouteToPagelessRoutes[result]!);
4196 }
4197 }
4198 assert(() {_debugUpdatingPage = false; return true;}());
4199 assert(() { _debugLocked = true; return true; }());
4200 _flushHistoryUpdates();
4201 assert(() { _debugLocked = false; return true; }());
4202 }
4203
4204 bool _flushingHistory = false;
4205
4206 void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
4207 assert(_debugLocked && !_debugUpdatingPage);
4208 _flushingHistory = true;
4209 // Clean up the list, sending updates to the routes that changed. Notably,
4210 // we don't send the didChangePrevious/didChangeNext updates to those that
4211 // did not change at this point, because we're not yet sure exactly what the
4212 // routes will be at the end of the day (some might get disposed).
4213 int index = _history.length - 1;
4214 _RouteEntry? next;
4215 _RouteEntry? entry = _history[index];
4216 _RouteEntry? previous = index > 0 ? _history[index - 1] : null;
4217 bool canRemoveOrAdd = false; // Whether there is a fully opaque route on top to silently remove or add route underneath.
4218 Route<dynamic>? poppedRoute; // The route that should trigger didPopNext on the top active route.
4219 bool seenTopActiveRoute = false; // Whether we've seen the route that would get didPopNext.
4220 final List<_RouteEntry> toBeDisposed = <_RouteEntry>[];
4221 while (index >= 0) {
4222 switch (entry!.currentState) {
4223 case _RouteLifecycle.add:
4224 assert(rearrangeOverlay);
4225 entry.handleAdd(
4226 navigator: this,
4227 previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
4228 );
4229 assert(entry.currentState == _RouteLifecycle.adding);
4230 continue;
4231 case _RouteLifecycle.adding:
4232 if (canRemoveOrAdd || next == null) {
4233 entry.didAdd(
4234 navigator: this,
4235 isNewFirst: next == null,
4236 );
4237 assert(entry.currentState == _RouteLifecycle.idle);
4238 continue;
4239 }
4240 case _RouteLifecycle.push:
4241 case _RouteLifecycle.pushReplace:
4242 case _RouteLifecycle.replace:
4243 assert(rearrangeOverlay);
4244 entry.handlePush(
4245 navigator: this,
4246 previous: previous?.route,
4247 previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
4248 isNewFirst: next == null,
4249 );
4250 assert(entry.currentState != _RouteLifecycle.push);
4251 assert(entry.currentState != _RouteLifecycle.pushReplace);
4252 assert(entry.currentState != _RouteLifecycle.replace);
4253 if (entry.currentState == _RouteLifecycle.idle) {
4254 continue;
4255 }
4256 case _RouteLifecycle.pushing: // Will exit this state when animation completes.
4257 if (!seenTopActiveRoute && poppedRoute != null) {
4258 entry.handleDidPopNext(poppedRoute);
4259 }
4260 seenTopActiveRoute = true;
4261 case _RouteLifecycle.idle:
4262 if (!seenTopActiveRoute && poppedRoute != null) {
4263 entry.handleDidPopNext(poppedRoute);
4264 }
4265 seenTopActiveRoute = true;
4266 // This route is idle, so we are allowed to remove subsequent (earlier)
4267 // routes that are waiting to be removed silently:
4268 canRemoveOrAdd = true;
4269 case _RouteLifecycle.pop:
4270 if (!entry.handlePop(
4271 navigator: this,
4272 previousPresent: _getRouteBefore(index, _RouteEntry.willBePresentPredicate)?.route)){
4273 assert(entry.currentState == _RouteLifecycle.idle);
4274 continue;
4275 }
4276 if (!seenTopActiveRoute) {
4277 if (poppedRoute != null) {
4278 entry.handleDidPopNext(poppedRoute);
4279 }
4280 poppedRoute = entry.route;
4281 }
4282 _observedRouteDeletions.add(
4283 _NavigatorPopObservation(entry.route, _getRouteBefore(index, _RouteEntry.willBePresentPredicate)?.route),
4284 );
4285 if (entry.currentState == _RouteLifecycle.dispose) {
4286 // The pop finished synchronously. This can happen if transition
4287 // duration is zero.
4288 continue;
4289 }
4290 assert(entry.currentState == _RouteLifecycle.popping);
4291 canRemoveOrAdd = true;
4292 case _RouteLifecycle.popping:
4293 // Will exit this state when animation completes.
4294 break;
4295 case _RouteLifecycle.complete:
4296 entry.handleComplete();
4297 assert(entry.currentState == _RouteLifecycle.remove);
4298 continue;
4299 case _RouteLifecycle.remove:
4300 if (!seenTopActiveRoute) {
4301 if (poppedRoute != null) {
4302 entry.route.didPopNext(poppedRoute);
4303 }
4304 poppedRoute = null;
4305 }
4306 entry.handleRemoval(
4307 navigator: this,
4308 previousPresent: _getRouteBefore(index, _RouteEntry.willBePresentPredicate)?.route,
4309 );
4310 assert(entry.currentState == _RouteLifecycle.removing);
4311 continue;
4312 case _RouteLifecycle.removing:
4313 if (!canRemoveOrAdd && next != null) {
4314 // We aren't allowed to remove this route yet.
4315 break;
4316 }
4317 entry.currentState = _RouteLifecycle.dispose;
4318 continue;
4319 case _RouteLifecycle.dispose:
4320 // Delay disposal until didChangeNext/didChangePrevious have been sent.
4321 toBeDisposed.add(_history.removeAt(index));
4322 entry = next;
4323 case _RouteLifecycle.disposing:
4324 case _RouteLifecycle.disposed:
4325 case _RouteLifecycle.staging:
4326 assert(false);
4327 }
4328 index -= 1;
4329 next = entry;
4330 entry = previous;
4331 previous = index > 0 ? _history[index - 1] : null;
4332 }
4333 // Informs navigator observers about route changes.
4334 _flushObserverNotifications();
4335
4336 // Now that the list is clean, send the didChangeNext/didChangePrevious
4337 // notifications.
4338 _flushRouteAnnouncement();
4339
4340 // Announce route name changes.
4341 if (widget.reportsRouteUpdateToEngine) {
4342 final _RouteEntry? lastEntry = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
4343 final String? routeName = lastEntry?.route.settings.name;
4344 if (routeName != null && routeName != _lastAnnouncedRouteName) {
4345 SystemNavigator.routeInformationUpdated(uri: Uri.parse(routeName));
4346 _lastAnnouncedRouteName = routeName;
4347 }
4348 }
4349
4350 // Lastly, removes the overlay entries of all marked entries and disposes
4351 // them.
4352 for (final _RouteEntry entry in toBeDisposed) {
4353 _disposeRouteEntry(entry, graceful: true);
4354 }
4355 if (rearrangeOverlay) {
4356 overlay?.rearrange(_allRouteOverlayEntries);
4357 }
4358 if (bucket != null) {
4359 _serializableHistory.update(_history);
4360 }
4361 _flushingHistory = false;
4362 }
4363
4364 void _flushObserverNotifications() {
4365 if (_effectiveObservers.isEmpty) {
4366 _observedRouteDeletions.clear();
4367 _observedRouteAdditions.clear();
4368 return;
4369 }
4370 while (_observedRouteAdditions.isNotEmpty) {
4371 final _NavigatorObservation observation = _observedRouteAdditions.removeLast();
4372 _effectiveObservers.forEach(observation.notify);
4373 }
4374
4375 while (_observedRouteDeletions.isNotEmpty) {
4376 final _NavigatorObservation observation = _observedRouteDeletions.removeFirst();
4377 _effectiveObservers.forEach(observation.notify);
4378 }
4379 }
4380
4381 void _flushRouteAnnouncement() {
4382 int index = _history.length - 1;
4383 while (index >= 0) {
4384 final _RouteEntry entry = _history[index];
4385 if (!entry.suitableForAnnouncement) {
4386 index -= 1;
4387 continue;
4388 }
4389 final _RouteEntry? next = _getRouteAfter(index + 1, _RouteEntry.suitableForTransitionAnimationPredicate);
4390
4391 if (next?.route != entry.lastAnnouncedNextRoute) {
4392 if (entry.shouldAnnounceChangeToNext(next?.route)) {
4393 entry.route.didChangeNext(next?.route);
4394 }
4395 entry.lastAnnouncedNextRoute = next?.route;
4396 }
4397 final _RouteEntry? previous = _getRouteBefore(index - 1, _RouteEntry.suitableForTransitionAnimationPredicate);
4398 if (previous?.route != entry.lastAnnouncedPreviousRoute) {
4399 entry.route.didChangePrevious(previous?.route);
4400 entry.lastAnnouncedPreviousRoute = previous?.route;
4401 }
4402 index -= 1;
4403 }
4404 }
4405
4406 _RouteEntry? _getRouteBefore(int index, _RouteEntryPredicate predicate) {
4407 index = _getIndexBefore(index, predicate);
4408 return index >= 0 ? _history[index] : null;
4409 }
4410
4411 int _getIndexBefore(int index, _RouteEntryPredicate predicate) {
4412 while (index >= 0 && !predicate(_history[index])) {
4413 index -= 1;
4414 }
4415 return index;
4416 }
4417
4418 _RouteEntry? _getRouteAfter(int index, _RouteEntryPredicate predicate) {
4419 while (index < _history.length && !predicate(_history[index])) {
4420 index += 1;
4421 }
4422 return index < _history.length ? _history[index] : null;
4423 }
4424
4425 Route<T?>? _routeNamed<T>(String name, { required Object? arguments, bool allowNull = false }) {
4426 assert(!_debugLocked);
4427 if (allowNull && widget.onGenerateRoute == null) {
4428 return null;
4429 }
4430 assert(() {
4431 if (widget.onGenerateRoute == null) {
4432 throw FlutterError(
4433 'Navigator.onGenerateRoute was null, but the route named "$name" was referenced.\n'
4434 'To use the Navigator API with named routes (pushNamed, pushReplacementNamed, or '
4435 'pushNamedAndRemoveUntil), the Navigator must be provided with an '
4436 'onGenerateRoute handler.\n'
4437 'The Navigator was:\n'
4438 ' $this',
4439 );
4440 }
4441 return true;
4442 }());
4443 final RouteSettings settings = RouteSettings(
4444 name: name,
4445 arguments: arguments,
4446 );
4447 Route<T?>? route = widget.onGenerateRoute!(settings) as Route<T?>?;
4448 if (route == null && !allowNull) {
4449 assert(() {
4450 if (widget.onUnknownRoute == null) {
4451 throw FlutterError.fromParts(<DiagnosticsNode>[
4452 ErrorSummary('Navigator.onGenerateRoute returned null when requested to build route "$name".'),
4453 ErrorDescription(
4454 'The onGenerateRoute callback must never return null, unless an onUnknownRoute '
4455 'callback is provided as well.',
4456 ),
4457 DiagnosticsProperty<NavigatorState>('The Navigator was', this, style: DiagnosticsTreeStyle.errorProperty),
4458 ]);
4459 }
4460 return true;
4461 }());
4462 route = widget.onUnknownRoute!(settings) as Route<T?>?;
4463 assert(() {
4464 if (route == null) {
4465 throw FlutterError.fromParts(<DiagnosticsNode>[
4466 ErrorSummary('Navigator.onUnknownRoute returned null when requested to build route "$name".'),
4467 ErrorDescription('The onUnknownRoute callback must never return null.'),
4468 DiagnosticsProperty<NavigatorState>('The Navigator was', this, style: DiagnosticsTreeStyle.errorProperty),
4469 ]);
4470 }
4471 return true;
4472 }());
4473 }
4474 assert(route != null || allowNull);
4475 return route;
4476 }
4477
4478 /// Push a named route onto the navigator.
4479 ///
4480 /// {@macro flutter.widgets.navigator.pushNamed}
4481 ///
4482 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
4483 ///
4484 /// {@macro flutter.widgets.Navigator.pushNamed}
4485 ///
4486 /// {@tool snippet}
4487 ///
4488 /// Typical usage is as follows:
4489 ///
4490 /// ```dart
4491 /// void _aaronBurrSir() {
4492 /// navigator.pushNamed('/nyc/1776');
4493 /// }
4494 /// ```
4495 /// {@end-tool}
4496 ///
4497 /// See also:
4498 ///
4499 /// * [restorablePushNamed], which pushes a route that can be restored
4500 /// during state restoration.
4501 @optionalTypeArgs
4502 Future<T?> pushNamed<T extends Object?>(
4503 String routeName, {
4504 Object? arguments,
4505 }) {
4506 return push<T?>(_routeNamed<T>(routeName, arguments: arguments)!);
4507 }
4508
4509 /// Push a named route onto the navigator.
4510 ///
4511 /// {@macro flutter.widgets.navigator.restorablePushNamed}
4512 ///
4513 /// {@macro flutter.widgets.navigator.pushNamed}
4514 ///
4515 /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
4516 ///
4517 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
4518 ///
4519 /// {@tool snippet}
4520 ///
4521 /// Typical usage is as follows:
4522 ///
4523 /// ```dart
4524 /// void _openDetails() {
4525 /// navigator.restorablePushNamed('/nyc/1776');
4526 /// }
4527 /// ```
4528 /// {@end-tool}
4529 @optionalTypeArgs
4530 String restorablePushNamed<T extends Object?>(
4531 String routeName, {
4532 Object? arguments,
4533 }) {
4534 assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
4535 final _RouteEntry entry = _RestorationInformation.named(
4536 name: routeName,
4537 arguments: arguments,
4538 restorationScopeId: _nextPagelessRestorationScopeId,
4539 ).toRouteEntry(this, initialState: _RouteLifecycle.push);
4540 _pushEntry(entry);
4541 return entry.restorationId!;
4542 }
4543
4544 /// Replace the current route of the navigator by pushing the route named
4545 /// [routeName] and then disposing the previous route once the new route has
4546 /// finished animating in.
4547 ///
4548 /// {@macro flutter.widgets.navigator.pushReplacementNamed}
4549 ///
4550 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
4551 ///
4552 /// {@macro flutter.widgets.Navigator.pushNamed}
4553 ///
4554 /// {@tool snippet}
4555 ///
4556 /// Typical usage is as follows:
4557 ///
4558 /// ```dart
4559 /// void _startBike() {
4560 /// navigator.pushReplacementNamed('/jouett/1781');
4561 /// }
4562 /// ```
4563 /// {@end-tool}
4564 ///
4565 /// See also:
4566 ///
4567 /// * [restorablePushReplacementNamed], which pushes a replacement route that
4568 /// can be restored during state restoration.
4569 @optionalTypeArgs
4570 Future<T?> pushReplacementNamed<T extends Object?, TO extends Object?>(
4571 String routeName, {
4572 TO? result,
4573 Object? arguments,
4574 }) {
4575 return pushReplacement<T?, TO>(_routeNamed<T>(routeName, arguments: arguments)!, result: result);
4576 }
4577
4578 /// Replace the current route of the navigator by pushing the route named
4579 /// [routeName] and then disposing the previous route once the new route has
4580 /// finished animating in.
4581 ///
4582 /// {@macro flutter.widgets.navigator.restorablePushReplacementNamed}
4583 ///
4584 /// {@macro flutter.widgets.navigator.pushReplacementNamed}
4585 ///
4586 /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
4587 ///
4588 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
4589 ///
4590 /// {@tool snippet}
4591 ///
4592 /// Typical usage is as follows:
4593 ///
4594 /// ```dart
4595 /// void _startCar() {
4596 /// navigator.restorablePushReplacementNamed('/jouett/1781');
4597 /// }
4598 /// ```
4599 /// {@end-tool}
4600 @optionalTypeArgs
4601 String restorablePushReplacementNamed<T extends Object?, TO extends Object?>(
4602 String routeName, {
4603 TO? result,
4604 Object? arguments,
4605 }) {
4606 assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
4607 final _RouteEntry entry = _RestorationInformation.named(
4608 name: routeName,
4609 arguments: arguments,
4610 restorationScopeId: _nextPagelessRestorationScopeId,
4611 ).toRouteEntry(this, initialState: _RouteLifecycle.pushReplace);
4612 _pushReplacementEntry(entry, result);
4613 return entry.restorationId!;
4614 }
4615
4616 /// Pop the current route off the navigator and push a named route in its
4617 /// place.
4618 ///
4619 /// {@macro flutter.widgets.navigator.popAndPushNamed}
4620 ///
4621 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
4622 ///
4623 /// {@macro flutter.widgets.Navigator.pushNamed}
4624 ///
4625 /// {@tool snippet}
4626 ///
4627 /// Typical usage is as follows:
4628 ///
4629 /// ```dart
4630 /// void _begin() {
4631 /// navigator.popAndPushNamed('/nyc/1776');
4632 /// }
4633 /// ```
4634 /// {@end-tool}
4635 ///
4636 /// See also:
4637 ///
4638 /// * [restorablePopAndPushNamed], which pushes a new route that can be
4639 /// restored during state restoration.
4640 @optionalTypeArgs
4641 Future<T?> popAndPushNamed<T extends Object?, TO extends Object?>(
4642 String routeName, {
4643 TO? result,
4644 Object? arguments,
4645 }) {
4646 pop<TO>(result);
4647 return pushNamed<T>(routeName, arguments: arguments);
4648 }
4649
4650 /// Pop the current route off the navigator and push a named route in its
4651 /// place.
4652 ///
4653 /// {@macro flutter.widgets.navigator.restorablePopAndPushNamed}
4654 ///
4655 /// {@macro flutter.widgets.navigator.popAndPushNamed}
4656 ///
4657 /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
4658 ///
4659 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
4660 ///
4661 /// {@tool snippet}
4662 ///
4663 /// Typical usage is as follows:
4664 ///
4665 /// ```dart
4666 /// void _end() {
4667 /// navigator.restorablePopAndPushNamed('/nyc/1776');
4668 /// }
4669 /// ```
4670 /// {@end-tool}
4671 @optionalTypeArgs
4672 String restorablePopAndPushNamed<T extends Object?, TO extends Object?>(
4673 String routeName, {
4674 TO? result,
4675 Object? arguments,
4676 }) {
4677 pop<TO>(result);
4678 return restorablePushNamed(routeName, arguments: arguments);
4679 }
4680
4681 /// Push the route with the given name onto the navigator, and then remove all
4682 /// the previous routes until the `predicate` returns true.
4683 ///
4684 /// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
4685 ///
4686 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
4687 ///
4688 /// {@macro flutter.widgets.Navigator.pushNamed}
4689 ///
4690 /// {@tool snippet}
4691 ///
4692 /// Typical usage is as follows:
4693 ///
4694 /// ```dart
4695 /// void _handleOpenCalendar() {
4696 /// navigator.pushNamedAndRemoveUntil('/calendar', ModalRoute.withName('/'));
4697 /// }
4698 /// ```
4699 /// {@end-tool}
4700 ///
4701 /// See also:
4702 ///
4703 /// * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
4704 /// be restored during state restoration.
4705 @optionalTypeArgs
4706 Future<T?> pushNamedAndRemoveUntil<T extends Object?>(
4707 String newRouteName,
4708 RoutePredicate predicate, {
4709 Object? arguments,
4710 }) {
4711 return pushAndRemoveUntil<T?>(_routeNamed<T>(newRouteName, arguments: arguments)!, predicate);
4712 }
4713
4714 /// Push the route with the given name onto the navigator, and then remove all
4715 /// the previous routes until the `predicate` returns true.
4716 ///
4717 /// {@macro flutter.widgets.navigator.restorablePushNamedAndRemoveUntil}
4718 ///
4719 /// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
4720 ///
4721 /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
4722 ///
4723 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
4724 ///
4725 /// {@tool snippet}
4726 ///
4727 /// Typical usage is as follows:
4728 ///
4729 /// ```dart
4730 /// void _openCalendar() {
4731 /// navigator.restorablePushNamedAndRemoveUntil('/calendar', ModalRoute.withName('/'));
4732 /// }
4733 /// ```
4734 /// {@end-tool}
4735 @optionalTypeArgs
4736 String restorablePushNamedAndRemoveUntil<T extends Object?>(
4737 String newRouteName,
4738 RoutePredicate predicate, {
4739 Object? arguments,
4740 }) {
4741 assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
4742 final _RouteEntry entry = _RestorationInformation.named(
4743 name: newRouteName,
4744 arguments: arguments,
4745 restorationScopeId: _nextPagelessRestorationScopeId,
4746 ).toRouteEntry(this, initialState: _RouteLifecycle.push);
4747 _pushEntryAndRemoveUntil(entry, predicate);
4748 return entry.restorationId!;
4749 }
4750
4751 /// Push the given route onto the navigator.
4752 ///
4753 /// {@macro flutter.widgets.navigator.push}
4754 ///
4755 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
4756 ///
4757 /// {@tool snippet}
4758 ///
4759 /// Typical usage is as follows:
4760 ///
4761 /// ```dart
4762 /// void _openPage() {
4763 /// navigator.push<void>(
4764 /// MaterialPageRoute<void>(
4765 /// builder: (BuildContext context) => const MyPage(),
4766 /// ),
4767 /// );
4768 /// }
4769 /// ```
4770 /// {@end-tool}
4771 ///
4772 /// See also:
4773 ///
4774 /// * [restorablePush], which pushes a route that can be restored during
4775 /// state restoration.
4776 @optionalTypeArgs
4777 Future<T?> push<T extends Object?>(Route<T> route) {
4778 _pushEntry(_RouteEntry(route, pageBased: false, initialState: _RouteLifecycle.push));
4779 return route.popped;
4780 }
4781
4782 bool _debugIsStaticCallback(Function callback) {
4783 bool result = false;
4784 assert(() {
4785 // TODO(goderbauer): remove the kIsWeb check when https://github.com/flutter/flutter/issues/33615 is resolved.
4786 result = kIsWeb || ui.PluginUtilities.getCallbackHandle(callback) != null;
4787 return true;
4788 }());
4789 return result;
4790 }
4791
4792 /// Push a new route onto the navigator.
4793 ///
4794 /// {@macro flutter.widgets.navigator.restorablePush}
4795 ///
4796 /// {@macro flutter.widgets.navigator.push}
4797 ///
4798 /// {@macro flutter.widgets.Navigator.restorablePush}
4799 ///
4800 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
4801 ///
4802 /// {@tool dartpad}
4803 /// Typical usage is as follows:
4804 ///
4805 /// ** See code in examples/api/lib/widgets/navigator/navigator_state.restorable_push.0.dart **
4806 /// {@end-tool}
4807 @optionalTypeArgs
4808 String restorablePush<T extends Object?>(RestorableRouteBuilder<T> routeBuilder, {Object? arguments}) {
4809 assert(_debugIsStaticCallback(routeBuilder), 'The provided routeBuilder must be a static function.');
4810 assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
4811 final _RouteEntry entry = _RestorationInformation.anonymous(
4812 routeBuilder: routeBuilder,
4813 arguments: arguments,
4814 restorationScopeId: _nextPagelessRestorationScopeId,
4815 ).toRouteEntry(this, initialState: _RouteLifecycle.push);
4816 _pushEntry(entry);
4817 return entry.restorationId!;
4818 }
4819
4820 void _pushEntry(_RouteEntry entry) {
4821 assert(!_debugLocked);
4822 assert(() {
4823 _debugLocked = true;
4824 return true;
4825 }());
4826 assert(entry.route._navigator == null);
4827 assert(entry.currentState == _RouteLifecycle.push);
4828 _history.add(entry);
4829 _flushHistoryUpdates();
4830 assert(() {
4831 _debugLocked = false;
4832 return true;
4833 }());
4834 _afterNavigation(entry.route);
4835 }
4836
4837 void _afterNavigation(Route<dynamic>? route) {
4838 if (!kReleaseMode) {
4839 // Among other uses, performance tools use this event to ensure that perf
4840 // stats reflect the time interval since the last navigation event
4841 // occurred, ensuring that stats only reflect the current page.
4842
4843 Map<String, dynamic>? routeJsonable;
4844 if (route != null) {
4845 routeJsonable = <String, dynamic>{};
4846
4847 final String description;
4848 if (route is TransitionRoute<dynamic>) {
4849 final TransitionRoute<dynamic> transitionRoute = route;
4850 description = transitionRoute.debugLabel;
4851 } else {
4852 description = '$route';
4853 }
4854 routeJsonable['description'] = description;
4855
4856 final RouteSettings settings = route.settings;
4857 final Map<String, dynamic> settingsJsonable = <String, dynamic> {
4858 'name': settings.name,
4859 };
4860 if (settings.arguments != null) {
4861 settingsJsonable['arguments'] = jsonEncode(
4862 settings.arguments,
4863 toEncodable: (Object? object) => '$object',
4864 );
4865 }
4866 routeJsonable['settings'] = settingsJsonable;
4867 }
4868
4869 developer.postEvent('Flutter.Navigation', <String, dynamic>{
4870 'route': routeJsonable,
4871 });
4872 }
4873 _cancelActivePointers();
4874 }
4875
4876 /// Replace the current route of the navigator by pushing the given route and
4877 /// then disposing the previous route once the new route has finished
4878 /// animating in.
4879 ///
4880 /// {@macro flutter.widgets.navigator.pushReplacement}
4881 ///
4882 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
4883 ///
4884 /// {@tool snippet}
4885 ///
4886 /// Typical usage is as follows:
4887 ///
4888 /// ```dart
4889 /// void _doOpenPage() {
4890 /// navigator.pushReplacement<void, void>(
4891 /// MaterialPageRoute<void>(
4892 /// builder: (BuildContext context) => const MyHomePage(),
4893 /// ),
4894 /// );
4895 /// }
4896 /// ```
4897 /// {@end-tool}
4898 ///
4899 /// See also:
4900 ///
4901 /// * [restorablePushReplacement], which pushes a replacement route that can
4902 /// be restored during state restoration.
4903 @optionalTypeArgs
4904 Future<T?> pushReplacement<T extends Object?, TO extends Object?>(Route<T> newRoute, { TO? result }) {
4905 assert(newRoute._navigator == null);
4906 _pushReplacementEntry(_RouteEntry(newRoute, pageBased: false, initialState: _RouteLifecycle.pushReplace), result);
4907 return newRoute.popped;
4908 }
4909
4910 /// Replace the current route of the navigator by pushing a new route and
4911 /// then disposing the previous route once the new route has finished
4912 /// animating in.
4913 ///
4914 /// {@macro flutter.widgets.navigator.restorablePushReplacement}
4915 ///
4916 /// {@macro flutter.widgets.navigator.pushReplacement}
4917 ///
4918 /// {@macro flutter.widgets.Navigator.restorablePush}
4919 ///
4920 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
4921 ///
4922 /// {@tool dartpad}
4923 /// Typical usage is as follows:
4924 ///
4925 /// ** See code in examples/api/lib/widgets/navigator/navigator_state.restorable_push_replacement.0.dart **
4926 /// {@end-tool}
4927 @optionalTypeArgs
4928 String restorablePushReplacement<T extends Object?, TO extends Object?>(RestorableRouteBuilder<T> routeBuilder, { TO? result, Object? arguments }) {
4929 assert(_debugIsStaticCallback(routeBuilder), 'The provided routeBuilder must be a static function.');
4930 assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
4931 final _RouteEntry entry = _RestorationInformation.anonymous(
4932 routeBuilder: routeBuilder,
4933 arguments: arguments,
4934 restorationScopeId: _nextPagelessRestorationScopeId,
4935 ).toRouteEntry(this, initialState: _RouteLifecycle.pushReplace);
4936 _pushReplacementEntry(entry, result);
4937 return entry.restorationId!;
4938 }
4939
4940 void _pushReplacementEntry<TO extends Object?>(_RouteEntry entry, TO? result) {
4941 assert(!_debugLocked);
4942 assert(() {
4943 _debugLocked = true;
4944 return true;
4945 }());
4946 assert(entry.route._navigator == null);
4947 assert(_history.isNotEmpty);
4948 assert(_history.any(_RouteEntry.isPresentPredicate), 'Navigator has no active routes to replace.');
4949 assert(entry.currentState == _RouteLifecycle.pushReplace);
4950 _history.lastWhere(_RouteEntry.isPresentPredicate).complete(result, isReplaced: true);
4951 _history.add(entry);
4952 _flushHistoryUpdates();
4953 assert(() {
4954 _debugLocked = false;
4955 return true;
4956 }());
4957 _afterNavigation(entry.route);
4958 }
4959
4960 /// Push the given route onto the navigator, and then remove all the previous
4961 /// routes until the `predicate` returns true.
4962 ///
4963 /// {@macro flutter.widgets.navigator.pushAndRemoveUntil}
4964 ///
4965 /// {@macro flutter.widgets.navigator.pushNamed.returnValue}
4966 ///
4967 /// {@tool snippet}
4968 ///
4969 /// Typical usage is as follows:
4970 ///
4971 /// ```dart
4972 /// void _resetAndOpenPage() {
4973 /// navigator.pushAndRemoveUntil<void>(
4974 /// MaterialPageRoute<void>(builder: (BuildContext context) => const MyHomePage()),
4975 /// ModalRoute.withName('/'),
4976 /// );
4977 /// }
4978 /// ```
4979 /// {@end-tool}
4980 ///
4981 ///
4982 /// See also:
4983 ///
4984 /// * [restorablePushAndRemoveUntil], which pushes a route that can be
4985 /// restored during state restoration.
4986 @optionalTypeArgs
4987 Future<T?> pushAndRemoveUntil<T extends Object?>(Route<T> newRoute, RoutePredicate predicate) {
4988 assert(newRoute._navigator == null);
4989 assert(newRoute.overlayEntries.isEmpty);
4990 _pushEntryAndRemoveUntil(_RouteEntry(newRoute, pageBased: false, initialState: _RouteLifecycle.push), predicate);
4991 return newRoute.popped;
4992 }
4993
4994 /// Push a new route onto the navigator, and then remove all the previous
4995 /// routes until the `predicate` returns true.
4996 ///
4997 /// {@macro flutter.widgets.navigator.restorablePushAndRemoveUntil}
4998 ///
4999 /// {@macro flutter.widgets.navigator.pushAndRemoveUntil}
5000 ///
5001 /// {@macro flutter.widgets.Navigator.restorablePush}
5002 ///
5003 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
5004 ///
5005 /// {@tool dartpad}
5006 /// Typical usage is as follows:
5007 ///
5008 /// ** See code in examples/api/lib/widgets/navigator/navigator_state.restorable_push_and_remove_until.0.dart **
5009 /// {@end-tool}
5010 @optionalTypeArgs
5011 String restorablePushAndRemoveUntil<T extends Object?>(RestorableRouteBuilder<T> newRouteBuilder, RoutePredicate predicate, {Object? arguments}) {
5012 assert(_debugIsStaticCallback(newRouteBuilder), 'The provided routeBuilder must be a static function.');
5013 assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
5014 final _RouteEntry entry = _RestorationInformation.anonymous(
5015 routeBuilder: newRouteBuilder,
5016 arguments: arguments,
5017 restorationScopeId: _nextPagelessRestorationScopeId,
5018 ).toRouteEntry(this, initialState: _RouteLifecycle.push);
5019 _pushEntryAndRemoveUntil(entry, predicate);
5020 return entry.restorationId!;
5021 }
5022
5023 void _pushEntryAndRemoveUntil(_RouteEntry entry, RoutePredicate predicate) {
5024 assert(!_debugLocked);
5025 assert(() {
5026 _debugLocked = true;
5027 return true;
5028 }());
5029 assert(entry.route._navigator == null);
5030 assert(entry.route.overlayEntries.isEmpty);
5031 assert(entry.currentState == _RouteLifecycle.push);
5032 int index = _history.length - 1;
5033 _history.add(entry);
5034 while (index >= 0 && !predicate(_history[index].route)) {
5035 if (_history[index].isPresent) {
5036 _history[index].remove();
5037 }
5038 index -= 1;
5039 }
5040 _flushHistoryUpdates();
5041
5042 assert(() {
5043 _debugLocked = false;
5044 return true;
5045 }());
5046 _afterNavigation(entry.route);
5047 }
5048
5049 /// Replaces a route on the navigator with a new route.
5050 ///
5051 /// {@macro flutter.widgets.navigator.replace}
5052 ///
5053 /// See also:
5054 ///
5055 /// * [replaceRouteBelow], which is the same but identifies the route to be
5056 /// removed by reference to the route above it, rather than directly.
5057 /// * [restorableReplace], which adds a replacement route that can be
5058 /// restored during state restoration.
5059 @optionalTypeArgs
5060 void replace<T extends Object?>({ required Route<dynamic> oldRoute, required Route<T> newRoute }) {
5061 assert(!_debugLocked);
5062 assert(oldRoute._navigator == this);
5063 _replaceEntry(_RouteEntry(newRoute, pageBased: false, initialState: _RouteLifecycle.replace), oldRoute);
5064 }
5065
5066 /// Replaces a route on the navigator with a new route.
5067 ///
5068 /// {@macro flutter.widgets.navigator.restorableReplace}
5069 ///
5070 /// {@macro flutter.widgets.navigator.replace}
5071 ///
5072 /// {@macro flutter.widgets.Navigator.restorablePush}
5073 ///
5074 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
5075 @optionalTypeArgs
5076 String restorableReplace<T extends Object?>({ required Route<dynamic> oldRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
5077 assert(oldRoute._navigator == this);
5078 assert(_debugIsStaticCallback(newRouteBuilder), 'The provided routeBuilder must be a static function.');
5079 assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
5080 final _RouteEntry entry = _RestorationInformation.anonymous(
5081 routeBuilder: newRouteBuilder,
5082 arguments: arguments,
5083 restorationScopeId: _nextPagelessRestorationScopeId,
5084 ).toRouteEntry(this, initialState: _RouteLifecycle.replace);
5085 _replaceEntry(entry, oldRoute);
5086 return entry.restorationId!;
5087 }
5088
5089 void _replaceEntry(_RouteEntry entry, Route<dynamic> oldRoute) {
5090 assert(!_debugLocked);
5091 if (oldRoute == entry.route) {
5092 return;
5093 }
5094 assert(() {
5095 _debugLocked = true;
5096 return true;
5097 }());
5098 assert(entry.currentState == _RouteLifecycle.replace);
5099 assert(entry.route._navigator == null);
5100 final int index = _history.indexWhere(_RouteEntry.isRoutePredicate(oldRoute));
5101 assert(index >= 0, 'This Navigator does not contain the specified oldRoute.');
5102 assert(_history[index].isPresent, 'The specified oldRoute has already been removed from the Navigator.');
5103 final bool wasCurrent = oldRoute.isCurrent;
5104 _history.insert(index + 1, entry);
5105 _history[index].remove(isReplaced: true);
5106 _flushHistoryUpdates();
5107 assert(() {
5108 _debugLocked = false;
5109 return true;
5110 }());
5111 if (wasCurrent) {
5112 _afterNavigation(entry.route);
5113 }
5114 }
5115
5116 /// Replaces a route on the navigator with a new route. The route to be
5117 /// replaced is the one below the given `anchorRoute`.
5118 ///
5119 /// {@macro flutter.widgets.navigator.replaceRouteBelow}
5120 ///
5121 /// See also:
5122 ///
5123 /// * [replace], which is the same but identifies the route to be removed
5124 /// directly.
5125 /// * [restorableReplaceRouteBelow], which adds a replacement route that can
5126 /// be restored during state restoration.
5127 @optionalTypeArgs
5128 void replaceRouteBelow<T extends Object?>({ required Route<dynamic> anchorRoute, required Route<T> newRoute }) {
5129 assert(newRoute._navigator == null);
5130 assert(anchorRoute._navigator == this);
5131 _replaceEntryBelow(_RouteEntry(newRoute, pageBased: false, initialState: _RouteLifecycle.replace), anchorRoute);
5132 }
5133
5134 /// Replaces a route on the navigator with a new route. The route to be
5135 /// replaced is the one below the given `anchorRoute`.
5136 ///
5137 /// {@macro flutter.widgets.navigator.restorableReplaceRouteBelow}
5138 ///
5139 /// {@macro flutter.widgets.navigator.replaceRouteBelow}
5140 ///
5141 /// {@macro flutter.widgets.Navigator.restorablePush}
5142 ///
5143 /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
5144 @optionalTypeArgs
5145 String restorableReplaceRouteBelow<T extends Object?>({ required Route<dynamic> anchorRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
5146 assert(anchorRoute._navigator == this);
5147 assert(_debugIsStaticCallback(newRouteBuilder), 'The provided routeBuilder must be a static function.');
5148 assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
5149 final _RouteEntry entry = _RestorationInformation.anonymous(
5150 routeBuilder: newRouteBuilder,
5151 arguments: arguments,
5152 restorationScopeId: _nextPagelessRestorationScopeId,
5153 ).toRouteEntry(this, initialState: _RouteLifecycle.replace);
5154 _replaceEntryBelow(entry, anchorRoute);
5155 return entry.restorationId!;
5156 }
5157
5158 void _replaceEntryBelow(_RouteEntry entry, Route<dynamic> anchorRoute) {
5159 assert(!_debugLocked);
5160 assert(() { _debugLocked = true; return true; }());
5161 final int anchorIndex = _history.indexWhere(_RouteEntry.isRoutePredicate(anchorRoute));
5162 assert(anchorIndex >= 0, 'This Navigator does not contain the specified anchorRoute.');
5163 assert(_history[anchorIndex].isPresent, 'The specified anchorRoute has already been removed from the Navigator.');
5164 int index = anchorIndex - 1;
5165 while (index >= 0) {
5166 if (_history[index].isPresent) {
5167 break;
5168 }
5169 index -= 1;
5170 }
5171 assert(index >= 0, 'There are no routes below the specified anchorRoute.');
5172 _history.insert(index + 1, entry);
5173 _history[index].remove(isReplaced: true);
5174 _flushHistoryUpdates();
5175 assert(() { _debugLocked = false; return true; }());
5176 }
5177
5178 /// Whether the navigator can be popped.
5179 ///
5180 /// {@macro flutter.widgets.navigator.canPop}
5181 ///
5182 /// See also:
5183 ///
5184 /// * [Route.isFirst], which returns true for routes for which [canPop]
5185 /// returns false.
5186 bool canPop() {
5187 final Iterator<_RouteEntry> iterator = _history.where(_RouteEntry.isPresentPredicate).iterator;
5188 if (!iterator.moveNext()) {
5189 // We have no active routes, so we can't pop.
5190 return false;
5191 }
5192 if (iterator.current.route.willHandlePopInternally) {
5193 // The first route can handle pops itself, so we can pop.
5194 return true;
5195 }
5196 if (!iterator.moveNext()) {
5197 // There's only one route, so we can't pop.
5198 return false;
5199 }
5200 return true; // there's at least two routes, so we can pop
5201 }
5202
5203 /// Consults the current route's [Route.popDisposition] method, and acts
5204 /// accordingly, potentially popping the route as a result; returns whether
5205 /// the pop request should be considered handled.
5206 ///
5207 /// {@macro flutter.widgets.navigator.maybePop}
5208 ///
5209 /// See also:
5210 ///
5211 /// * [Form], which provides a [Form.canPop] boolean that enables the
5212 /// form to prevent any [pop]s initiated by the app's back button.
5213 /// * [ModalRoute], which provides a `scopedOnPopCallback` that can be used
5214 /// to define the route's `willPop` method.
5215 @optionalTypeArgs
5216 Future<bool> maybePop<T extends Object?>([ T? result ]) async {
5217 final _RouteEntry? lastEntry = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
5218 if (lastEntry == null) {
5219 return false;
5220 }
5221 assert(lastEntry.route._navigator == this);
5222
5223 // TODO(justinmc): When the deprecated willPop method is removed, delete
5224 // this code and use only popDisposition, below.
5225 final RoutePopDisposition willPopDisposition = await lastEntry.route.willPop();
5226 if (!mounted) {
5227 // Forget about this pop, we were disposed in the meantime.
5228 return true;
5229 }
5230 if (willPopDisposition == RoutePopDisposition.doNotPop) {
5231 return true;
5232 }
5233 final _RouteEntry? newLastEntry = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
5234 if (lastEntry != newLastEntry) {
5235 // Forget about this pop, something happened to our history in the meantime.
5236 return true;
5237 }
5238
5239 switch (lastEntry.route.popDisposition) {
5240 case RoutePopDisposition.bubble:
5241 return false;
5242 case RoutePopDisposition.pop:
5243 pop(result);
5244 return true;
5245 case RoutePopDisposition.doNotPop:
5246 lastEntry.route.onPopInvoked(false);
5247 return true;
5248 }
5249 }
5250
5251 /// Pop the top-most route off the navigator.
5252 ///
5253 /// {@macro flutter.widgets.navigator.pop}
5254 ///
5255 /// {@tool snippet}
5256 ///
5257 /// Typical usage for closing a route is as follows:
5258 ///
5259 /// ```dart
5260 /// void _handleClose() {
5261 /// navigator.pop();
5262 /// }
5263 /// ```
5264 /// {@end-tool}
5265 /// {@tool snippet}
5266 ///
5267 /// A dialog box might be closed with a result:
5268 ///
5269 /// ```dart
5270 /// void _handleAccept() {
5271 /// navigator.pop(true); // dialog returns true
5272 /// }
5273 /// ```
5274 /// {@end-tool}
5275 @optionalTypeArgs
5276 void pop<T extends Object?>([ T? result ]) {
5277 assert(!_debugLocked);
5278 assert(() {
5279 _debugLocked = true;
5280 return true;
5281 }());
5282 final _RouteEntry entry = _history.lastWhere(_RouteEntry.isPresentPredicate);
5283 if (entry.pageBased) {
5284 if (widget.onPopPage!(entry.route, result) && entry.currentState == _RouteLifecycle.idle) {
5285 // The entry may have been disposed if the pop finishes synchronously.
5286 assert(entry.route._popCompleter.isCompleted);
5287 entry.currentState = _RouteLifecycle.pop;
5288 }
5289 } else {
5290 entry.pop<T>(result);
5291 assert (entry.currentState == _RouteLifecycle.pop);
5292 }
5293 if (entry.currentState == _RouteLifecycle.pop) {
5294 _flushHistoryUpdates(rearrangeOverlay: false);
5295 }
5296 assert(entry.currentState == _RouteLifecycle.idle || entry.route._popCompleter.isCompleted);
5297 assert(() {
5298 _debugLocked = false;
5299 return true;
5300 }());
5301 _afterNavigation(entry.route);
5302 }
5303
5304 /// Calls [pop] repeatedly until the predicate returns true.
5305 ///
5306 /// {@macro flutter.widgets.navigator.popUntil}
5307 ///
5308 /// {@tool snippet}
5309 ///
5310 /// Typical usage is as follows:
5311 ///
5312 /// ```dart
5313 /// void _doLogout() {
5314 /// navigator.popUntil(ModalRoute.withName('/login'));
5315 /// }
5316 /// ```
5317 /// {@end-tool}
5318 void popUntil(RoutePredicate predicate) {
5319 _RouteEntry? candidate = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
5320 while (candidate != null) {
5321 if (predicate(candidate.route)) {
5322 return;
5323 }
5324 pop();
5325 candidate = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
5326 }
5327 }
5328
5329 /// Immediately remove `route` from the navigator, and [Route.dispose] it.
5330 ///
5331 /// {@macro flutter.widgets.navigator.removeRoute}
5332 void removeRoute(Route<dynamic> route) {
5333 assert(!_debugLocked);
5334 assert(() {
5335 _debugLocked = true;
5336 return true;
5337 }());
5338 assert(route._navigator == this);
5339 final bool wasCurrent = route.isCurrent;
5340 final _RouteEntry entry = _history.firstWhere(_RouteEntry.isRoutePredicate(route));
5341 entry.remove();
5342 _flushHistoryUpdates(rearrangeOverlay: false);
5343 assert(() {
5344 _debugLocked = false;
5345 return true;
5346 }());
5347 if (wasCurrent) {
5348 _afterNavigation(
5349 _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate)?.route,
5350 );
5351 }
5352 }
5353
5354 /// Immediately remove a route from the navigator, and [Route.dispose] it. The
5355 /// route to be removed is the one below the given `anchorRoute`.
5356 ///
5357 /// {@macro flutter.widgets.navigator.removeRouteBelow}
5358 void removeRouteBelow(Route<dynamic> anchorRoute) {
5359 assert(!_debugLocked);
5360 assert(() {
5361 _debugLocked = true;
5362 return true;
5363 }());
5364 assert(anchorRoute._navigator == this);
5365 final int anchorIndex = _history.indexWhere(_RouteEntry.isRoutePredicate(anchorRoute));
5366 assert(anchorIndex >= 0, 'This Navigator does not contain the specified anchorRoute.');
5367 assert(_history[anchorIndex].isPresent, 'The specified anchorRoute has already been removed from the Navigator.');
5368 int index = anchorIndex - 1;
5369 while (index >= 0) {
5370 if (_history[index].isPresent) {
5371 break;
5372 }
5373 index -= 1;
5374 }
5375 assert(index >= 0, 'There are no routes below the specified anchorRoute.');
5376 _history[index].remove();
5377 _flushHistoryUpdates(rearrangeOverlay: false);
5378 assert(() {
5379 _debugLocked = false;
5380 return true;
5381 }());
5382 }
5383
5384 /// Complete the lifecycle for a route that has been popped off the navigator.
5385 ///
5386 /// When the navigator pops a route, the navigator retains a reference to the
5387 /// route in order to call [Route.dispose] if the navigator itself is removed
5388 /// from the tree. When the route is finished with any exit animation, the
5389 /// route should call this function to complete its lifecycle (e.g., to
5390 /// receive a call to [Route.dispose]).
5391 ///
5392 /// The given `route` must have already received a call to [Route.didPop].
5393 /// This function may be called directly from [Route.didPop] if [Route.didPop]
5394 /// will return true.
5395 void finalizeRoute(Route<dynamic> route) {
5396 // FinalizeRoute may have been called while we were already locked as a
5397 // responds to route.didPop(). Make sure to leave in the state we were in
5398 // before the call.
5399 bool? wasDebugLocked;
5400 assert(() { wasDebugLocked = _debugLocked; _debugLocked = true; return true; }());
5401 assert(_history.where(_RouteEntry.isRoutePredicate(route)).length == 1);
5402 final int index = _history.indexWhere(_RouteEntry.isRoutePredicate(route));
5403 final _RouteEntry entry = _history[index];
5404 // For page-based route with zero transition, the finalizeRoute can be
5405 // called on any life cycle above pop.
5406 if (entry.pageBased && entry.currentState.index < _RouteLifecycle.pop.index) {
5407 _observedRouteDeletions.add(_NavigatorPopObservation(route, _getRouteBefore(index - 1, _RouteEntry.willBePresentPredicate)?.route));
5408 } else {
5409 assert(entry.currentState == _RouteLifecycle.popping);
5410 }
5411 entry.finalize();
5412 // finalizeRoute can be called during _flushHistoryUpdates if a pop
5413 // finishes synchronously.
5414 if (!_flushingHistory) {
5415 _flushHistoryUpdates(rearrangeOverlay: false);
5416 }
5417
5418 assert(() { _debugLocked = wasDebugLocked!; return true; }());
5419 }
5420
5421 @optionalTypeArgs
5422 Route<T>? _getRouteById<T>(String id) {
5423 return _firstRouteEntryWhereOrNull((_RouteEntry entry) => entry.restorationId == id)?.route as Route<T>?;
5424 }
5425
5426 int get _userGesturesInProgress => _userGesturesInProgressCount;
5427 int _userGesturesInProgressCount = 0;
5428 set _userGesturesInProgress(int value) {
5429 _userGesturesInProgressCount = value;
5430 userGestureInProgressNotifier.value = _userGesturesInProgress > 0;
5431 }
5432
5433 /// Whether a route is currently being manipulated by the user, e.g.
5434 /// as during an iOS back gesture.
5435 ///
5436 /// See also:
5437 ///
5438 /// * [userGestureInProgressNotifier], which notifies its listeners if
5439 /// the value of [userGestureInProgress] changes.
5440 bool get userGestureInProgress => userGestureInProgressNotifier.value;
5441
5442 /// Notifies its listeners if the value of [userGestureInProgress] changes.
5443 final ValueNotifier<bool> userGestureInProgressNotifier = ValueNotifier<bool>(false);
5444
5445 /// The navigator is being controlled by a user gesture.
5446 ///
5447 /// For example, called when the user beings an iOS back gesture.
5448 ///
5449 /// When the gesture finishes, call [didStopUserGesture].
5450 void didStartUserGesture() {
5451 _userGesturesInProgress += 1;
5452 if (_userGesturesInProgress == 1) {
5453 final int routeIndex = _getIndexBefore(
5454 _history.length - 1,
5455 _RouteEntry.willBePresentPredicate,
5456 );
5457 final Route<dynamic> route = _history[routeIndex].route;
5458 Route<dynamic>? previousRoute;
5459 if (!route.willHandlePopInternally && routeIndex > 0) {
5460 previousRoute = _getRouteBefore(
5461 routeIndex - 1,
5462 _RouteEntry.willBePresentPredicate,
5463 )!.route;
5464 }
5465 for (final NavigatorObserver observer in _effectiveObservers) {
5466 observer.didStartUserGesture(route, previousRoute);
5467 }
5468 }
5469 }
5470
5471 /// A user gesture completed.
5472 ///
5473 /// Notifies the navigator that a gesture regarding which the navigator was
5474 /// previously notified with [didStartUserGesture] has completed.
5475 void didStopUserGesture() {
5476 assert(_userGesturesInProgress > 0);
5477 _userGesturesInProgress -= 1;
5478 if (_userGesturesInProgress == 0) {
5479 for (final NavigatorObserver observer in _effectiveObservers) {
5480 observer.didStopUserGesture();
5481 }
5482 }
5483 }
5484
5485 final Set<int> _activePointers = <int>{};
5486
5487 void _handlePointerDown(PointerDownEvent event) {
5488 _activePointers.add(event.pointer);
5489 }
5490
5491 void _handlePointerUpOrCancel(PointerEvent event) {
5492 _activePointers.remove(event.pointer);
5493 }
5494
5495 void _cancelActivePointers() {
5496 // TODO(abarth): This mechanism is far from perfect. See https://github.com/flutter/flutter/issues/4770
5497 if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
5498 // If we're between frames (SchedulerPhase.idle) then absorb any
5499 // subsequent pointers from this frame. The absorbing flag will be
5500 // reset in the next frame, see build().
5501 final RenderAbsorbPointer? absorber = _overlayKey.currentContext?.findAncestorRenderObjectOfType<RenderAbsorbPointer>();
5502 setState(() {
5503 absorber?.absorbing = true;
5504 // We do this in setState so that we'll reset the absorbing value back
5505 // to false on the next frame.
5506 });
5507 }
5508 _activePointers.toList().forEach(WidgetsBinding.instance.cancelPointer);
5509 }
5510
5511 /// Gets first route entry satisfying the predicate, or null if not found.
5512 _RouteEntry? _firstRouteEntryWhereOrNull(_RouteEntryPredicate test) {
5513 for (final _RouteEntry element in _history) {
5514 if (test(element)) {
5515 return element;
5516 }
5517 }
5518 return null;
5519 }
5520
5521 /// Gets last route entry satisfying the predicate, or null if not found.
5522 _RouteEntry? _lastRouteEntryWhereOrNull(_RouteEntryPredicate test) {
5523 _RouteEntry? result;
5524 for (final _RouteEntry element in _history) {
5525 if (test(element)) {
5526 result = element;
5527 }
5528 }
5529 return result;
5530 }
5531
5532 @override
5533 Widget build(BuildContext context) {
5534 assert(!_debugLocked);
5535 assert(_history.isNotEmpty);
5536
5537 // Hides the HeroControllerScope for the widget subtree so that the other
5538 // nested navigator underneath will not pick up the hero controller above
5539 // this level.
5540 return HeroControllerScope.none(
5541 child: NotificationListener<NavigationNotification>(
5542 onNotification: (NavigationNotification notification) {
5543 // If the state of this Navigator does not change whether or not the
5544 // whole framework can pop, propagate the Notification as-is.
5545 if (notification.canHandlePop || !canPop()) {
5546 return false;
5547 }
5548 // Otherwise, dispatch a new Notification with the correct canPop and
5549 // stop the propagation of the old Notification.
5550 const NavigationNotification nextNotification = NavigationNotification(
5551 canHandlePop: true,
5552 );
5553 nextNotification.dispatch(context);
5554 return true;
5555 },
5556 child: Listener(
5557 onPointerDown: _handlePointerDown,
5558 onPointerUp: _handlePointerUpOrCancel,
5559 onPointerCancel: _handlePointerUpOrCancel,
5560 child: AbsorbPointer(
5561 absorbing: false, // it's mutated directly by _cancelActivePointers above
5562 child: FocusTraversalGroup(
5563 policy: FocusTraversalGroup.maybeOf(context),
5564 child: Focus(
5565 focusNode: focusNode,
5566 autofocus: true,
5567 skipTraversal: true,
5568 includeSemantics: false,
5569 child: UnmanagedRestorationScope(
5570 bucket: bucket,
5571 child: Overlay(
5572 key: _overlayKey,
5573 clipBehavior: widget.clipBehavior,
5574 initialEntries: overlay == null ? _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[],
5575 ),
5576 ),
5577 ),
5578 ),
5579 ),
5580 ),
5581 ),
5582 );
5583 }
5584}
5585
5586enum _RouteRestorationType {
5587 named,
5588 anonymous,
5589}
5590
5591abstract class _RestorationInformation {
5592 _RestorationInformation(this.type);
5593 factory _RestorationInformation.named({
5594 required String name,
5595 required Object? arguments,
5596 required int restorationScopeId,
5597 }) = _NamedRestorationInformation;
5598 factory _RestorationInformation.anonymous({
5599 required RestorableRouteBuilder<Object?> routeBuilder,
5600 required Object? arguments,
5601 required int restorationScopeId,
5602 }) = _AnonymousRestorationInformation;
5603
5604 factory _RestorationInformation.fromSerializableData(Object data) {
5605 final List<Object?> casted = data as List<Object?>;
5606 assert(casted.isNotEmpty);
5607 final _RouteRestorationType type = _RouteRestorationType.values[casted[0]! as int];
5608 switch (type) {
5609 case _RouteRestorationType.named:
5610 return _NamedRestorationInformation.fromSerializableData(casted.sublist(1));
5611 case _RouteRestorationType.anonymous:
5612 return _AnonymousRestorationInformation.fromSerializableData(casted.sublist(1));
5613 }
5614 }
5615
5616 final _RouteRestorationType type;
5617 int get restorationScopeId;
5618 Object? _serializableData;
5619
5620 bool get isRestorable => true;
5621
5622 Object getSerializableData() {
5623 _serializableData ??= computeSerializableData();
5624 return _serializableData!;
5625 }
5626
5627 @mustCallSuper
5628 List<Object> computeSerializableData() {
5629 return <Object>[type.index];
5630 }
5631
5632 @protected
5633 Route<dynamic> createRoute(NavigatorState navigator);
5634
5635 _RouteEntry toRouteEntry(NavigatorState navigator, {_RouteLifecycle initialState = _RouteLifecycle.add}) {
5636 final Route<Object?> route = createRoute(navigator);
5637 return _RouteEntry(
5638 route,
5639 pageBased: false,
5640 initialState: initialState,
5641 restorationInformation: this,
5642 );
5643 }
5644}
5645
5646class _NamedRestorationInformation extends _RestorationInformation {
5647 _NamedRestorationInformation({
5648 required this.name,
5649 required this.arguments,
5650 required this.restorationScopeId,
5651 }) : super(_RouteRestorationType.named);
5652
5653 factory _NamedRestorationInformation.fromSerializableData(List<Object?> data) {
5654 assert(data.length >= 2);
5655 return _NamedRestorationInformation(
5656 restorationScopeId: data[0]! as int,
5657 name: data[1]! as String,
5658 arguments: data.length > 2 ? data[2] : null,
5659 );
5660 }
5661
5662 @override
5663 List<Object> computeSerializableData() {
5664 return super.computeSerializableData()..addAll(<Object>[
5665 restorationScopeId,
5666 name,
5667 if (arguments != null)
5668 arguments!,
5669 ]);
5670 }
5671
5672 @override
5673 final int restorationScopeId;
5674 final String name;
5675 final Object? arguments;
5676
5677 @override
5678 Route<dynamic> createRoute(NavigatorState navigator) {
5679 final Route<dynamic> route = navigator._routeNamed<dynamic>(name, arguments: arguments)!;
5680 return route;
5681 }
5682}
5683
5684class _AnonymousRestorationInformation extends _RestorationInformation {
5685 _AnonymousRestorationInformation({
5686 required this.routeBuilder,
5687 required this.arguments,
5688 required this.restorationScopeId,
5689 }) : super(_RouteRestorationType.anonymous);
5690
5691 factory _AnonymousRestorationInformation.fromSerializableData(List<Object?> data) {
5692 assert(data.length > 1);
5693 final RestorableRouteBuilder<Object?> routeBuilder = ui.PluginUtilities.getCallbackFromHandle(ui.CallbackHandle.fromRawHandle(data[1]! as int))! as RestorableRouteBuilder;
5694 return _AnonymousRestorationInformation(
5695 restorationScopeId: data[0]! as int,
5696 routeBuilder: routeBuilder,
5697 arguments: data.length > 2 ? data[2] : null,
5698 );
5699 }
5700
5701 @override
5702 // TODO(goderbauer): remove the kIsWeb check when https://github.com/flutter/flutter/issues/33615 is resolved.
5703 bool get isRestorable => !kIsWeb;
5704
5705 @override
5706 List<Object> computeSerializableData() {
5707 assert(isRestorable);
5708 final ui.CallbackHandle? handle = ui.PluginUtilities.getCallbackHandle(routeBuilder);
5709 assert(handle != null);
5710 return super.computeSerializableData()..addAll(<Object>[
5711 restorationScopeId,
5712 handle!.toRawHandle(),
5713 if (arguments != null)
5714 arguments!,
5715 ]);
5716 }
5717
5718 @override
5719 final int restorationScopeId;
5720 final RestorableRouteBuilder<Object?> routeBuilder;
5721 final Object? arguments;
5722
5723 @override
5724 Route<dynamic> createRoute(NavigatorState navigator) {
5725 final Route<dynamic> result = routeBuilder(navigator.context, arguments);
5726 return result;
5727 }
5728}
5729
5730class _HistoryProperty extends RestorableProperty<Map<String?, List<Object>>?> {
5731 // Routes not associated with a page are stored under key 'null'.
5732 Map<String?, List<Object>>? _pageToPagelessRoutes;
5733
5734 // Updating.
5735
5736 void update(_History history) {
5737 assert(isRegistered);
5738 final bool wasUninitialized = _pageToPagelessRoutes == null;
5739 bool needsSerialization = wasUninitialized;
5740 _pageToPagelessRoutes ??= <String, List<Object>>{};
5741 _RouteEntry? currentPage;
5742 List<Object> newRoutesForCurrentPage = <Object>[];
5743 List<Object> oldRoutesForCurrentPage = _pageToPagelessRoutes![null] ?? const <Object>[];
5744 bool restorationEnabled = true;
5745
5746 final Map<String?, List<Object>> newMap = <String?, List<Object>>{};
5747 final Set<String?> removedPages = _pageToPagelessRoutes!.keys.toSet();
5748
5749 for (final _RouteEntry entry in history) {
5750 if (!entry.isPresentForRestoration) {
5751 entry.restorationEnabled = false;
5752 continue;
5753 }
5754
5755 assert(entry.isPresentForRestoration);
5756 if (entry.pageBased) {
5757 needsSerialization = needsSerialization || newRoutesForCurrentPage.length != oldRoutesForCurrentPage.length;
5758 _finalizeEntry(newRoutesForCurrentPage, currentPage, newMap, removedPages);
5759 currentPage = entry;
5760 restorationEnabled = entry.restorationId != null;
5761 entry.restorationEnabled = restorationEnabled;
5762 if (restorationEnabled) {
5763 assert(entry.restorationId != null);
5764 newRoutesForCurrentPage = <Object>[];
5765 oldRoutesForCurrentPage = _pageToPagelessRoutes![entry.restorationId] ?? const <Object>[];
5766 } else {
5767 newRoutesForCurrentPage = const <Object>[];
5768 oldRoutesForCurrentPage = const <Object>[];
5769 }
5770 continue;
5771 }
5772
5773 assert(!entry.pageBased);
5774 restorationEnabled = restorationEnabled && (entry.restorationInformation?.isRestorable ?? false);
5775 entry.restorationEnabled = restorationEnabled;
5776 if (restorationEnabled) {
5777 assert(entry.restorationId != null);
5778 assert(currentPage == null || currentPage.restorationId != null);
5779 assert(entry.restorationInformation != null);
5780 final Object serializedData = entry.restorationInformation!.getSerializableData();
5781 needsSerialization = needsSerialization
5782 || oldRoutesForCurrentPage.length <= newRoutesForCurrentPage.length
5783 || oldRoutesForCurrentPage[newRoutesForCurrentPage.length] != serializedData;
5784 newRoutesForCurrentPage.add(serializedData);
5785 }
5786 }
5787 needsSerialization = needsSerialization || newRoutesForCurrentPage.length != oldRoutesForCurrentPage.length;
5788 _finalizeEntry(newRoutesForCurrentPage, currentPage, newMap, removedPages);
5789
5790 needsSerialization = needsSerialization || removedPages.isNotEmpty;
5791
5792 assert(wasUninitialized || _debugMapsEqual(_pageToPagelessRoutes!, newMap) != needsSerialization);
5793
5794 if (needsSerialization) {
5795 _pageToPagelessRoutes = newMap;
5796 notifyListeners();
5797 }
5798 }
5799
5800 void _finalizeEntry(
5801 List<Object> routes,
5802 _RouteEntry? page,
5803 Map<String?, List<Object>> pageToRoutes,
5804 Set<String?> pagesToRemove,
5805 ) {
5806 assert(page == null || page.pageBased);
5807 assert(!pageToRoutes.containsKey(page?.restorationId));
5808 if (routes.isNotEmpty) {
5809 assert(page == null || page.restorationId != null);
5810 final String? restorationId = page?.restorationId;
5811 pageToRoutes[restorationId] = routes;
5812 pagesToRemove.remove(restorationId);
5813 }
5814 }
5815
5816 bool _debugMapsEqual(Map<String?, List<Object>> a, Map<String?, List<Object>> b) {
5817 if (!setEquals(a.keys.toSet(), b.keys.toSet())) {
5818 return false;
5819 }
5820 for (final String? key in a.keys) {
5821 if (!listEquals(a[key], b[key])) {
5822 return false;
5823 }
5824 }
5825 return true;
5826 }
5827
5828 void clear() {
5829 assert(isRegistered);
5830 if (_pageToPagelessRoutes == null) {
5831 return;
5832 }
5833 _pageToPagelessRoutes = null;
5834 notifyListeners();
5835 }
5836
5837 // Restoration.
5838
5839 bool get hasData => _pageToPagelessRoutes != null;
5840
5841 List<_RouteEntry> restoreEntriesForPage(_RouteEntry? page, NavigatorState navigator) {
5842 assert(isRegistered);
5843 assert(page == null || page.pageBased);
5844 final List<_RouteEntry> result = <_RouteEntry>[];
5845 if (_pageToPagelessRoutes == null || (page != null && page.restorationId == null)) {
5846 return result;
5847 }
5848 final List<Object>? serializedData = _pageToPagelessRoutes![page?.restorationId];
5849 if (serializedData == null) {
5850 return result;
5851 }
5852 for (final Object data in serializedData) {
5853 result.add(_RestorationInformation.fromSerializableData(data).toRouteEntry(navigator));
5854 }
5855 return result;
5856 }
5857
5858 // RestorableProperty overrides.
5859
5860 @override
5861 Map<String?, List<Object>>? createDefaultValue() {
5862 return null;
5863 }
5864
5865 @override
5866 Map<String?, List<Object>>? fromPrimitives(Object? data) {
5867 final Map<dynamic, dynamic> casted = data! as Map<dynamic, dynamic>;
5868 return casted.map<String?, List<Object>>((dynamic key, dynamic value) => MapEntry<String?, List<Object>>(
5869 key as String?,
5870 List<Object>.from(value as List<dynamic>),
5871 ));
5872 }
5873
5874 @override
5875 void initWithValue(Map<String?, List<Object>>? value) {
5876 _pageToPagelessRoutes = value;
5877 }
5878
5879 @override
5880 Object? toPrimitives() {
5881 return _pageToPagelessRoutes;
5882 }
5883
5884 @override
5885 bool get enabled => hasData;
5886}
5887
5888/// A callback that given a [BuildContext] finds a [NavigatorState].
5889///
5890/// Used by [RestorableRouteFuture.navigatorFinder] to determine the navigator
5891/// to which a new route should be added.
5892typedef NavigatorFinderCallback = NavigatorState Function(BuildContext context);
5893
5894/// A callback that given some `arguments` and a `navigator` adds a new
5895/// restorable route to that `navigator` and returns the opaque ID of that
5896/// new route.
5897///
5898/// Usually, this callback calls one of the imperative methods on the Navigator
5899/// that have "restorable" in the name and returns their return value.
5900///
5901/// Used by [RestorableRouteFuture.onPresent].
5902typedef RoutePresentationCallback = String Function(NavigatorState navigator, Object? arguments);
5903
5904/// A callback to handle the result of a completed [Route].
5905///
5906/// The return value of the route (which can be null for e.g. void routes) is
5907/// passed to the callback.
5908///
5909/// Used by [RestorableRouteFuture.onComplete].
5910typedef RouteCompletionCallback<T> = void Function(T result);
5911
5912/// Gives access to a [Route] object and its return value that was added to a
5913/// navigator via one of its "restorable" API methods.
5914///
5915/// When a [State] object wants access to the return value of a [Route] object
5916/// it has pushed onto the [Navigator], a [RestorableRouteFuture] ensures that
5917/// it will also have access to that value after state restoration.
5918///
5919/// To show a new route on the navigator defined by the [navigatorFinder], call
5920/// [present], which will invoke the [onPresent] callback. The [onPresent]
5921/// callback must add a new route to the navigator provided to it using one
5922/// of the "restorable" API methods. When the newly added route completes, the
5923/// [onComplete] callback executes. It is given the return value of the route,
5924/// which may be null.
5925///
5926/// While the route added via [present] is shown on the navigator, it can be
5927/// accessed via the [route] getter.
5928///
5929/// If the property is restored to a state in which [present] had been called on
5930/// it, but the route has not completed yet, the [RestorableRouteFuture] will
5931/// obtain the restored route object from the navigator again and call
5932/// [onComplete] once it completes.
5933///
5934/// The [RestorableRouteFuture] can only keep track of one active [route].
5935/// When [present] has been called to add a route, it may only be called again
5936/// after the previously added route has completed.
5937///
5938/// {@tool dartpad}
5939/// This example uses a [RestorableRouteFuture] in the `_MyHomeState` to push a
5940/// new `MyCounter` route and to retrieve its return value.
5941///
5942/// ** See code in examples/api/lib/widgets/navigator/restorable_route_future.0.dart **
5943/// {@end-tool}
5944class RestorableRouteFuture<T> extends RestorableProperty<String?> {
5945 /// Creates a [RestorableRouteFuture].
5946 RestorableRouteFuture({
5947 this.navigatorFinder = _defaultNavigatorFinder,
5948 required this.onPresent,
5949 this.onComplete,
5950 });
5951
5952 /// A callback that given the [BuildContext] of the [State] object to which
5953 /// this property is registered returns the [NavigatorState] of the navigator
5954 /// to which the route instantiated in [onPresent] is added.
5955 final NavigatorFinderCallback navigatorFinder;
5956
5957 /// A callback that add a new [Route] to the provided navigator.
5958 ///
5959 /// The callback must use one of the API methods on the [NavigatorState] that
5960 /// have "restorable" in their name (e.g. [NavigatorState.restorablePush],
5961 /// [NavigatorState.restorablePushNamed], etc.) and return the opaque ID
5962 /// returned by those methods.
5963 ///
5964 /// This callback is invoked when [present] is called with the `arguments`
5965 /// Object that was passed to that method and the [NavigatorState] obtained
5966 /// from [navigatorFinder].
5967 final RoutePresentationCallback onPresent;
5968
5969 /// A callback that is invoked when the [Route] added via [onPresent]
5970 /// completes.
5971 ///
5972 /// The return value of that route is passed to this method.
5973 final RouteCompletionCallback<T>? onComplete;
5974
5975 /// Shows the route created by [onPresent] and invoke [onComplete] when it
5976 /// completes.
5977 ///
5978 /// The `arguments` object is passed to [onPresent] and can be used to
5979 /// customize the route. It must be serializable via the
5980 /// [StandardMessageCodec]. Often, a [Map] is used to pass key-value pairs.
5981 void present([Object? arguments]) {
5982 assert(!isPresent);
5983 assert(isRegistered);
5984 final String routeId = onPresent(_navigator, arguments);
5985 _hookOntoRouteFuture(routeId);
5986 notifyListeners();
5987 }
5988
5989 /// Whether the [Route] created by [present] is currently shown.
5990 ///
5991 /// Returns true after [present] has been called until the [Route] completes.
5992 bool get isPresent => route != null;
5993
5994 /// The route that [present] added to the Navigator.
5995 ///
5996 /// Returns null when currently no route is shown
5997 Route<T>? get route => _route;
5998 Route<T>? _route;
5999
6000 @override
6001 String? createDefaultValue() => null;
6002
6003 @override
6004 void initWithValue(String? value) {
6005 if (value != null) {
6006 _hookOntoRouteFuture(value);
6007 }
6008 }
6009
6010 @override
6011 Object? toPrimitives() {
6012 assert(route != null);
6013 assert(enabled);
6014 return route?.restorationScopeId.value;
6015 }
6016
6017 @override
6018 String fromPrimitives(Object? data) {
6019 assert(data != null);
6020 return data! as String;
6021 }
6022
6023 bool _disposed = false;
6024
6025 @override
6026 void dispose() {
6027 super.dispose();
6028 _route?.restorationScopeId.removeListener(notifyListeners);
6029 _disposed = true;
6030 }
6031
6032 @override
6033 bool get enabled => route?.restorationScopeId.value != null;
6034
6035 NavigatorState get _navigator {
6036 final NavigatorState navigator = navigatorFinder(state.context);
6037 return navigator;
6038 }
6039
6040 void _hookOntoRouteFuture(String id) {
6041 _route = _navigator._getRouteById<T>(id);
6042 assert(_route != null);
6043 route!.restorationScopeId.addListener(notifyListeners);
6044 route!.popped.then((dynamic result) {
6045 if (_disposed) {
6046 return;
6047 }
6048 _route?.restorationScopeId.removeListener(notifyListeners);
6049 _route = null;
6050 notifyListeners();
6051 onComplete?.call(result as T);
6052 });
6053 }
6054
6055 static NavigatorState _defaultNavigatorFinder(BuildContext context) => Navigator.of(context);
6056}
6057
6058/// A notification that a change in navigation has taken place.
6059///
6060/// Specifically, this notification indicates that at least one of the following
6061/// has occurred:
6062///
6063/// * That route stack of a [Navigator] has changed in any way.
6064/// * The ability to pop has changed, such as controlled by [PopScope].
6065class NavigationNotification extends Notification {
6066 /// Creates a notification that some change in navigation has happened.
6067 const NavigationNotification({
6068 required this.canHandlePop,
6069 });
6070
6071 /// Indicates that the originator of this [Notification] is capable of
6072 /// handling a navigation pop.
6073 final bool canHandlePop;
6074
6075 @override
6076 String toString() {
6077 return 'NavigationNotification canHandlePop: $canHandlePop';
6078 }
6079}
6080