navigator.dart 74.3 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:async';

7
import 'package:flutter/rendering.dart';
8
import 'package:flutter/scheduler.dart';
9

10 11
import 'basic.dart';
import 'binding.dart';
12 13
import 'focus_manager.dart';
import 'focus_scope.dart';
14
import 'framework.dart';
Adam Barth's avatar
Adam Barth committed
15
import 'overlay.dart';
16
import 'ticker_provider.dart';
17

18 19 20 21 22 23 24 25
// Examples can assume:
// class MyPage extends Placeholder { MyPage({String title}); }
// class MyHomePage extends Placeholder { }
// NavigatorState navigator;

/// Creates a route for the given route settings.
///
/// Used by [Navigator.onGenerateRoute] and [Navigator.onUnknownRoute].
26
typedef Route<dynamic> RouteFactory(RouteSettings settings);
27 28

/// Signature for the [Navigator.popUntil] predicate argument.
29
typedef bool RoutePredicate(Route<dynamic> route);
30 31 32 33 34

/// Signature for a callback that verifies that it's OK to call [Navigator.pop].
///
/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback],
/// [ModalRoute.removeScopedWillPopCallback], and [WillPopScope].
35
typedef Future<bool> WillPopCallback();
36

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
/// Indicates whether the current route should be popped.
///
/// Used as the return value for [Route.willPop].
///
/// See also:
///
///  * [WillPopScope], a widget that hooks into the route's [Route.willPop]
///    mechanism.
enum RoutePopDisposition {
  /// Pop the route.
  ///
  /// If [Route.willPop] returns [pop] then the back button will actually pop
  /// the current route.
  pop,

  /// Do not pop the route.
  ///
  /// If [Route.willPop] returns [doNotPop] then the back button will be ignored.
  doNotPop,

  /// Delegate this to the next level of navigation.
  ///
  /// If [Route.willPop] return [bubble] then the back button will be handled
  /// by the [SystemNavigator], which will usually close the application.
  bubble,
}

64 65 66 67 68 69
/// An abstraction for an entry managed by a [Navigator].
///
/// This class defines an abstract interface between the navigator and the
/// "routes" that are pushed on and popped off the navigator. Most routes have
/// visual affordances, which they place in the navigators [Overlay] using one
/// or more [OverlayEntry] objects.
70 71 72 73 74 75
///
/// See [Navigator] for more explanation of how to use a Route
/// with navigation, including code examples.
///
/// See [MaterialPageRoute] for a route that replaces the
/// entire screen with a platform-adaptive transition.
Hixie's avatar
Hixie committed
76
abstract class Route<T> {
77 78 79 80 81 82
  /// Initialize the [Route].
  ///
  /// If the [settings] are not provided, an empty [RouteSettings] object is
  /// used instead.
  Route({ RouteSettings settings }) : this.settings = settings ?? const RouteSettings();

83 84 85 86
  /// The navigator that the route is in, if any.
  NavigatorState get navigator => _navigator;
  NavigatorState _navigator;

87 88 89 90 91
  /// The settings for this route.
  ///
  /// See [RouteSettings] for details.
  final RouteSettings settings;

92
  /// The overlay entries for this route.
93 94 95
  List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];

  /// Called when the route is inserted into the navigator.
96
  ///
97 98 99 100 101
  /// Use this to populate [overlayEntries] and add them to the overlay
  /// (accessible as [Navigator.overlay]). (The reason the [Route] is
  /// responsible for doing this, rather than the [Navigator], is that the
  /// [Route] will be responsible for _removing_ the entries and this way it's
  /// symmetric.)
102
  ///
103 104 105
  /// The `insertionPoint` argument will be null if this is the first route
  /// inserted. Otherwise, it indicates the overlay entry to place immediately
  /// below the first overlay for this route.
106 107
  @protected
  @mustCallSuper
Hixie's avatar
Hixie committed
108
  void install(OverlayEntry insertionPoint) { }
109

110
  /// Called after [install] when the route is pushed onto the navigator.
111 112
  ///
  /// The returned value resolves when the push transition is complete.
113 114 115
  ///
  /// The [didChangeNext] method is typically called immediately after this
  /// method is called.
116
  @protected
117
  TickerFuture didPush() => new TickerFuture.complete();
Hixie's avatar
Hixie committed
118

119
  /// Called after [install] when the route replaced another in the navigator.
120 121 122
  ///
  /// The [didChangeNext] method is typically called immediately after this
  /// method is called.
123 124
  @protected
  @mustCallSuper
125
  void didReplace(Route<dynamic> oldRoute) { }
126

127
  /// Returns false if this route wants to veto a [Navigator.pop]. This method is
Ian Hickson's avatar
Ian Hickson committed
128
  /// called by [Navigator.maybePop].
129
  ///
130 131 132 133
  /// By default, routes veto a pop if they're the first route in the history
  /// (i.e., if [isFirst]). This behavior prevents the user from popping the
  /// first route off the history and being stranded at a blank screen.
  ///
134 135
  /// See also:
  ///
136 137 138 139
  ///  * [Form], which provides a [Form.onWillPop] callback that uses this
  ///    mechanism.
  ///  * [WillPopScope], another widget that provides a way to intercept the
  ///    back button.
140 141 142
  Future<RoutePopDisposition> willPop() async {
    return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop;
  }
143

144 145 146 147 148 149 150 151 152 153 154 155 156
  /// Whether calling [didPop] would return false.
  bool get willHandlePopInternally => false;

  /// When this route is popped (see [Navigator.pop]) if the result isn't
  /// specified or if it's null, this value will be used instead.
  T get currentResult => null;

  /// A future that completes when this route is popped off the navigator.
  ///
  /// The future completes with the value given to [Navigator.pop], if any.
  Future<T> get popped => _popCompleter.future;
  final Completer<T> _popCompleter = new Completer<T>();

Hixie's avatar
Hixie committed
157 158
  /// A request was made to pop this route. If the route can handle it
  /// internally (e.g. because it has its own stack of internal state) then
159 160 161
  /// return false, otherwise return true (by return the value of calling
  /// `super.didPop`). Returning false will prevent the default behavior of
  /// [NavigatorState.pop].
162
  ///
163 164 165 166 167 168
  /// When this function returns true, the navigator removes this route from
  /// the history but does not yet call [dispose]. Instead, it is the route's
  /// responsibility to call [NavigatorState.finalizeRoute], which will in turn
  /// call [dispose] on the route. This sequence lets the route perform an
  /// exit animation (or some other visual effect) after being popped but prior
  /// to being disposed.
169 170 171
  @protected
  @mustCallSuper
  bool didPop(T result) {
172
    didComplete(result);
173 174
    return true;
  }
175

176 177 178 179 180 181 182 183 184 185
  /// The route was popped or is otherwise being removed somewhat gracefully.
  ///
  /// This is called by [didPop] and in response to [Navigator.pushReplacement].
  ///
  /// The [popped] future is completed by this method.
  @protected
  @mustCallSuper
  void didComplete(T result) {
    _popCompleter.complete(result);
  }
Hixie's avatar
Hixie committed
186

187
  /// The given route, which was above this one, has been popped off the
188
  /// navigator.
189 190
  @protected
  @mustCallSuper
191
  void didPopNext(Route<dynamic> nextRoute) { }
192

Hixie's avatar
Hixie committed
193
  /// This route's next route has changed to the given new route. This is called
194 195 196 197
  /// on a route whenever the next route changes for any reason, so long as it
  /// is in the history, including when a route is first added to a [Navigator]
  /// (e.g. by [Navigator.push]), except for cases when [didPopNext] would be
  /// called. `nextRoute` will be null if there's no next route.
198 199
  @protected
  @mustCallSuper
200
  void didChangeNext(Route<dynamic> nextRoute) { }
201

202 203 204
  /// This route's previous route has changed to the given new route. This is
  /// called on a route whenever the previous route changes for any reason, so
  /// long as it is in the history, except for immediately after the route has
205
  /// been pushed (in which case [didPush] or [didReplace] will be called
206 207 208 209 210
  /// instead). `previousRoute` will be null if there's no previous route.
  @protected
  @mustCallSuper
  void didChangePrevious(Route<dynamic> previousRoute) { }

211
  /// Called whenever the internal state of the route has changed.
212
  ///
213 214 215 216 217 218 219 220
  /// This should be called whenever [willHandlePopInternally], [didPop],
  /// [offstage], or other internal state of the route changes value. It is used
  /// by [ModalRoute], for example, to report the new information via its
  /// inherited widget to any children of the route.
  ///
  /// See also:
  ///
  ///  * [changedExternalState], which is called when the [Navigator] rebuilds.
221 222
  @protected
  @mustCallSuper
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
  void changedInternalState() { }

  /// Called whenever the [Navigator] has its widget rebuilt, to indicate that
  /// the route may wish to rebuild as well.
  ///
  /// This is called by the [Navigator] whenever the [NavigatorState]'s
  /// [widget] changes, for example because the [MaterialApp] has been rebuilt.
  /// This ensures that routes that directly refer to the state of the widget
  /// that built the [MaterialApp] will be notified when that widget rebuilds,
  /// since it would otherwise be difficult to notify the routes that state they
  /// depend on may have changed.
  ///
  /// See also:
  ///
  ///  * [changedInternalState], the equivalent but for changes to the internal
  ///    state of the route.
  @protected
  @mustCallSuper
  void changedExternalState() { }
242

243 244
  /// The route should remove its overlays and free any other resources.
  ///
245
  /// This route is no longer referenced by the navigator.
246
  @mustCallSuper
247 248 249 250
  @protected
  void dispose() {
    _navigator = null;
  }
251 252

  /// Whether this route is the top-most route on the navigator.
253 254
  ///
  /// If this is true, then [isActive] is also true.
255
  bool get isCurrent {
256
    return _navigator != null && _navigator._history.last == this;
257
  }
