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

Provided by KDAB

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