258

259 260 261 262 263 264 265 266 267 268 269
  /// Whether this route is the bottom-most route on the navigator.
  ///
  /// If this is true, then [Navigator.canPop] will return false if this route's
  /// [willHandlePopInternally] returns false.
  ///
  /// If [isFirst] and [isCurrent] are both true then this is the only route on
  /// the navigator (and [isActive] will also be true).
  bool get isFirst {
    return _navigator != null && _navigator._history.first == this;
  }

270 271 272
  /// Whether this route is on the navigator.
  ///
  /// If the route is not only active, but also the current route (the top-most
273 274
  /// route), then [isCurrent] will also be true. If it is the first route (the
  /// bottom-most route), then [isFirst] will also be true.
275
  ///
276
  /// If a higher route is entirely opaque, then the route will be active but not
277
  /// rendered. It is even possible for the route to be active but for the stateful
278
  /// widgets within the route to not be instantiated. See [ModalRoute.maintainState].
279
  bool get isActive {
280
    return _navigator != null && _navigator._history.contains(this);
281
  }
Adam Barth's avatar
Adam Barth committed
282 283
}

284
/// Data that might be useful in constructing a [Route].
285
@immutable
286
class RouteSettings {
287
  /// Creates data used to construct routes.
288
  const RouteSettings({
289
    this.name,
290
    this.isInitialRoute = false,
291 292
  });

293 294 295 296 297 298 299 300 301 302 303 304
  /// Creates a copy of this route settings object with the given fields
  /// replaced with the new values.
  RouteSettings copyWith({
    String name,
    bool isInitialRoute,
  }) {
    return new RouteSettings(
      name: name ?? this.name,
      isInitialRoute: isInitialRoute ?? this.isInitialRoute,
    );
  }

305
  /// The name of the route (e.g., "/settings").
306 307
  ///
  /// If null, the route is anonymous.
Adam Barth's avatar
Adam Barth committed
308
  final String name;
309 310 311 312

  /// Whether this route is the very first route being pushed onto this [Navigator].
  ///
  /// The initial route typically skips any entrance transition to speed startup.
313
  final bool isInitialRoute;
314

315
  @override
316
  String toString() => '"$name"';
Adam Barth's avatar
Adam Barth committed
317 318
}

319
/// An interface for observing the behavior of a [Navigator].
320
class NavigatorObserver {
321
  /// The navigator that the observer is observing, if any.
322
  NavigatorState get navigator => _navigator;
323
  NavigatorState _navigator;
324

325
  /// The [Navigator] pushed `route`.
326 327 328
  ///
  /// The route immediately below that one, and thus the previously active
  /// route, is `previousRoute`.
329
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) { }
330

331
  /// The [Navigator] popped `route`.
332 333 334
  ///
  /// The route immediately below that one, and thus the newly active
  /// route, is `previousRoute`.
335
  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) { }
336

337
  /// The [Navigator] removed `route`.
338 339 340 341 342 343 344 345
  ///
  /// If only one route is being removed, then the route immediately below
  /// that one, if any, is `previousRoute`.
  ///
  /// If multiple routes are being removed, then the route below the
  /// bottommost route being removed, if any, is `previousRoute`, and this
  /// method will be called once for each removed route, from the topmost route
  /// to the bottommost route.
346 347
  void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) { }

348 349 350
  /// The [Navigator] replaced `oldRoute` with `newRoute`.
  void didReplace({ Route<dynamic> newRoute, Route<dynamic> oldRoute }) { }

351
  /// The [Navigator]'s routes are being moved by a user gesture.
352
  ///
353 354
  /// For example, this is called when an iOS back gesture starts, and is used
  /// to disabled hero animations during such interactions.
355 356 357
  void didStartUserGesture() { }

  /// User gesture is no longer controlling the [Navigator].
358
  ///
359 360
  /// Paired with an earlier call to [didStartUserGesture].
  void didStopUserGesture() { }
361 362
}

363
/// A widget that manages a set of child widgets with a stack discipline.
364 365 366 367
///
/// Many apps have a navigator near the top of their widget hierarchy in order
/// to display their logical history using an [Overlay] with the most recently
/// visited pages visually on top of the older pages. Using this pattern lets
368
/// the navigator visually transition from one page to another by moving the widgets
369 370
/// around in the overlay. Similarly, the navigator can be used to show a dialog
/// by positioning the dialog widget above the current page.
371
///
372 373 374 375 376 377 378 379 380 381 382 383 384 385
/// ## Using the Navigator
///
/// Mobile apps typically reveal their contents via full-screen elements
/// called "screens" or "pages". In Flutter these elements are called
/// routes and they're managed by a [Navigator] widget. The navigator
/// manages a stack of [Route] objects and provides methods for managing
/// the stack, like [Navigator.push] and [Navigator.pop].
///
/// ### Displaying a full-screen route
///
/// Although you can create a navigator directly, it's most common to use
/// the navigator created by a [WidgetsApp] or a [MaterialApp] widget. You
/// can refer to that navigator with [Navigator.of].
///
386 387 388
/// A [MaterialApp] is the simplest way to set things up. The [MaterialApp]'s
/// home becomes the route at the bottom of the [Navigator]'s stack. It is what
/// you see when the app is launched.
389 390 391 392 393 394 395 396 397 398 399 400
///
/// ```dart
/// void main() {
///   runApp(new MaterialApp(home: new MyAppHome()));
/// }
/// ```
///
/// To push a new route on the stack you can create an instance of
/// [MaterialPageRoute] with a builder function that creates whatever you
/// want to appear on the screen. For example:
///
/// ```dart
401
/// Navigator.push(context, new MaterialPageRoute<void>(
402 403 404 405 406 407 408
///   builder: (BuildContext context) {
///     return new Scaffold(
///       appBar: new AppBar(title: new Text('My Page')),
///       body: new Center(
///         child: new FlatButton(
///           child: new Text('POP'),
///           onPressed: () {
409
///             Navigator.pop(context);
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
///           },
///         ),
///       ),
///     );
///   },
/// ));
/// ```
///
/// The route defines its widget with a builder function instead of a
/// child widget because it will be built and rebuilt in different
/// contexts depending on when it's pushed and popped.
///
/// As you can see, the new route can be popped, revealing the app's home
/// page, with the Navigator's pop method:
///
/// ```dart
426
/// Navigator.pop(context);
427 428 429
/// ```
///
/// It usually isn't necessary to provide a widget that pops the Navigator
430
/// in a route with a [Scaffold] because the Scaffold automatically adds a
431 432 433 434 435 436 437
/// 'back' button to its AppBar. Pressing the back button causes
/// [Navigator.pop] to be called. On Android, pressing the system back
/// button does the same thing.
///
/// ### Using named navigator routes
///
/// Mobile apps often manage a large number of routes and it's often
438 439 440 441 442
/// easiest to refer to them by name. Route names, by convention,
/// use a path-like structure (for example, '/a/b/c').
/// The app's home page route is named '/' by default.
///
/// The [MaterialApp] can be created
443
/// with a [Map<String, WidgetBuilder>] which maps from a route's name to
444 445 446 447 448 449
/// a builder function that will create it. The [MaterialApp] uses this
/// map to create a value for its navigator's [onGenerateRoute] callback.
///
/// ```dart
/// void main() {
///   runApp(new MaterialApp(
450
///     home: new MyAppHome(), // becomes the route named '/'
451 452 453 454 455 456 457 458 459 460
///     routes: <String, WidgetBuilder> {
///       '/a': (BuildContext context) => new MyPage(title: 'page A'),
///       '/b': (BuildContext context) => new MyPage(title: 'page B'),
///       '/c': (BuildContext context) => new MyPage(title: 'page C'),
///     },
///   ));
/// }
/// ```
///
/// To show a route by name:
461
///
462
/// ```dart
463
/// Navigator.pushNamed(context, '/b');
464 465
/// ```
///
466 467 468 469 470
/// ### Routes can return a value
///
/// When a route is pushed to ask the user for a value, the value can be
/// returned via the [pop] method's result parameter.
///
471 472
/// Methods that push a route return a [Future]. The Future resolves when the
/// route is popped and the [Future]'s value is the [pop] method's `result`
473 474 475 476 477
/// parameter.
///
/// For example if we wanted to ask the user to press 'OK' to confirm an
/// operation we could `await` the result of [Navigator.push]:
///
478 479
/// ```dart
/// bool value = await Navigator.push(context, new MaterialPageRoute<bool>(
480 481
///   builder: (BuildContext context) {
///     return new Center(
482
///       child: new GestureDetector(
483
///         child: new Text('OK'),
484
///         onTap: () { Navigator.pop(context, true); }
485 486 487 488 489
///       ),
///     );
///   }
/// ));
/// ```
490
///
491 492 493 494
/// If the user presses 'OK' then value will be true. If the user backs
/// out of the route, for example by pressing the Scaffold's back button,
/// the value will be null.
///
495 496 497 498 499
/// When a route is used to return a value, the route's type parameter must
/// match the type of [pop]'s result. That's why we've used
/// `MaterialPageRoute<bool>` instead of `MaterialPageRoute<void>` or just
/// `MaterialPageRoute`. (If you prefer to not specify the types, though, that's
/// fine too.)
500 501
///
/// ### Popup routes
502
///
503 504 505 506
/// Routes don't have to obscure the entire screen. [PopupRoute]s cover the
/// screen with a [ModalRoute.barrierColor] that can be only partially opaque to
/// allow the current screen to show through. Popup routes are "modal" because
/// they block input to the widgets below.
507 508
///
/// There are functions which create and show popup routes. For
509
/// example: [showDialog], [showMenu], and [showModalBottomSheet]. These
510 511 512
/// functions return their pushed route's Future as described above.
/// Callers can await the returned value to take an action when the
/// route is popped, or to discover the route's value.
513
///
514 515
/// There are also widgets which create popup routes, like [PopupMenuButton] and
/// [DropdownButton]. These widgets create internal subclasses of PopupRoute
Josh Soref's avatar
Josh Soref committed
516
/// and use the Navigator's push and pop methods to show and dismiss them.
517
///
518
/// ### Custom routes
519
///
520
/// You can create your own subclass of one of the widget library route classes
521 522 523
/// like [PopupRoute], [ModalRoute], or [PageRoute], to control the animated
/// transition employed to show the route, the color and behavior of the route's
/// modal barrier, and other aspects of the route.
524
///
525
/// The [PageRouteBuilder] class makes it possible to define a custom route
526 527 528
/// in terms of callbacks. Here's an example that rotates and fades its child
/// when the route appears or disappears. This route does not obscure the entire
/// screen because it specifies `opaque: false`, just as a popup route does.
529
///
530
/// ```dart
531
/// Navigator.push(context, new PageRouteBuilder(
532 533 534 535
///   opaque: false,
///   pageBuilder: (BuildContext context, _, __) {
///     return new Center(child: new Text('My PageRoute'));
///   },
536
///   transitionsBuilder: (___, Animation<double> animation, ____, Widget child) {
537 538 539 540 541 542 543 544 545 546
///     return new FadeTransition(
///       opacity: animation,
///       child: new RotationTransition(
///         turns: new Tween<double>(begin: 0.5, end: 1.0).animate(animation),
///         child: child,
///       ),
///     );
///   }
/// ));
/// ```
547
///
548 549 550 551 552 553
/// The page route is built in two parts, the "page" and the
/// "transitions". The page becomes a descendant of the child passed to
/// the `buildTransitions` method. Typically the page is only built once,
/// because it doesn't depend on its animation parameters (elided with `_`
/// and `__` in this example). The transition is built on every frame
/// for its duration.
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
///
/// ### Nesting Navigators
///
/// An app can use more than one Navigator. Nesting one Navigator below
/// another Navigator can be used to create an "inner journey" such as tabbed
/// navigation, user registration, store checkout, or other independent journeys
/// that represent a subsection of your overall application.
///
/// #### Real World Example
///
/// It is standard practice for iOS apps to use tabbed navigation where each
/// tab maintains its own navigation history. Therefore, each tab has its own
/// [Navigator], creating a kind of "parallel navigation."
///
/// In addition to the parallel navigation of the tabs, it is still possible to
/// launch full-screen pages that completely cover the tabs. For example: an
/// on-boarding flow, or an alert dialog. Therefore, there must exist a "root"
/// [Navigator] that sits above the tab navigation. As a result, each of the
/// tab's [Navigator]s are actually nested [Navigator]s sitting below a single
/// root [Navigator].
///
/// The nested [Navigator]s for tabbed navigation sit in [WidgetApp] and
/// [CupertinoTabView], so you don't need to worry about nested [Navigator]s
/// in this situation, but it's a real world example where nested [Navigator]s
/// are used.
///
/// #### Sample Code
///
/// The following example demonstrates how a nested [Navigator] can be used to
/// present a standalone user registration journey.
///
/// Even though this example uses two [Navigator]s to demonstrate nested
/// [Navigator]s, a similar result is possible using only a single [Navigator].
///
/// ```dart
/// class MyApp extends StatelessWidget {
///  @override
///  Widget build(BuildContext context) {
///    return new MaterialApp(
///      // ...some parameters omitted...
///      // MaterialApp contains our top-level Navigator
///      initialRoute: '/',
///      routes: {
///        '/': (BuildContext context) => new HomePage(),
///        '/signup': (BuildContext context) => new SignUpPage(),
///      },
///    );
///  }
/// }
///
/// class SignUpPage extends StatelessWidget {
///  @override
///  Widget build(BuildContext context) {
///    // SignUpPage builds its own Navigator which ends up being a nested
///    // Navigator in our app.
///    return new Navigator(
///      initialRoute: 'signup/personal_info',
///      onGenerateRoute: (RouteSettings settings) {
///        WidgetBuilder builder;
///        switch (settings.name) {
///          case 'signup/personal_info':
///            // Assume CollectPersonalInfoPage collects personal info and then
///            // navigates to 'signup/choose_credentials'.
///            builder = (BuildContext _) => new CollectPersonalInfoPage();
///            break;
///          case 'signup/choose_credentials':
///            // Assume ChooseCredentialsPage collects new credentials and then
///            // invokes 'onSignupComplete()'.
///            builder = (BuildContext _) => new ChooseCredentialsPage(
///              onSignupComplete: () {
///                // Referencing Navigator.of(context) from here refers to the
///                // top level Navigator because SignUpPage is above the
///                // nested Navigator that it created. Therefore, this pop()
///                // will pop the entire "sign up" journey and return to the
///                // "/" route, AKA HomePage.
///                Navigator.of(context).pop();
///              },
///            );
///            break;
///          default:
///            throw new Exception('Invalid route: ${settings.name}');
///        }
///        return new MaterialPageRoute(builder: builder, settings: settings);
///      },
///    );
///  }
/// }
/// ```
///
/// [Navigator.of] operates on the nearest ancestor [Navigator] from the given
/// [BuildContext]. Be sure to provide a [BuildContext] below the intended
/// [Navigator], especially in large [build] methods where nested [Navigator]s
/// are created. The [Builder] widget can be used to access a [BuildContext] at
/// a desired location in the widget subtree.
648
class Navigator extends StatefulWidget {
649 650 651
  /// Creates a widget that maintains a stack-based history of child widgets.
  ///
  /// The [onGenerateRoute] argument must not be null.
652
  const Navigator({
653
    Key key,
654
    this.initialRoute,
655
    @required this.onGenerateRoute,
656
    this.onUnknownRoute,
657
    this.observers = const <NavigatorObserver>[]
658 659
  }) : assert(onGenerateRoute != null),
       super(key: key);
660

661
  /// The name of the first route to show.
662 663 664 665 666 667 668 669 670 671 672
  ///
  /// By default, this defers to [dart:ui.Window.defaultRouteName].
  ///
  /// If this string contains any `/` characters, then the string is split on
  /// those characters and substrings from the start of the string up to each
  /// such character are, in turn, used as routes to push.
  ///
  /// For example, if the route `/stocks/HOOLI` was used as the [initialRoute],
  /// then the [Navigator] would push the following routes on startup: `/`,
  /// `/stocks`, `/stocks/HOOLI`. This enables deep linking while allowing the
  /// application to maintain a predictable route history.
673
  final String initialRoute;
674 675

  /// Called to generate a route for a given [RouteSettings].
Adam Barth's avatar
Adam Barth committed
676
  final RouteFactory onGenerateRoute;
677 678 679 680 681 682 683 684 685

  /// Called when [onGenerateRoute] fails to generate a route.
  ///
  /// This callback is typically used for error handling. For example, this
  /// callback might always generate a "not found" page that describes the route
  /// that wasn't found.
  ///
  /// Unknown routes can arise either from errors in the app or from external
  /// requests to push routes, such as from Android intents.
Adam Barth's avatar
Adam Barth committed
686
  final RouteFactory onUnknownRoute;
687

688 689
  /// A list of observers for this navigator.
  final List<NavigatorObserver> observers;
Adam Barth's avatar
Adam Barth committed
690

691 692 693 694 695 696
  /// The default name for the [initialRoute].
  ///
  /// See also:
  ///
  ///  * [dart:ui.Window.defaultRouteName], which reflects the route that the
  ///    application was started with.
Adam Barth's avatar
Adam Barth committed
697
  static const String defaultRouteName = '/';
698

699 700
  /// Push a named route onto the navigator that most tightly encloses the given
  /// context.
701
  ///
702
  /// {@template flutter.widgets.navigator.pushNamed}
703
  /// The route name will be passed to that navigator's [onGenerateRoute]
704
  /// callback. The returned route will be pushed into the navigator.
705
  ///
706 707 708 709 710 711 712 713
  /// The new route and the previous route (if any) are notified (see
  /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
  /// [Navigator.observers], they will be notified as well (see
  /// [NavigatorObserver.didPush]).
  ///
  /// Ongoing gestures within the current route are canceled when a new route is
  /// pushed.
  ///
714 715
  /// Returns a [Future] that completes to the `result` value passed to [pop]
  /// when the pushed route is popped off the navigator.
716
  ///
717 718 719 720 721
  /// The `T` type argument is the type of the return value of the route.
  /// {@endtemplate}
  ///
  /// ## Sample code
  ///
722 723 724
  /// Typical usage is as follows:
  ///
  /// ```dart
725 726 727
  /// void _didPushButton() {
  ///   Navigator.pushNamed(context, '/nyc/1776');
  /// }
728
  /// ```
729 730 731
  @optionalTypeArgs
  static Future<T> pushNamed<T extends Object>(BuildContext context, String routeName) {
    return Navigator.of(context).pushNamed<T>(routeName);
Hixie's avatar
Hixie committed
732 733
  }

734 735 736
  /// Replace the current route of the navigator that most tightly encloses the
  /// given context by pushing the route named [routeName] and then disposing
  /// the previous route once the new route has finished animating in.
737
  ///
738 739 740 741 742 743 744 745 746 747 748 749
  /// {@template flutter.widgets.navigator.pushReplacementNamed}
  /// If non-null, `result` will be used as the result of the route that is
  /// removed; the future that had been returned from pushing that old route
  /// will complete with `result`. Routes such as dialogs or popup menus
  /// typically use this mechanism to return the value selected by the user to
  /// the widget that created their route. The type of `result`, if provided,
  /// must match the type argument of the class of the old route (`TO`).
  ///
  /// The route name will be passed to the navigator's [onGenerateRoute]
  /// callback. The returned route will be pushed into the navigator.
  ///
  /// The new route and the route below the removed route are notified (see
750 751
  /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
  /// [Navigator.observers], they will be notified as well (see
752 753 754 755
  /// [NavigatorObserver.didReplace]). The removed route is notified once the
  /// new route has finished animating (see [Route.didComplete]). The removed
  /// route's exit animation is not run (see [popAndPushNamed] for a variant
  /// that does animated the removed route).
756 757 758
  ///
  /// Ongoing gestures within the current route are canceled when a new route is
  /// pushed.
759
  ///
760 761
  /// Returns a [Future] that completes to the `result` value passed to [pop]
  /// when the pushed route is popped off the navigator.
762 763 764 765 766 767 768 769 770 771 772 773 774 775
  ///
  /// The `T` type argument is the type of the return value of the new route,
  /// and `TO` is the type of the return value of the old route.
  /// {@endtemplate}
  ///
  /// ## Sample code
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// void _showNext() {
  ///   Navigator.pushReplacementNamed(context, '/jouett/1781');
  /// }
  /// ```
776
  @optionalTypeArgs
777 778
  static Future<T> pushReplacementNamed<T extends Object, TO extends Object>(BuildContext context, String routeName, { TO result }) {
    return Navigator.of(context).pushReplacementNamed<T, TO>(routeName, result: result);
Hixie's avatar
Hixie committed
779 780
  }

781 782
  /// Pop the current route off the navigator that most tightly encloses the
  /// given context and push a named route in its place.
783
  ///
784 785 786 787 788 789 790
  /// {@template flutter.widgets.navigator.popAndPushNamed}
  /// If non-null, `result` will be used as the result of the route that is
  /// popped; the future that had been returned from pushing the popped route
  /// will complete with `result`. Routes such as dialogs or popup menus
  /// typically use this mechanism to return the value selected by the user to
  /// the widget that created their route. The type of `result`, if provided,
  /// must match the type argument of the class of the popped route (`TO`).
791
  ///
792 793
  /// The route name will be passed to the navigator's [onGenerateRoute]
  /// callback. The returned route will be pushed into the navigator.
794
  ///
795 796 797 798 799 800 801 802
  /// The new route, the old route, and the route below the old route (if any)
  /// are all notified (see [Route.didPop], [Route.didComplete],
  /// [Route.didPopNext], [Route.didPush], and [Route.didChangeNext]). If the
  /// [Navigator] has any [Navigator.observers], they will be notified as well
  /// (see [NavigatorObserver.didPop] and [NavigatorObservers.didPush]). The
  /// animations for the pop and the push are performed simultaneously, so the
  /// route below may be briefly visible even if both the old route and the new
  /// route are opaque (see [TransitionRoute.opaque]).
803
  ///
804 805 806 807 808 809 810 811 812
  /// Ongoing gestures within the current route are canceled when a new route is
  /// pushed.
  ///
  /// Returns a [Future] that completes to the `result` value passed to [pop]
  /// when the pushed route is popped off the navigator.
  ///
  /// The `T` type argument is the type of the return value of the new route,
  /// and `TO` is the return value type of the old route.
  /// {@endtemplate}
813
  ///
814
  /// ## Sample code
815 816 817 818
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
819 820 821
  /// void _selectNewYork() {
  ///   Navigator.popAndPushNamed(context, '/nyc/1776');
  /// }
822
  /// ```
823 824 825
  @optionalTypeArgs
  static Future<T> popAndPushNamed<T extends Object, TO extends Object>(BuildContext context, String routeName, { TO result }) {
    return Navigator.of(context).popAndPushNamed<T, TO>(routeName, result: result);
Hixie's avatar
Hixie committed
826
  }
827

828 829 830
  /// Push the route with the given name onto the navigator that most tightly
  /// encloses the given context, and then remove all the previous routes until
  /// the `predicate` returns true.
831
  ///
832
  /// {@template flutter.widgets.navigator.pushNamedAndRemoveUntil}
Hans Muller's avatar
Hans Muller committed
833 834
  /// The predicate may be applied to the same route more than once if
  /// [Route.willHandlePopInternally] is true.
835
  ///
836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867
  /// To remove routes until a route with a certain name, use the
  /// [RoutePredicate] returned from [ModalRoute.withName].
  ///
  /// To remove all the routes below the pushed route, use a [RoutePredicate]
  /// that always returns false (e.g. `(Route<dynamic> route) => false`).
  ///
  /// The removed routes are removed without being completed, so this method
  /// does not take a return value argument.
  ///
  /// The new route's name (`routeName`) will be passed to the navigator's
  /// [onGenerateRoute] callback. The returned route will be pushed into the
  /// navigator.
  ///
  /// The new route and the route below the bottommost removed route (which
  /// becomes the route below the new route) are notified (see [Route.didPush]
  /// and [Route.didChangeNext]). If the [Navigator] has any
  /// [Navigator.observers], they will be notified as well (see
  /// [NavigatorObservers.didPush] and [NavigatorObservers.didRemove]). The
  /// removed routes are disposed, without being notified, once the new route
  /// has finished animating. The futures that had been returned from pushing
  /// those routes will not complete.
  ///
  /// Ongoing gestures within the current route are canceled when a new route is
  /// pushed.
  ///
  /// Returns a [Future] that completes to the `result` value passed to [pop]
  /// when the pushed route is popped off the navigator.
  ///
  /// The `T` type argument is the type of the return value of the new route.
  /// {@endtemplate}
  ///
  /// ## Sample code
868 869 870 871
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
872 873 874
  /// void _resetToCalendar() {
  ///   Navigator.pushNamedAndRemoveUntil(context, '/calendar', ModalRoute.withName('/'));
  /// }
875
  /// ```
876 877 878
  @optionalTypeArgs
  static Future<T> pushNamedAndRemoveUntil<T extends Object>(BuildContext context, String newRouteName, RoutePredicate predicate) {
    return Navigator.of(context).pushNamedAndRemoveUntil<T>(newRouteName, predicate);
Hixie's avatar
Hixie committed
879 880
  }

881
  /// Push the given route onto the navigator that most tightly encloses the
882 883
  /// given context.
  ///
884 885 886 887 888 889 890 891
  /// {@template flutter.widgets.navigator.push}
  /// The new route and the previous route (if any) are notified (see
  /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
  /// [Navigator.observers], they will be notified as well (see
  /// [NavigatorObserver.didPush]).
  ///
  /// Ongoing gestures within the current route are canceled when a new route is
  /// pushed.
892
  ///
893 894
  /// Returns a [Future] that completes to the `result` value passed to [pop]
  /// when the pushed route is popped off the navigator.
895
  ///
896 897 898 899 900
  /// The `T` type argument is the type of the return value of the route.
  /// {@endtemplate}
  ///
  /// ## Sample code
  ///
901 902 903
  /// Typical usage is as follows:
  ///
  /// ```dart
904
  /// void _openMyPage() {
905
  ///   Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new MyPage()));
906
  /// }
907
  /// ```
908 909 910
  @optionalTypeArgs
  static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
    return Navigator.of(context).push(route);
Hixie's avatar
Hixie committed
911 912
  }

913 914 915
  /// Replace the current route of the navigator that most tightly encloses the
  /// given context by pushing the given route and then disposing the previous
  /// route once the new route has finished animating in.
916
  ///
917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
  /// {@template flutter.widgets.navigator.pushReplacement}
  /// If non-null, `result` will be used as the result of the route that is
  /// removed; the future that had been returned from pushing that old route will
  /// complete with `result`. Routes such as dialogs or popup menus typically
  /// use this mechanism to return the value selected by the user to the widget
  /// that created their route. The type of `result`, if provided, must match
  /// the type argument of the class of the old route (`TO`).
  ///
  /// The new route and the route below the removed route are notified (see
  /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
  /// [Navigator.observers], they will be notified as well (see
  /// [NavigatorObserver.didReplace]). The removed route is notified once the
  /// new route has finished animating (see [Route.didComplete]).
  ///
  /// Ongoing gestures within the current route are canceled when a new route is
  /// pushed.
933 934 935 936
  ///
  /// Returns a [Future] that completes to the `result` value passed to [pop]
  /// when the pushed route is popped off the navigator.
  ///
937 938 939 940 941 942
  /// The `T` type argument is the type of the return value of the new route,
  /// and `TO` is the type of the return value of the old route.
  /// {@endtemplate}
  ///
  /// ## Sample code
  ///
943 944 945
  /// Typical usage is as follows:
  ///
  /// ```dart
946
  /// void _completeLogin() {
947 948
  ///   Navigator.pushReplacement(
  ///       context, new MaterialPageRoute(builder: (BuildContext context) => new MyHomePage()));
949
  /// }
950
  /// ```
951 952 953
  @optionalTypeArgs
  static Future<T> pushReplacement<T extends Object, TO extends Object>(BuildContext context, Route<T> newRoute, { TO result }) {
    return Navigator.of(context).pushReplacement<T, TO>(newRoute, result: result);
954 955
  }

956 957 958
  /// Push the given route onto the navigator that most tightly encloses the
  /// given context, and then remove all the previous routes until the
  /// `predicate` returns true.
959
  ///
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980
  /// {@template flutter.widgets.navigator.pushAndRemoveUntil}
  /// The predicate may be applied to the same route more than once if
  /// [Route.willHandlePopInternally] is true.
  ///
  /// To remove routes until a route with a certain name, use the
  /// [RoutePredicate] returned from [ModalRoute.withName].
  ///
  /// To remove all the routes below the pushed route, use a [RoutePredicate]
  /// that always returns false (e.g. `(Route<dynamic> route) => false`).
  ///
  /// The removed routes are removed without being completed, so this method
  /// does not take a return value argument.
  ///
  /// The new route and the route below the bottommost removed route (which
  /// becomes the route below the new route) are notified (see [Route.didPush]
  /// and [Route.didChangeNext]). If the [Navigator] has any
  /// [Navigator.observers], they will be notified as well (see
  /// [NavigatorObservers.didPush] and [NavigatorObservers.didRemove]). The
  /// removed routes are disposed, without being notified, once the new route
  /// has finished animating. The futures that had been returned from pushing
  /// those routes will not complete.
981
  ///
982 983
  /// Ongoing gestures within the current route are canceled when a new route is
  /// pushed.
984 985 986
  ///
  /// Returns a [Future] that completes to the `result` value passed to [pop]
  /// when the pushed route is popped off the navigator.
987 988 989 990 991 992 993 994 995 996
  ///
  /// The `T` type argument is the type of the return value of the new route.
  /// {@endtemplate}
  ///
  /// ## Sample code
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// void _finishAccountCreation() {
997
  ///   Navigator.pushAndRemoveUntil(
998
  ///     context,
999
  ///     new MaterialPageRoute(builder: (BuildContext context) => new MyHomePage()),
1000 1001 1002 1003 1004 1005 1006
  ///     ModalRoute.withName('/'),
  ///   );
  /// }
  /// ```
  @optionalTypeArgs
  static Future<T> pushAndRemoveUntil<T extends Object>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) {
    return Navigator.of(context).pushAndRemoveUntil<T>(newRoute, predicate);
1007 1008
  }

1009 1010
  /// Replaces a route on the navigator that most tightly encloses the given
  /// context with a new route.
1011
  ///
1012 1013 1014 1015 1016 1017
  /// {@template flutter.widgets.navigator.replace}
  /// The old route must not be current visible, as this method skips the
  /// animations and therefore the removal would be jarring if it was visible.
  /// To replace the top-most route, consider [pushReplacement] instead, which
  /// _does_ animate the new route, and delays removing the old route until the
  /// new route has finished animating.
1018
  ///
1019 1020
  /// The removed route is removed without being completed, so this method does
  /// not take a return value argument.
1021
  ///
1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
  /// The new route, the route below the new route (if any), and the route above
  /// the new route, are all notified (see [Route.didReplace],
  /// [Route.didChangeNext], and [Route.didChangePrevious]). If the [Navigator]
  /// has any [Navigator.observers], they will be notified as well (see
  /// [NavigatorObservers.didReplace]). The removed route is disposed without
  /// being notified. The future that had been returned from pushing that routes
  /// will not complete.
  ///
  /// This can be useful in combination with [removeRouteBelow] when building a
  /// non-linear user experience.
  ///
  /// The `T` type argument is the type of the return value of the new route.
  /// {@endtemplate}
  ///
  /// See also:
  ///
  ///  * [replaceRouteBelow], which is the same but identifies the route to be
  ///    removed by reference to the route above it, rather than directly.
  @optionalTypeArgs
  static void replace<T extends Object>(BuildContext context, { @required Route<dynamic> oldRoute, @required Route<T> newRoute }) {
    return Navigator.of(context).replace<T>(oldRoute: oldRoute, newRoute: newRoute);
1043 1044
  }

1045 1046 1047
  /// Replaces a route on the navigator that most tightly encloses the given
  /// context with a new route. The route to be replaced is the one below the
  /// given `anchorRoute`.
1048
  ///
1049 1050 1051 1052 1053 1054
  /// {@template flutter.widgets.navigator.replaceRouteBelow}
  /// The old route must not be current visible, as this method skips the
  /// animations and therefore the removal would be jarring if it was visible.
  /// To replace the top-most route, consider [pushReplacement] instead, which
  /// _does_ animate the new route, and delays removing the old route until the
  /// new route has finished animating.
1055
  ///
1056 1057
  /// The removed route is removed without being completed, so this method does
  /// not take a return value argument.
1058
  ///
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
  /// The new route, the route below the new route (if any), and the route above
  /// the new route, are all notified (see [Route.didReplace],
  /// [Route.didChangeNext], and [Route.didChangePrevious]). If the [Navigator]
  /// has any [Navigator.observers], they will be notified as well (see
  /// [NavigatorObservers.didReplace]). The removed route is disposed without
  /// being notified. The future that had been returned from pushing that routes
  /// will not complete.
  ///
  /// The `T` type argument is the type of the return value of the new route.
  /// {@endtemplate}
  ///
  /// See also:
  ///
  ///  * [replace], which is the same but identifies the route to be removed
  ///    directly.
  @optionalTypeArgs
  static void replaceRouteBelow<T extends Object>(BuildContext context, { @required Route<dynamic> anchorRoute, Route<T> newRoute }) {
    return Navigator.of(context).replaceRouteBelow<T>(anchorRoute: anchorRoute, newRoute: newRoute);
Hixie's avatar
Hixie committed
1077
  }
Adam Barth's avatar
Adam Barth committed
1078

1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265
  /// Whether the navigator that most tightly encloses the given context can be
  /// popped.
  ///
  /// {@template flutter.widgets.navigator.canPop}
  /// The initial route cannot be popped off the navigator, which implies that
  /// this function returns true only if popping the navigator would not remove
  /// the initial route.
  ///
  /// If there is no [Navigator] in scope, returns false.
  /// {@endtemplate}
  ///
  /// See also:
  ///
  ///  * [Route.isFirst], which returns true for routes for which [canPop]
  ///    returns false.
  static bool canPop(BuildContext context) {
    final NavigatorState navigator = Navigator.of(context, nullOk: true);
    return navigator != null && navigator.canPop();
  }

  /// Returns the value of the current route's [Route.willPop] method for the
  /// navigator that most tightly encloses the given context.
  ///
  /// {@template flutter.widgets.navigator.maybePop}
  /// This method is typically called before a user-initiated [pop]. For example
  /// on Android it's called by the binding for the system's back button.
  ///
  /// The `T` type argument is the type of the return value of the current
  /// route.
  /// {@endtemplate}
  ///
  /// See also:
  ///
  /// * [Form], which provides an `onWillPop` callback that enables the form
  ///   to veto a [pop] initiated by the app's back button.
  /// * [ModalRoute], which provides a `scopedWillPopCallback` that can be used
  ///   to define the route's `willPop` method.
  @optionalTypeArgs
  static Future<bool> maybePop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).maybePop<T>(result);
  }

  /// Pop the top-most route off the navigator that most tightly encloses the
  /// given context.
  ///
  /// {@template flutter.widgets.navigator.pop}
  /// The current route's [Route.didPop] method is called first. If that method
  /// returns false, then this method returns true but nothing else is changed
  /// (the route is expected to have popped some internal state; see e.g.
  /// [LocalHistoryRoute]). Otherwise, the rest of this description applies.
  ///
  /// If non-null, `result` will be used as the result of the route that is
  /// popped; the future that had been returned from pushing the popped route
  /// will complete with `result`. Routes such as dialogs or popup menus
  /// typically use this mechanism to return the value selected by the user to
  /// the widget that created their route. The type of `result`, if provided,
  /// must match the type argument of the class of the popped route (`T`).
  ///
  /// The popped route and the route below it are notified (see [Route.didPop],
  /// [Route.didComplete], and [Route.didPopNext]). If the [Navigator] has any
  /// [Navigator.observers], they will be notified as well (see
  /// [NavigatorObserver.didPop]).
  ///
  /// The `T` type argument is the type of the return value of the popped route.
  ///
  /// Returns true if a route was popped (including if [Route.didPop] returned
  /// false); returns false if there are no further previous routes.
  /// {@endtemplate}
  ///
  /// ## Sample code
  ///
  /// Typical usage for closing a route is as follows:
  ///
  /// ```dart
  /// void _close() {
  ///   Navigator.pop(context);
  /// }
  /// ```
  ///
  /// A dialog box might be closed with a result:
  ///
  /// ```dart
  /// void _accept() {
  ///   Navigator.pop(context, true); // dialog returns true
  /// }
  /// ```
  @optionalTypeArgs
  static bool pop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).pop<T>(result);
  }

  /// Calls [pop] repeatedly on the navigator that most tightly encloses the
  /// given context until the predicate returns true.
  ///
  /// {@template flutter.widgets.navigator.popUntil}
  /// The predicate may be applied to the same route more than once if
  /// [Route.willHandlePopInternally] is true.
  ///
  /// To pop until a route with a certain name, use the [RoutePredicate]
  /// returned from [ModalRoute.withName].
  ///
  /// The routes are closed with null as their `return` value.
  ///
  /// See [pop] for more details of the semantics of popping a route.
  /// {@endtemplate}
  ///
  /// ## Sample code
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// void _logout() {
  ///   Navigator.popUntil(context, ModalRoute.withName('/login'));
  /// }
  /// ```
  static void popUntil(BuildContext context, RoutePredicate predicate) {
    Navigator.of(context).popUntil(predicate);
  }

  /// Immediately remove `route` from the navigator that most tightly encloses
  /// the given context, and [Route.dispose] it.
  ///
  /// {@template flutter.widgets.navigator.removeRoute}
  /// The removed route is removed without being completed, so this method does
  /// not take a return value argument. No animations are run as a result of
  /// this method call.
  ///
  /// The routes below and above the removed route are notified (see
  /// [Route.didChangeNext] and [Route.didChangePrevious]). If the [Navigator]
  /// has any [Navigator.observers], they will be notified as well (see
  /// [NavigatorObserver.didRemove]). The removed route is disposed without
  /// being notified. The future that had been returned from pushing that routes
  /// will not complete.
  ///
  /// The given `route` must be in the history; this method will throw an
  /// exception if it is not.
  ///
  /// Ongoing gestures within the current route are canceled.
  /// {@endtemplate}
  ///
  /// This method is used, for example, to instantly dismiss dropdown menus that
  /// are up when the screen's orientation changes.
  static void removeRoute(BuildContext context, Route<dynamic> route) {
    return Navigator.of(context).removeRoute(route);
  }

  /// Immediately remove a route from the navigator that most tightly encloses
  /// the given context, and [Route.dispose] it. The route to be replaced is the
  /// one below the given `anchorRoute`.
  ///
  /// {@template flutter.widgets.navigator.removeRouteBelow}
  /// The removed route is removed without being completed, so this method does
  /// not take a return value argument. No animations are run as a result of
  /// this method call.
  ///
  /// The routes below and above the removed route are notified (see
  /// [Route.didChangeNext] and [Route.didChangePrevious]). If the [Navigator]
  /// has any [Navigator.observers], they will be notified as well (see
  /// [NavigatorObserver.didRemove]). The removed route is disposed without
  /// being notified. The future that had been returned from pushing that routes
  /// will not complete.
  ///
  /// The given `anchorRoute` must be in the history and must have a route below
  /// it; this method will throw an exception if it is not or does not.
  ///
  /// Ongoing gestures within the current route are canceled.
  /// {@endtemplate}
  static void removeRouteBelow(BuildContext context, Route<dynamic> anchorRoute) {
    return Navigator.of(context).removeRouteBelow(anchorRoute);
  }

  /// The state from the closest instance of this class that encloses the given context.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// Navigator.of(context)
  ///   ..pop()
  ///   ..pop()
  ///   ..pushNamed('/settings');
  /// ```
  ///
  /// If `rootNavigator` is set to true, the state from the furthest instance of
  /// this class is given instead. Useful for pushing contents above all subsequent
  /// instances of [Navigator].
  static NavigatorState of(
    BuildContext context, {
1266 1267
      bool rootNavigator = false,
      bool nullOk = false,
1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289
    }) {
    final NavigatorState navigator = rootNavigator
        ? context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>())
        : context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
    assert(() {
      if (navigator == null && !nullOk) {
        throw new FlutterError(
          'Navigator operation requested with a context that does not include a Navigator.\n'
          'The context used to push or pop routes from the Navigator must be that of a '
          'widget that is a descendant of a Navigator widget.'
        );
      }
      return true;
    }());
    return navigator;
  }

  @override
  NavigatorState createState() => new NavigatorState();
}

/// The state for a [Navigator] widget.
1290
class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
Adam Barth's avatar
Adam Barth committed
1291
  final GlobalKey<OverlayState> _overlayKey = new GlobalKey<OverlayState>();
1292
  final List<Route<dynamic>> _history = <Route<dynamic>>[];
1293
  final Set<Route<dynamic>> _poppedRoutes = new Set<Route<dynamic>>();
1294

1295 1296 1297
  /// The [FocusScopeNode] for the [FocusScope] that encloses the routes.
  final FocusScopeNode focusScopeNode = new FocusScopeNode();

1298 1299
  final List<OverlayEntry> _initialOverlayEntries = <OverlayEntry>[];

1300
  @override
1301 1302
  void initState() {
    super.initState();
1303
    for (NavigatorObserver observer in widget.observers) {
1304 1305 1306
      assert(observer.navigator == null);
      observer._navigator = this;
    }
1307 1308 1309 1310 1311 1312 1313 1314
    String initialRouteName = widget.initialRoute ?? Navigator.defaultRouteName;
    if (initialRouteName.startsWith('/') && initialRouteName.length > 1) {
      initialRouteName = initialRouteName.substring(1); // strip leading '/'
      assert(Navigator.defaultRouteName == '/');
      final List<String> plannedInitialRouteNames = <String>[
        Navigator.defaultRouteName,
      ];
      final List<Route<dynamic>> plannedInitialRoutes = <Route<dynamic>>[
1315
        _routeNamed<dynamic>(Navigator.defaultRouteName, allowNull: true),
1316 1317 1318 1319 1320 1321 1322
      ];
      final List<String> routeParts = initialRouteName.split('/');
      if (initialRouteName.isNotEmpty) {
        String routeName = '';
        for (String part in routeParts) {
          routeName += '/$part';
          plannedInitialRouteNames.add(routeName);
1323
          plannedInitialRoutes.add(_routeNamed<dynamic>(routeName, allowNull: true));
1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341
        }
      }
      if (plannedInitialRoutes.contains(null)) {
        assert(() {
          FlutterError.reportError(
            new FlutterErrorDetails( // ignore: prefer_const_constructors, https://github.com/dart-lang/sdk/issues/29952
              exception:
                'Could not navigate to initial route.\n'
                'The requested route name was: "/$initialRouteName"\n'
                'The following routes were therefore attempted:\n'
                ' * ${plannedInitialRouteNames.join("\n * ")}\n'
                'This resulted in the following objects:\n'
                ' * ${plannedInitialRoutes.join("\n * ")}\n'
                'One or more of those objects was null, and therefore the initial route specified will be '
                'ignored and "${Navigator.defaultRouteName}" will be used instead.'
            ),
          );
          return true;
1342
        }());
1343
        push(_routeNamed<Object>(Navigator.defaultRouteName));
1344
      } else {
1345
        plannedInitialRoutes.forEach(push);
1346 1347
      }
    } else {
1348
      Route<Object> route;
1349
      if (initialRouteName != Navigator.defaultRouteName)
1350 1351
        route = _routeNamed<Object>(initialRouteName, allowNull: true);
      route ??= _routeNamed<Object>(Navigator.defaultRouteName);
1352 1353 1354 1355
      push(route);
    }
    for (Route<dynamic> route in _history)
      _initialOverlayEntries.addAll(route.overlayEntries);
1356 1357
  }

1358
  @override
1359
  void didUpdateWidget(Navigator oldWidget) {
1360
    super.didUpdateWidget(oldWidget);
1361 1362
    if (oldWidget.observers != widget.observers) {
      for (NavigatorObserver observer in oldWidget.observers)
1363
        observer._navigator = null;
1364
      for (NavigatorObserver observer in widget.observers) {
1365 1366 1367
        assert(observer.navigator == null);
        observer._navigator = this;
      }
1368
    }
1369 1370
    for (Route<dynamic> route in _history)
      route.changedExternalState();
1371 1372
  }

1373
  @override
1374
  void dispose() {
1375
    assert(!_debugLocked);
1376
    assert(() { _debugLocked = true; return true; }());
1377
    for (NavigatorObserver observer in widget.observers)
1378
      observer._navigator = null;
1379 1380
    final List<Route<dynamic>> doomed = _poppedRoutes.toList()..addAll(_history);
    for (Route<dynamic> route in doomed)
1381
      route.dispose();
1382 1383
    _poppedRoutes.clear();
    _history.clear();
1384
    focusScopeNode.detach();
1385
    super.dispose();
1386
    assert(() { _debugLocked = false; return true; }());
1387 1388
  }

1389
  /// The overlay this navigator uses for its visual presentation.
Adam Barth's avatar
Adam Barth committed
1390
  OverlayState get overlay => _overlayKey.currentState;
1391

Hixie's avatar
Hixie committed
1392
  OverlayEntry get _currentOverlayEntry {
1393
    for (Route<dynamic> route in _history.reversed) {
Adam Barth's avatar
Adam Barth committed
1394 1395
      if (route.overlayEntries.isNotEmpty)
        return route.overlayEntries.last;
1396
    }
Adam Barth's avatar
Adam Barth committed
1397
    return null;
1398 1399
  }

1400 1401
  bool _debugLocked = false; // used to prevent re-entrant calls to push, pop, and friends

1402
  Route<T> _routeNamed<T>(String name, { bool allowNull = false }) {
1403 1404
    assert(!_debugLocked);
    assert(name != null);
1405 1406 1407 1408
    final RouteSettings settings = new RouteSettings(
      name: name,
      isInitialRoute: _history.isEmpty,
    );
1409
    Route<T> route = widget.onGenerateRoute(settings);
1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421
    if (route == null && !allowNull) {
      assert(() {
        if (widget.onUnknownRoute == null) {
          throw new FlutterError(
            'If a Navigator has no onUnknownRoute, then its onGenerateRoute must never return null.\n'
            'When trying to build the route "$name", onGenerateRoute returned null, but there was no '
            'onUnknownRoute callback specified.\n'
            'The Navigator was:\n'
            '  $this'
          );
        }
        return true;
1422
      }());
1423
      route = widget.onUnknownRoute(settings);
1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434
      assert(() {
        if (route == null) {
          throw new FlutterError(
            'A Navigator\'s onUnknownRoute returned null.\n'
            'When trying to build the route "$name", both onGenerateRoute and onUnknownRoute returned '
            'null. The onUnknownRoute callback should never return null.\n'
            'The Navigator was:\n'
            '  $this'
          );
        }
        return true;
1435
      }());
1436 1437 1438 1439
    }
    return route;
  }

1440
  /// Push a named route onto the navigator.
1441
  ///
1442
  /// {@macro flutter.widgets.navigator.pushNamed}
1443
  ///
1444
  /// ## Sample code
1445 1446 1447 1448
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
1449 1450 1451
  /// void _aaronBurrSir() {
  ///   navigator.pushNamed('/nyc/1776');
  /// }
1452
  /// ```
1453 1454 1455
  @optionalTypeArgs
  Future<T> pushNamed<T extends Object>(String routeName) {
    return push<T>(_routeNamed<T>(routeName));
1456 1457
  }

1458 1459 1460
  /// Replace the current route of the navigator by pushing the route named
  /// [routeName] and then disposing the previous route once the new route has
  /// finished animating in.
1461
  ///
1462
  /// {@macro flutter.widgets.navigator.pushReplacementNamed}
1463
  ///
1464
  /// ## Sample code
1465
  ///
1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// void _startBike() {
  ///   navigator.pushReplacementNamed('/jouett/1781');
  /// }
  /// ```
  @optionalTypeArgs
  Future<T> pushReplacementNamed<T extends Object, TO extends Object>(String routeName, { TO result }) {
    return pushReplacement<T, TO>(_routeNamed<T>(routeName), result: result);
  }

  /// Pop the current route off the navigator and push a named route in its
  /// place.
  ///
  /// {@macro flutter.widgets.navigator.popAndPushNamed}
  ///
  /// ## Sample code
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// void _begin() {
  ///   navigator.popAndPushNamed('/nyc/1776');
  /// }
  /// ```
  @optionalTypeArgs
  Future<T> popAndPushNamed<T extends Object, TO extends Object>(String routeName, { TO result }) {
    pop<TO>(result);
    return pushNamed<T>(routeName);
  }

  /// Push the route with the given name onto the navigator, and then remove all
  /// the previous routes until the `predicate` returns true.
  ///
  /// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
  ///
  /// ## Sample code
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// void _handleOpenCalendar() {
  ///   navigator.pushNamedAndRemoveUntil('/calendar', ModalRoute.withName('/'));
  /// }
  /// ```
  @optionalTypeArgs
  Future<T> pushNamedAndRemoveUntil<T extends Object>(String newRouteName, RoutePredicate predicate) {
    return pushAndRemoveUntil<T>(_routeNamed<T>(newRouteName), predicate);
  }

  /// Push the given route onto the navigator.
  ///
  /// {@macro flutter.widgets.navigator.push}
  ///
  /// ## Sample code
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// void _openPage() {
1527
  ///   navigator.push(new MaterialPageRoute(builder: (BuildContext context) => new MyPage()));
1528 1529 1530 1531
  /// }
  /// ```
  @optionalTypeArgs
  Future<T> push<T extends Object>(Route<T> route) {
1532
    assert(!_debugLocked);
1533
    assert(() { _debugLocked = true; return true; }());
1534 1535
    assert(route != null);
    assert(route._navigator == null);
1536 1537 1538 1539 1540 1541 1542 1543 1544 1545
    final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
    route._navigator = this;
    route.install(_currentOverlayEntry);
    _history.add(route);
    route.didPush();
    route.didChangeNext(null);
    if (oldRoute != null)
      oldRoute.didChangeNext(route);
    for (NavigatorObserver observer in widget.observers)
      observer.didPush(route, oldRoute);
1546
    assert(() { _debugLocked = false; return true; }());
1547
    _cancelActivePointers();
1548
    return route.popped;
Hixie's avatar
Hixie committed
1549 1550
  }

1551 1552 1553
  /// Replace the current route of the navigator by pushing the given route and
  /// then disposing the previous route once the new route has finished
  /// animating in.
1554
  ///
1555
  /// {@macro flutter.widgets.navigator.pushReplacement}
1556
  ///
1557
  /// ## Sample code
1558
  ///
1559
  /// Typical usage is as follows:
1560
  ///
1561 1562
  /// ```dart
  /// void _doOpenPage() {
1563 1564
  ///   navigator.pushReplacement(
  ///       new MaterialPageRoute(builder: (BuildContext context) => new MyHomePage()));
1565 1566 1567 1568
  /// }
  /// ```
  @optionalTypeArgs
  Future<T> pushReplacement<T extends Object, TO extends Object>(Route<T> newRoute, { TO result }) {
1569
    assert(!_debugLocked);
1570
    assert(() { _debugLocked = true; return true; }());
1571 1572 1573 1574 1575
    final Route<dynamic> oldRoute = _history.last;
    assert(oldRoute != null && oldRoute._navigator == this);
    assert(oldRoute.overlayEntries.isNotEmpty);
    assert(newRoute._navigator == null);
    assert(newRoute.overlayEntries.isEmpty);
1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589
    final int index = _history.length - 1;
    assert(index >= 0);
    assert(_history.indexOf(oldRoute) == index);
    newRoute._navigator = this;
    newRoute.install(_currentOverlayEntry);
    _history[index] = newRoute;
    newRoute.didPush().whenCompleteOrCancel(() {
      // The old route's exit is not animated. We're assuming that the
      // new route completely obscures the old one.
      if (mounted) {
        oldRoute
          ..didComplete(result ?? oldRoute.currentResult)
          ..dispose();
      }
1590
    });
1591 1592 1593 1594 1595
    newRoute.didChangeNext(null);
    if (index > 0)
      _history[index - 1].didChangeNext(newRoute);
    for (NavigatorObserver observer in widget.observers)
      observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
1596
    assert(() { _debugLocked = false; return true; }());
1597 1598 1599 1600
    _cancelActivePointers();
    return newRoute.popped;
  }

1601 1602
  /// Push the given route onto the navigator, and then remove all the previous
  /// routes until the `predicate` returns true.
1603
  ///
1604
  /// {@macro flutter.widgets.navigator.pushAndRemoveUntil}
1605
  ///
1606
  /// ## Sample code
1607
  ///
1608
  /// Typical usage is as follows:
1609
  ///
1610 1611
  /// ```dart
  /// void _resetAndOpenPage() {
1612 1613
  ///   navigator.pushAndRemoveUntil(
  ///     new MaterialPageRoute(builder: (BuildContext context) => new MyHomePage()),
1614 1615 1616 1617 1618 1619
  ///     ModalRoute.withName('/'),
  ///   );
  /// }
  /// ```
  @optionalTypeArgs
  Future<T> pushAndRemoveUntil<T extends Object>(Route<T> newRoute, RoutePredicate predicate) {
1620
    assert(!_debugLocked);
1621
    assert(() { _debugLocked = true; return true; }());
1622 1623 1624 1625 1626 1627 1628 1629 1630
    final List<Route<dynamic>> removedRoutes = <Route<dynamic>>[];
    while (_history.isNotEmpty && !predicate(_history.last)) {
      final Route<dynamic> removedRoute = _history.removeLast();
      assert(removedRoute != null && removedRoute._navigator == this);
      assert(removedRoute.overlayEntries.isNotEmpty);
      removedRoutes.add(removedRoute);
    }
    assert(newRoute._navigator == null);
    assert(newRoute.overlayEntries.isEmpty);
1631 1632 1633 1634 1635 1636 1637 1638 1639
    final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
    newRoute._navigator = this;
    newRoute.install(_currentOverlayEntry);
    _history.add(newRoute);
    newRoute.didPush().whenCompleteOrCancel(() {
      if (mounted) {
        for (Route<dynamic> route in removedRoutes)
          route.dispose();
      }
1640
    });
1641 1642 1643 1644 1645 1646 1647 1648
    newRoute.didChangeNext(null);
    if (oldRoute != null)
      oldRoute.didChangeNext(newRoute);
    for (NavigatorObserver observer in widget.observers) {
      observer.didPush(newRoute, oldRoute);
      for (Route<dynamic> removedRoute in removedRoutes)
        observer.didRemove(removedRoute, oldRoute);
    }
1649
    assert(() { _debugLocked = false; return true; }());
1650 1651 1652 1653
    _cancelActivePointers();
    return newRoute.popped;
  }

1654
  /// Replaces a route on the navigator with a new route.
1655
  ///
1656
  /// {@macro flutter.widgets.navigator.replace}
1657
  ///
1658
  /// See also:
1659
  ///
1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692
  ///  * [replaceRouteBelow], which is the same but identifies the route to be
  ///    removed by reference to the route above it, rather than directly.
  @optionalTypeArgs
  void replace<T extends Object>({ @required Route<dynamic> oldRoute, @required Route<T> newRoute }) {
    assert(!_debugLocked);
    assert(oldRoute != null);
    assert(newRoute != null);
    if (oldRoute == newRoute) // ignore: unrelated_type_equality_checks, https://github.com/dart-lang/sdk/issues/32522
      return;
    assert(() { _debugLocked = true; return true; }());
    assert(oldRoute._navigator == this);
    assert(newRoute._navigator == null);
    assert(oldRoute.overlayEntries.isNotEmpty);
    assert(newRoute.overlayEntries.isEmpty);
    assert(!overlay.debugIsVisible(oldRoute.overlayEntries.last));
    final int index = _history.indexOf(oldRoute);
    assert(index >= 0);
    newRoute._navigator = this;
    newRoute.install(oldRoute.overlayEntries.last);
    _history[index] = newRoute;
    newRoute.didReplace(oldRoute);
    if (index + 1 < _history.length) {
      newRoute.didChangeNext(_history[index + 1]);
      _history[index + 1].didChangePrevious(newRoute);
    } else {
      newRoute.didChangeNext(null);
    }
    if (index > 0)
      _history[index - 1].didChangeNext(newRoute);
    for (NavigatorObserver observer in widget.observers)
      observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    oldRoute.dispose();
    assert(() { _debugLocked = false; return true; }());
1693 1694
  }

1695 1696 1697 1698
  /// Replaces a route on the navigator with a new route. The route to be
  /// replaced is the one below the given `anchorRoute`.
  ///
  /// {@macro flutter.widgets.navigator.replaceRouteBelow}
1699 1700 1701
  ///
  /// See also:
  ///
1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738
  ///  * [replace], which is the same but identifies the route to be removed
  ///    directly.
  @optionalTypeArgs
  void replaceRouteBelow<T extends Object>({ @required Route<dynamic> anchorRoute, Route<T> newRoute }) {
    assert(anchorRoute != null);
    assert(anchorRoute._navigator == this);
    assert(_history.indexOf(anchorRoute) > 0);
    replace<T>(oldRoute: _history[_history.indexOf(anchorRoute) - 1], newRoute: newRoute);
  }

  /// Whether the navigator can be popped.
  ///
  /// {@macro flutter.widgets.navigator.canPop}
  ///
  /// See also:
  ///
  ///  * [Route.isFirst], which returns true for routes for which [canPop]
  ///    returns false.
  bool canPop() {
    assert(_history.isNotEmpty);
    return _history.length > 1 || _history[0].willHandlePopInternally;
  }

  /// Returns the value of the current route's [Route.willPop] method for the
  /// navigator.
  ///
  /// {@macro flutter.widgets.navigator.maybePop}
  ///
  /// See also:
  ///
  /// * [Form], which provides an `onWillPop` callback that enables the form
  ///   to veto a [pop] initiated by the app's back button.
  /// * [ModalRoute], which provides a `scopedWillPopCallback` that can be used
  ///   to define the route's `willPop` method.
  @optionalTypeArgs
  Future<bool> maybePop<T extends Object>([ T result ]) async {
    final Route<T> route = _history.last;
1739
    assert(route._navigator == this);
1740 1741 1742 1743
    final RoutePopDisposition disposition = await route.willPop();
    if (disposition != RoutePopDisposition.bubble && mounted) {
      if (disposition == RoutePopDisposition.pop)
        pop(result);
1744 1745 1746
      return true;
    }
    return false;
1747 1748
  }

1749
  /// Pop the top-most route off the navigator.
1750
  ///
1751
  /// {@macro flutter.widgets.navigator.pop}
1752
  ///
1753
  /// ## Sample code
1754
  ///
1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771
  /// Typical usage for closing a route is as follows:
  ///
  /// ```dart
  /// void _handleClose() {
  ///   navigator.pop();
  /// }
  /// ```
  ///
  /// A dialog box might be closed with a result:
  ///
  /// ```dart
  /// void _handleAccept() {
  ///   navigator.pop(true); // dialog returns true
  /// }
  /// ```
  @optionalTypeArgs
  bool pop<T extends Object>([ T result ]) {
1772
    assert(!_debugLocked);
1773
    assert(() { _debugLocked = true; return true; }());
1774
    final Route<dynamic> route = _history.last;
Hixie's avatar
Hixie committed
1775
    assert(route._navigator == this);
Hixie's avatar
Hixie committed
1776
    bool debugPredictedWouldPop;
1777
    assert(() { debugPredictedWouldPop = !route.willHandlePopInternally; return true; }());
1778
    if (route.didPop(result ?? route.currentResult)) {
Hixie's avatar
Hixie committed
1779
      assert(debugPredictedWouldPop);
Hixie's avatar
Hixie committed
1780
      if (_history.length > 1) {
1781 1782 1783 1784 1785 1786 1787 1788 1789
        _history.removeLast();
        // If route._navigator is null, the route called finalizeRoute from
        // didPop, which means the route has already been disposed and doesn't
        // need to be added to _poppedRoutes for later disposal.
        if (route._navigator != null)
          _poppedRoutes.add(route);
        _history.last.didPopNext(route);
        for (NavigatorObserver observer in widget.observers)
          observer.didPop(route, _history.last);
Hixie's avatar
Hixie committed
1790
      } else {
1791
        assert(() { _debugLocked = false; return true; }());
Hixie's avatar
Hixie committed
1792 1793
        return false;
      }
Hixie's avatar
Hixie committed
1794 1795
    } else {
      assert(!debugPredictedWouldPop);
Hixie's avatar
Hixie committed
1796
    }
1797
    assert(() { _debugLocked = false; return true; }());
1798
    _cancelActivePointers();
Hixie's avatar
Hixie committed
1799
    return true;
Hixie's avatar
Hixie committed
1800 1801
  }

1802 1803 1804
  /// Calls [pop] repeatedly until the predicate returns true.
  ///
  /// {@macro flutter.widgets.navigator.popUntil}
1805
  ///
1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820
  /// ## Sample code
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// void _doLogout() {
  ///   navigator.popUntil(ModalRoute.withName('/login'));
  /// }
  /// ```
  void popUntil(RoutePredicate predicate) {
    while (!predicate(_history.last))
      pop();
  }

  /// Immediately remove `route` from the navigator, and [Route.dispose] it.
1821
  ///
1822
  /// {@macro flutter.widgets.navigator.removeRoute}
1823 1824 1825
  void removeRoute(Route<dynamic> route) {
    assert(route != null);
    assert(!_debugLocked);
1826
    assert(() { _debugLocked = true; return true; }());
1827 1828 1829 1830 1831
    assert(route._navigator == this);
    final int index = _history.indexOf(route);
    assert(index != -1);
    final Route<dynamic> previousRoute = index > 0 ? _history[index - 1] : null;
    final Route<dynamic> nextRoute = (index + 1 < _history.length) ? _history[index + 1] : null;
1832 1833 1834 1835 1836 1837
    _history.removeAt(index);
    previousRoute?.didChangeNext(nextRoute);
    nextRoute?.didChangePrevious(previousRoute);
    for (NavigatorObserver observer in widget.observers)
      observer.didRemove(route, previousRoute);
    route.dispose();
1838
    assert(() { _debugLocked = false; return true; }());
1839 1840 1841
    _cancelActivePointers();
  }

1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865
  /// Immediately remove a route from the navigator, and [Route.dispose] it. The
  /// route to be replaced is the one below the given `anchorRoute`.
  ///
  /// {@macro flutter.widgets.navigator.removeRouteBelow}
  void removeRouteBelow(Route<dynamic> anchorRoute) {
    assert(!_debugLocked);
    assert(() { _debugLocked = true; return true; }());
    assert(anchorRoute._navigator == this);
    final int index = _history.indexOf(anchorRoute) - 1;
    assert(index >= 0);
    final Route<dynamic> targetRoute = _history[index];
    assert(targetRoute._navigator == this);
    assert(targetRoute.overlayEntries.isEmpty || !overlay.debugIsVisible(targetRoute.overlayEntries.last));
    _history.removeAt(index);
    final Route<dynamic> nextRoute = index < _history.length ? _history[index] : null;
    final Route<dynamic> previousRoute = index > 0 ? _history[index - 1] : null;
    if (previousRoute != null)
      previousRoute.didChangeNext(nextRoute);
    if (nextRoute != null)
      nextRoute.didChangePrevious(previousRoute);
    targetRoute.dispose();
    assert(() { _debugLocked = false; return true; }());
  }

1866 1867 1868 1869 1870 1871 1872 1873 1874 1875
  /// Complete the lifecycle for a route that has been popped off the navigator.
  ///
  /// When the navigator pops a route, the navigator retains a reference to the
  /// route in order to call [Route.dispose] if the navigator itself is removed
  /// from the tree. When the route is finished with any exit animation, the
  /// route should call this function to complete its lifecycle (e.g., to
  /// receive a call to [Route.dispose]).
  ///
  /// The given `route` must have already received a call to [Route.didPop].
  /// This function may be called directly from [Route.didPop] if [Route.didPop]
1876
  /// will return true.
1877 1878 1879 1880 1881
  void finalizeRoute(Route<dynamic> route) {
    _poppedRoutes.remove(route);
    route.dispose();
  }

1882 1883 1884 1885
  /// Whether a route is currently being manipulated by the user, e.g.
  /// as during an iOS back gesture.
  bool get userGestureInProgress => _userGesturesInProgress > 0;
  int _userGesturesInProgress = 0;
1886

1887 1888
  /// The navigator is being controlled by a user gesture.
  ///
1889 1890 1891
  /// For example, called when the user beings an iOS back gesture.
  ///
  /// When the gesture finishes, call [didStopUserGesture].
1892
  void didStartUserGesture() {
1893 1894 1895 1896 1897
    _userGesturesInProgress += 1;
    if (_userGesturesInProgress == 1) {
      for (NavigatorObserver observer in widget.observers)
        observer.didStartUserGesture();
    }
1898 1899
  }

1900 1901 1902 1903
  /// A user gesture completed.
  ///
  /// Notifies the navigator that a gesture regarding which the navigator was
  /// previously notified with [didStartUserGesture] has completed.
1904
  void didStopUserGesture() {
1905 1906 1907 1908 1909 1910
    assert(_userGesturesInProgress > 0);
    _userGesturesInProgress -= 1;
    if (_userGesturesInProgress == 0) {
      for (NavigatorObserver observer in widget.observers)
        observer.didStopUserGesture();
    }
1911 1912
  }

1913
  final Set<int> _activePointers = new Set<int>();
Hixie's avatar
Hixie committed
1914

1915 1916 1917 1918 1919 1920 1921 1922 1923
  void _handlePointerDown(PointerDownEvent event) {
    _activePointers.add(event.pointer);
  }

  void _handlePointerUpOrCancel(PointerEvent event) {
    _activePointers.remove(event.pointer);
  }

  void _cancelActivePointers() {
1924
    // TODO(abarth): This mechanism is far from perfect. See https://github.com/flutter/flutter/issues/4770
1925 1926 1927 1928 1929 1930 1931
    if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
      // If we're between frames (SchedulerPhase.idle) then absorb any
      // subsequent pointers from this frame. The absorbing flag will be
      // reset in the next frame, see build().
      final RenderAbsorbPointer absorber = _overlayKey.currentContext?.ancestorRenderObjectOfType(const TypeMatcher<RenderAbsorbPointer>());
      setState(() {
        absorber?.absorbing = true;
1932 1933
        // We do this in setState so that we'll reset the absorbing value back
        // to false on the next frame.
1934 1935
      });
    }
1936
    _activePointers.toList().forEach(WidgetsBinding.instance.cancelPointer);
1937 1938
  }

1939
  @override
Adam Barth's avatar
Adam Barth committed
1940
  Widget build(BuildContext context) {
1941
    assert(!_debugLocked);
1942
    assert(_history.isNotEmpty);
1943 1944 1945 1946 1947
    return new Listener(
      onPointerDown: _handlePointerDown,
      onPointerUp: _handlePointerUpOrCancel,
      onPointerCancel: _handlePointerUpOrCancel,
      child: new AbsorbPointer(
1948
        absorbing: false, // it's mutated directly by _cancelActivePointers above
1949 1950 1951
        child: new FocusScope(
          node: focusScopeNode,
          autofocus: true,
1952 1953
          child: new Overlay(
            key: _overlayKey,
1954
            initialEntries: _initialOverlayEntries,
1955 1956 1957
          ),
        ),
      ),
1958 1959 1960
    );
  }
}