| // 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. |
| |
| import 'dart:async'; |
| import 'dart:developer' as developer; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/scheduler.dart'; |
| |
| import 'basic.dart'; |
| import 'binding.dart'; |
| import 'focus_manager.dart'; |
| import 'focus_scope.dart'; |
| import 'framework.dart'; |
| import 'overlay.dart'; |
| import 'ticker_provider.dart'; |
| |
| // Examples can assume: |
| // class MyPage extends Placeholder { MyPage({String title}); } |
| // class MyHomePage extends Placeholder { } |
| // NavigatorState navigator; |
| // BuildContext context; |
| |
| /// Creates a route for the given route settings. |
| /// |
| /// Used by [Navigator.onGenerateRoute] and [Navigator.onUnknownRoute]. |
| typedef RouteFactory = Route<dynamic> Function(RouteSettings settings); |
| |
| /// Signature for the [Navigator.popUntil] predicate argument. |
| typedef RoutePredicate = bool Function(Route<dynamic> route); |
| |
| /// Signature for a callback that verifies that it's OK to call [Navigator.pop]. |
| /// |
| /// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback], |
| /// [ModalRoute.removeScopedWillPopCallback], and [WillPopScope]. |
| typedef WillPopCallback = Future<bool> Function(); |
| |
| /// 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, |
| } |
| |
| /// 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. |
| /// |
| /// 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. |
| abstract class Route<T> { |
| /// Initialize the [Route]. |
| /// |
| /// If the [settings] are not provided, an empty [RouteSettings] object is |
| /// used instead. |
| Route({ RouteSettings settings }) : settings = settings ?? const RouteSettings(); |
| |
| /// The navigator that the route is in, if any. |
| NavigatorState get navigator => _navigator; |
| NavigatorState _navigator; |
| |
| /// The settings for this route. |
| /// |
| /// See [RouteSettings] for details. |
| final RouteSettings settings; |
| |
| /// The overlay entries for this route. |
| List<OverlayEntry> get overlayEntries => const <OverlayEntry>[]; |
| |
| /// Called when the route is inserted into the navigator. |
| /// |
| /// 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.) |
| /// |
| /// 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. |
| @protected |
| @mustCallSuper |
| void install(OverlayEntry insertionPoint) { } |
| |
| /// Called after [install] when the route is pushed onto the navigator. |
| /// |
| /// The returned value resolves when the push transition is complete. |
| /// |
| /// The [didChangeNext] and [didChangePrevious] methods are typically called |
| /// immediately after this method is called. |
| @protected |
| TickerFuture didPush() => TickerFuture.complete(); |
| |
| /// Called after [install] when the route replaced another in the navigator. |
| /// |
| /// The [didChangeNext] and [didChangePrevious] methods are typically called |
| /// immediately after this method is called. |
| @protected |
| @mustCallSuper |
| void didReplace(Route<dynamic> oldRoute) { } |
| |
| /// Returns false if this route wants to veto a [Navigator.pop]. This method is |
| /// called by [Navigator.maybePop]. |
| /// |
| /// 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. |
| /// |
| /// See also: |
| /// |
| /// * [Form], which provides a [Form.onWillPop] callback that uses this |
| /// mechanism. |
| /// * [WillPopScope], another widget that provides a way to intercept the |
| /// back button. |
| Future<RoutePopDisposition> willPop() async { |
| return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop; |
| } |
| |
| /// 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 = Completer<T>(); |
| |
| /// 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 |
| /// return false, otherwise return true (by return the value of calling |
| /// `super.didPop`). Returning false will prevent the default behavior of |
| /// [NavigatorState.pop]. |
| /// |
| /// 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. |
| @protected |
| @mustCallSuper |
| bool didPop(T result) { |
| didComplete(result); |
| return true; |
| } |
| |
| /// 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); |
| } |
| |
| /// The given route, which was above this one, has been popped off the |
| /// navigator. |
| @protected |
| @mustCallSuper |
| void didPopNext(Route<dynamic> nextRoute) { } |
| |
| /// This route's next route has changed to the given new route. This is called |
| /// 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. |
| @protected |
| @mustCallSuper |
| void didChangeNext(Route<dynamic> nextRoute) { } |
| |
| /// 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. `previousRoute` will be null if there's no |
| /// previous route. |
| @protected |
| @mustCallSuper |
| void didChangePrevious(Route<dynamic> previousRoute) { } |
| |
| /// Called whenever the internal state of the route has changed. |
| /// |
| /// 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. |
| @protected |
| @mustCallSuper |
| 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() { } |
| |
| /// The route should remove its overlays and free any other resources. |
| /// |
| /// This route is no longer referenced by the navigator. |
| @mustCallSuper |
| @protected |
| void dispose() { |
| _navigator = null; |
| } |
| |
| /// Whether this route is the top-most route on the navigator. |
| /// |
| /// If this is true, then [isActive] is also true. |
| bool get isCurrent { |
| return _navigator != null && _navigator._history.last == this; |
| } |
| |
| /// 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; |
| } |
| |
| /// Whether this route is on the navigator. |
| /// |
| /// If the route is not only active, but also the current route (the top-most |
| /// route), then [isCurrent] will also be true. If it is the first route (the |
| /// bottom-most route), then [isFirst] will also be true. |
| /// |
| /// If a higher route is entirely opaque, then the route will be active but not |
| /// rendered. It is even possible for the route to be active but for the stateful |
| /// widgets within the route to not be instantiated. See [ModalRoute.maintainState]. |
| bool get isActive { |
| return _navigator != null && _navigator._history.contains(this); |
| } |
| } |
| |
| /// Data that might be useful in constructing a [Route]. |
| @immutable |
| class RouteSettings { |
| /// Creates data used to construct routes. |
| const RouteSettings({ |
| this.name, |
| this.isInitialRoute = false, |
| this.arguments, |
| }); |
| |
| /// Creates a copy of this route settings object with the given fields |
| /// replaced with the new values. |
| RouteSettings copyWith({ |
| String name, |
| bool isInitialRoute, |
| Object arguments, |
| }) { |
| return RouteSettings( |
| name: name ?? this.name, |
| isInitialRoute: isInitialRoute ?? this.isInitialRoute, |
| arguments: arguments ?? this.arguments, |
| ); |
| } |
| |
| /// The name of the route (e.g., "/settings"). |
| /// |
| /// If null, the route is anonymous. |
| final String name; |
| |
| /// Whether this route is the very first route being pushed onto this [Navigator]. |
| /// |
| /// The initial route typically skips any entrance transition to speed startup. |
| final bool isInitialRoute; |
| |
| /// The arguments passed to this route. |
| /// |
| /// May be used when building the route, e.g. in [Navigator.onGenerateRoute]. |
| final Object arguments; |
| |
| @override |
| String toString() => '$runtimeType("$name", $arguments)'; |
| } |
| |
| /// An interface for observing the behavior of a [Navigator]. |
| class NavigatorObserver { |
| /// The navigator that the observer is observing, if any. |
| NavigatorState get navigator => _navigator; |
| NavigatorState _navigator; |
| |
| /// The [Navigator] pushed `route`. |
| /// |
| /// The route immediately below that one, and thus the previously active |
| /// route, is `previousRoute`. |
| void didPush(Route<dynamic> route, Route<dynamic> previousRoute) { } |
| |
| /// The [Navigator] popped `route`. |
| /// |
| /// The route immediately below that one, and thus the newly active |
| /// route, is `previousRoute`. |
| void didPop(Route<dynamic> route, Route<dynamic> previousRoute) { } |
| |
| /// The [Navigator] removed `route`. |
| /// |
| /// 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. |
| void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) { } |
| |
| /// The [Navigator] replaced `oldRoute` with `newRoute`. |
| void didReplace({ Route<dynamic> newRoute, Route<dynamic> oldRoute }) { } |
| |
| /// The [Navigator]'s route `route` is being moved by a user gesture. |
| /// |
| /// For example, this is called when an iOS back gesture starts. |
| /// |
| /// Paired with a call to [didStopUserGesture] when the route is no longer |
| /// being manipulated via user gesture. |
| /// |
| /// If present, the route immediately below `route` is `previousRoute`. |
| /// Though the gesture may not necessarily conclude at `previousRoute` if |
| /// the gesture is canceled. In that case, [didStopUserGesture] is still |
| /// called but a follow-up [didPop] is not. |
| void didStartUserGesture(Route<dynamic> route, Route<dynamic> previousRoute) { } |
| |
| /// User gesture is no longer controlling the [Navigator]. |
| /// |
| /// Paired with an earlier call to [didStartUserGesture]. |
| void didStopUserGesture() { } |
| } |
| |
| /// A widget that manages a set of child widgets with a stack discipline. |
| /// |
| /// 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 |
| /// the navigator visually transition from one page to another by moving the widgets |
| /// around in the overlay. Similarly, the navigator can be used to show a dialog |
| /// by positioning the dialog widget above the current page. |
| /// |
| /// ## 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]. |
| /// |
| /// 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. |
| /// |
| /// ```dart |
| /// void main() { |
| /// runApp(MaterialApp(home: 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 |
| /// Navigator.push(context, MaterialPageRoute<void>( |
| /// builder: (BuildContext context) { |
| /// return Scaffold( |
| /// appBar: AppBar(title: Text('My Page')), |
| /// body: Center( |
| /// child: FlatButton( |
| /// child: Text('POP'), |
| /// onPressed: () { |
| /// Navigator.pop(context); |
| /// }, |
| /// ), |
| /// ), |
| /// ); |
| /// }, |
| /// )); |
| /// ``` |
| /// |
| /// 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 |
| /// Navigator.pop(context); |
| /// ``` |
| /// |
| /// It usually isn't necessary to provide a widget that pops the Navigator |
| /// in a route with a [Scaffold] because the Scaffold automatically adds a |
| /// '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 |
| /// 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 |
| /// with a [Map<String, WidgetBuilder>] which maps from a route's name to |
| /// 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(MaterialApp( |
| /// home: MyAppHome(), // becomes the route named '/' |
| /// routes: <String, WidgetBuilder> { |
| /// '/a': (BuildContext context) => MyPage(title: 'page A'), |
| /// '/b': (BuildContext context) => MyPage(title: 'page B'), |
| /// '/c': (BuildContext context) => MyPage(title: 'page C'), |
| /// }, |
| /// )); |
| /// } |
| /// ``` |
| /// |
| /// To show a route by name: |
| /// |
| /// ```dart |
| /// Navigator.pushNamed(context, '/b'); |
| /// ``` |
| /// |
| /// ### 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. |
| /// |
| /// 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` |
| /// 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]: |
| /// |
| /// ```dart |
| /// bool value = await Navigator.push(context, MaterialPageRoute<bool>( |
| /// builder: (BuildContext context) { |
| /// return Center( |
| /// child: GestureDetector( |
| /// child: Text('OK'), |
| /// onTap: () { Navigator.pop(context, true); } |
| /// ), |
| /// ); |
| /// } |
| /// )); |
| /// ``` |
| /// |
| /// 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. |
| /// |
| /// 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.) |
| /// |
| /// ### Popup routes |
| /// |
| /// 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. |
| /// |
| /// There are functions which create and show popup routes. For |
| /// example: [showDialog], [showMenu], and [showModalBottomSheet]. These |
| /// 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. |
| /// |
| /// There are also widgets which create popup routes, like [PopupMenuButton] and |
| /// [DropdownButton]. These widgets create internal subclasses of PopupRoute |
| /// and use the Navigator's push and pop methods to show and dismiss them. |
| /// |
| /// ### Custom routes |
| /// |
| /// You can create your own subclass of one of the widget library route classes |
| /// 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. |
| /// |
| /// The [PageRouteBuilder] class makes it possible to define a custom route |
| /// 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. |
| /// |
| /// ```dart |
| /// Navigator.push(context, PageRouteBuilder( |
| /// opaque: false, |
| /// pageBuilder: (BuildContext context, _, __) { |
| /// return Center(child: Text('My PageRoute')); |
| /// }, |
| /// transitionsBuilder: (___, Animation<double> animation, ____, Widget child) { |
| /// return FadeTransition( |
| /// opacity: animation, |
| /// child: RotationTransition( |
| /// turns: Tween<double>(begin: 0.5, end: 1.0).animate(animation), |
| /// child: child, |
| /// ), |
| /// ); |
| /// } |
| /// )); |
| /// ``` |
| /// |
| /// 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. |
| /// |
| /// ### 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. |
| /// |
| /// {@tool snippet --template=freeform} |
| /// 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]. |
| /// |
| /// Run this example with `flutter run --route=/signup` to start it with |
| /// the signup flow instead of on the home page. |
| /// |
| /// ```dart imports |
| /// import 'package:flutter/material.dart'; |
| /// ``` |
| /// |
| /// ```dart main |
| /// void main() => runApp(new MyApp()); |
| /// ``` |
| /// |
| /// ```dart |
| /// class MyApp extends StatelessWidget { |
| /// @override |
| /// Widget build(BuildContext context) { |
| /// return MaterialApp( |
| /// title: 'Flutter Code Sample for Navigator', |
| /// // MaterialApp contains our top-level Navigator |
| /// initialRoute: '/', |
| /// routes: { |
| /// '/': (BuildContext context) => HomePage(), |
| /// '/signup': (BuildContext context) => SignUpPage(), |
| /// }, |
| /// ); |
| /// } |
| /// } |
| /// |
| /// class HomePage extends StatelessWidget { |
| /// @override |
| /// Widget build(BuildContext context) { |
| /// return DefaultTextStyle( |
| /// style: Theme.of(context).textTheme.display1, |
| /// child: Container( |
| /// color: Colors.white, |
| /// alignment: Alignment.center, |
| /// child: Text('Home Page'), |
| /// ), |
| /// ); |
| /// } |
| /// } |
| /// |
| /// class CollectPersonalInfoPage extends StatelessWidget { |
| /// @override |
| /// Widget build(BuildContext context) { |
| /// return DefaultTextStyle( |
| /// style: Theme.of(context).textTheme.display1, |
| /// child: GestureDetector( |
| /// onTap: () { |
| /// // This moves from the personal info page to the credentials page, |
| /// // replacing this page with that one. |
| /// Navigator.of(context) |
| /// .pushReplacementNamed('signup/choose_credentials'); |
| /// }, |
| /// child: Container( |
| /// color: Colors.lightBlue, |
| /// alignment: Alignment.center, |
| /// child: Text('Collect Personal Info Page'), |
| /// ), |
| /// ), |
| /// ); |
| /// } |
| /// } |
| /// |
| /// class ChooseCredentialsPage extends StatelessWidget { |
| /// const ChooseCredentialsPage({ |
| /// this.onSignupComplete, |
| /// }); |
| /// |
| /// final VoidCallback onSignupComplete; |
| /// |
| /// @override |
| /// Widget build(BuildContext context) { |
| /// return GestureDetector( |
| /// onTap: onSignupComplete, |
| /// child: DefaultTextStyle( |
| /// style: Theme.of(context).textTheme.display1, |
| /// child: Container( |
| /// color: Colors.pinkAccent, |
| /// alignment: Alignment.center, |
| /// child: Text('Choose Credentials Page'), |
| /// ), |
| /// ), |
| /// ); |
| /// } |
| /// } |
| /// |
| /// 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 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 _) => CollectPersonalInfoPage(); |
| /// break; |
| /// case 'signup/choose_credentials': |
| /// // Assume ChooseCredentialsPage collects new credentials and then |
| /// // invokes 'onSignupComplete()'. |
| /// builder = (BuildContext _) => 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 Exception('Invalid route: ${settings.name}'); |
| /// } |
| /// return MaterialPageRoute(builder: builder, settings: settings); |
| /// }, |
| /// ); |
| /// } |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// [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. |
| class Navigator extends StatefulWidget { |
| /// Creates a widget that maintains a stack-based history of child widgets. |
| /// |
| /// The [onGenerateRoute] argument must not be null. |
| const Navigator({ |
| Key key, |
| this.initialRoute, |
| @required this.onGenerateRoute, |
| this.onUnknownRoute, |
| this.observers = const <NavigatorObserver>[] |
| }) : assert(onGenerateRoute != null), |
| super(key: key); |
| |
| /// The name of the first route to show. |
| /// |
| /// 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. |
| final String initialRoute; |
| |
| /// Called to generate a route for a given [RouteSettings]. |
| final RouteFactory onGenerateRoute; |
| |
| /// 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. |
| final RouteFactory onUnknownRoute; |
| |
| /// A list of observers for this navigator. |
| final List<NavigatorObserver> observers; |
| |
| /// The default name for the [initialRoute]. |
| /// |
| /// See also: |
| /// |
| /// * [dart:ui.Window.defaultRouteName], which reflects the route that the |
| /// application was started with. |
| static const String defaultRouteName = '/'; |
| |
| /// Push a named route onto the navigator that most tightly encloses the given |
| /// context. |
| /// |
| /// {@template flutter.widgets.navigator.pushNamed} |
| /// The route name will be passed to that navigator's [onGenerateRoute] |
| /// callback. The returned route will be pushed into the navigator. |
| /// |
| /// 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. |
| /// |
| /// 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 route. |
| /// {@endtemplate} |
| /// |
| /// {@template flutter.widgets.navigator.pushNamed.arguments} |
| /// The provided `arguments` are passed to the pushed route via |
| /// [RouteSettings.arguments]. Any object can be passed as `arguments` (e.g. a |
| /// [String], [int], or an instance of a custom `MyRouteArguments` class). |
| /// Often, a [Map] is used to pass key-value pairs. |
| /// |
| /// The `arguments` may be used in [Navigator.onGenerateRoute] or |
| /// [Navigator.onUnknownRoute] to construct the route. |
| /// {@endtemplate} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _didPushButton() { |
| /// Navigator.pushNamed(context, '/settings'); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// {@tool sample} |
| /// |
| /// The following example shows how to pass additional `arguments` to the |
| /// route: |
| /// |
| /// ```dart |
| /// void _showBerlinWeather() { |
| /// Navigator.pushNamed( |
| /// context, |
| /// '/weather', |
| /// arguments: <String, String>{ |
| /// 'city': 'Berlin', |
| /// 'country': 'Germany', |
| /// }, |
| /// ); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// {@tool sample} |
| /// |
| /// The following example shows how to pass a custom Object to the route: |
| /// |
| /// ```dart |
| /// class WeatherRouteArguments { |
| /// WeatherRouteArguments({ this.city, this.country }); |
| /// final String city; |
| /// final String country; |
| /// |
| /// bool get isGermanCapital { |
| /// return country == 'Germany' && city == 'Berlin'; |
| /// } |
| /// } |
| /// |
| /// void _showWeather() { |
| /// Navigator.pushNamed( |
| /// context, |
| /// '/weather', |
| /// arguments: WeatherRouteArguments(city: 'Berlin', country: 'Germany'), |
| /// ); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| static Future<T> pushNamed<T extends Object>( |
| BuildContext context, |
| String routeName, { |
| Object arguments, |
| }) { |
| return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments); |
| } |
| |
| /// 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. |
| /// |
| /// {@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 |
| /// [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]). The removed |
| /// route's exit animation is not run (see [popAndPushNamed] for a variant |
| /// that does animated the removed route). |
| /// |
| /// 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 type of the return value of the old route. |
| /// {@endtemplate} |
| /// |
| /// {@macro flutter.widgets.navigator.pushNamed.arguments} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _switchToBrightness() { |
| /// Navigator.pushReplacementNamed(context, '/settings/brightness'); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| static Future<T> pushReplacementNamed<T extends Object, TO extends Object>( |
| BuildContext context, |
| String routeName, { |
| TO result, |
| Object arguments, |
| }) { |
| return Navigator.of(context).pushReplacementNamed<T, TO>(routeName, arguments: arguments, result: result); |
| } |
| |
| /// Pop the current route off the navigator that most tightly encloses the |
| /// given context and push a named route in its place. |
| /// |
| /// {@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`). |
| /// |
| /// The route name will be passed to the navigator's [onGenerateRoute] |
| /// callback. The returned route will be pushed into the navigator. |
| /// |
| /// 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]). |
| /// |
| /// 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} |
| /// |
| /// {@macro flutter.widgets.navigator.pushNamed.arguments} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _selectAccessibility() { |
| /// Navigator.popAndPushNamed(context, '/settings/accessibility'); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| static Future<T> popAndPushNamed<T extends Object, TO extends Object>( |
| BuildContext context, |
| String routeName, { |
| TO result, |
| Object arguments, |
| }) { |
| return Navigator.of(context).popAndPushNamed<T, TO>(routeName, arguments: arguments, result: result); |
| } |
| |
| /// 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. |
| /// |
| /// {@template flutter.widgets.navigator.pushNamedAndRemoveUntil} |
| /// 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'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} |
| /// |
| /// {@macro flutter.widgets.navigator.pushNamed.arguments} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _resetToCalendar() { |
| /// Navigator.pushNamedAndRemoveUntil(context, '/calendar', ModalRoute.withName('/')); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| static Future<T> pushNamedAndRemoveUntil<T extends Object>( |
| BuildContext context, |
| String newRouteName, |
| RoutePredicate predicate, { |
| Object arguments, |
| }) { |
| return Navigator.of(context).pushNamedAndRemoveUntil<T>(newRouteName, predicate, arguments: arguments); |
| } |
| |
| /// Push the given route onto the navigator that most tightly encloses the |
| /// given context. |
| /// |
| /// {@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. |
| /// |
| /// 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 route. |
| /// {@endtemplate} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _openMyPage() { |
| /// Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => MyPage())); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| static Future<T> push<T extends Object>(BuildContext context, Route<T> route) { |
| return Navigator.of(context).push(route); |
| } |
| |
| /// 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. |
| /// |
| /// {@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. |
| /// |
| /// 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 type of the return value of the old route. |
| /// {@endtemplate} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _completeLogin() { |
| /// Navigator.pushReplacement( |
| /// context, MaterialPageRoute(builder: (BuildContext context) => MyHomePage())); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @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); |
| } |
| |
| /// 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. |
| /// |
| /// {@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. |
| /// |
| /// 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} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _finishAccountCreation() { |
| /// Navigator.pushAndRemoveUntil( |
| /// context, |
| /// MaterialPageRoute(builder: (BuildContext context) => MyHomePage()), |
| /// ModalRoute.withName('/'), |
| /// ); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| static Future<T> pushAndRemoveUntil<T extends Object>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) { |
| return Navigator.of(context).pushAndRemoveUntil<T>(newRoute, predicate); |
| } |
| |
| /// Replaces a route on the navigator that most tightly encloses the given |
| /// context with a new route. |
| /// |
| /// {@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. |
| /// |
| /// The removed route is removed without being completed, so this method does |
| /// not take a return value argument. |
| /// |
| /// 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); |
| } |
| |
| /// 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`. |
| /// |
| /// {@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. |
| /// |
| /// The removed route is removed without being completed, so this method does |
| /// not take a return value argument. |
| /// |
| /// 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); |
| } |
| |
| /// 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} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage for closing a route is as follows: |
| /// |
| /// ```dart |
| /// void _close() { |
| /// Navigator.pop(context); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// 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} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _logout() { |
| /// Navigator.popUntil(context, ModalRoute.withName('/login')); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| 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, { |
| bool rootNavigator = false, |
| bool nullOk = false, |
| }) { |
| final NavigatorState navigator = rootNavigator |
| ? context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>()) |
| : context.ancestorStateOfType(const TypeMatcher<NavigatorState>()); |
| assert(() { |
| if (navigator == null && !nullOk) { |
| throw 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() => NavigatorState(); |
| } |
| |
| /// The state for a [Navigator] widget. |
| class NavigatorState extends State<Navigator> with TickerProviderStateMixin { |
| final GlobalKey<OverlayState> _overlayKey = GlobalKey<OverlayState>(); |
| final List<Route<dynamic>> _history = <Route<dynamic>>[]; |
| final Set<Route<dynamic>> _poppedRoutes = Set<Route<dynamic>>(); |
| |
| /// The [FocusScopeNode] for the [FocusScope] that encloses the routes. |
| final FocusScopeNode focusScopeNode = FocusScopeNode(); |
| |
| final List<OverlayEntry> _initialOverlayEntries = <OverlayEntry>[]; |
| |
| @override |
| void initState() { |
| super.initState(); |
| for (NavigatorObserver observer in widget.observers) { |
| assert(observer.navigator == null); |
| observer._navigator = this; |
| } |
| 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>>[ |
| _routeNamed<dynamic>(Navigator.defaultRouteName, allowNull: true, arguments: null), |
| ]; |
| final List<String> routeParts = initialRouteName.split('/'); |
| if (initialRouteName.isNotEmpty) { |
| String routeName = ''; |
| for (String part in routeParts) { |
| routeName += '/$part'; |
| plannedInitialRouteNames.add(routeName); |
| plannedInitialRoutes.add(_routeNamed<dynamic>(routeName, allowNull: true, arguments: null)); |
| } |
| } |
| if (plannedInitialRoutes.contains(null)) { |
| assert(() { |
| FlutterError.reportError( |
| FlutterErrorDetails( |
| 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; |
| }()); |
| push(_routeNamed<Object>(Navigator.defaultRouteName, arguments: null)); |
| } else { |
| plannedInitialRoutes.forEach(push); |
| } |
| } else { |
| Route<Object> route; |
| if (initialRouteName != Navigator.defaultRouteName) |
| route = _routeNamed<Object>(initialRouteName, allowNull: true, arguments: null); |
| route ??= _routeNamed<Object>(Navigator.defaultRouteName, arguments: null); |
| push(route); |
| } |
| for (Route<dynamic> route in _history) |
| _initialOverlayEntries.addAll(route.overlayEntries); |
| } |
| |
| @override |
| void didUpdateWidget(Navigator oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| if (oldWidget.observers != widget.observers) { |
| for (NavigatorObserver observer in oldWidget.observers) |
| observer._navigator = null; |
| for (NavigatorObserver observer in widget.observers) { |
| assert(observer.navigator == null); |
| observer._navigator = this; |
| } |
| } |
| for (Route<dynamic> route in _history) |
| route.changedExternalState(); |
| } |
| |
| @override |
| void dispose() { |
| assert(!_debugLocked); |
| assert(() { _debugLocked = true; return true; }()); |
| for (NavigatorObserver observer in widget.observers) |
| observer._navigator = null; |
| final List<Route<dynamic>> doomed = _poppedRoutes.toList()..addAll(_history); |
| for (Route<dynamic> route in doomed) |
| route.dispose(); |
| _poppedRoutes.clear(); |
| _history.clear(); |
| focusScopeNode.detach(); |
| super.dispose(); |
| assert(() { _debugLocked = false; return true; }()); |
| } |
| |
| /// The overlay this navigator uses for its visual presentation. |
| OverlayState get overlay => _overlayKey.currentState; |
| |
| OverlayEntry get _currentOverlayEntry { |
| for (Route<dynamic> route in _history.reversed) { |
| if (route.overlayEntries.isNotEmpty) |
| return route.overlayEntries.last; |
| } |
| return null; |
| } |
| |
| bool _debugLocked = false; // used to prevent re-entrant calls to push, pop, and friends |
| |
| Route<T> _routeNamed<T>(String name, { @required Object arguments, bool allowNull = false }) { |
| assert(!_debugLocked); |
| assert(name != null); |
| final RouteSettings settings = RouteSettings( |
| name: name, |
| isInitialRoute: _history.isEmpty, |
| arguments: arguments, |
| ); |
| Route<T> route = widget.onGenerateRoute(settings); |
| if (route == null && !allowNull) { |
| assert(() { |
| if (widget.onUnknownRoute == null) { |
| throw 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; |
| }()); |
| route = widget.onUnknownRoute(settings); |
| assert(() { |
| if (route == null) { |
| throw 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; |
| }()); |
| } |
| return route; |
| } |
| |
| /// Push a named route onto the navigator. |
| /// |
| /// {@macro flutter.widgets.navigator.pushNamed} |
| /// |
| /// {@macro flutter.widgets.navigator.pushNamed.arguments} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _aaronBurrSir() { |
| /// navigator.pushNamed('/nyc/1776'); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| Future<T> pushNamed<T extends Object>( |
| String routeName, { |
| Object arguments, |
| }) { |
| return push<T>(_routeNamed<T>(routeName, arguments: arguments)); |
| } |
| |
| /// 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. |
| /// |
| /// {@macro flutter.widgets.navigator.pushReplacementNamed} |
| /// |
| /// {@macro flutter.widgets.navigator.pushNamed.arguments} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _startBike() { |
| /// navigator.pushReplacementNamed('/jouett/1781'); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| Future<T> pushReplacementNamed<T extends Object, TO extends Object>( |
| String routeName, { |
| TO result, |
| Object arguments, |
| }) { |
| return pushReplacement<T, TO>(_routeNamed<T>(routeName, arguments: arguments), result: result); |
| } |
| |
| /// Pop the current route off the navigator and push a named route in its |
| /// place. |
| /// |
| /// {@macro flutter.widgets.navigator.popAndPushNamed} |
| /// |
| /// {@macro flutter.widgets.navigator.pushNamed.arguments} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _begin() { |
| /// navigator.popAndPushNamed('/nyc/1776'); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| Future<T> popAndPushNamed<T extends Object, TO extends Object>( |
| String routeName, { |
| TO result, |
| Object arguments, |
| }) { |
| pop<TO>(result); |
| return pushNamed<T>(routeName, arguments: arguments); |
| } |
| |
| /// 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} |
| /// |
| /// {@macro flutter.widgets.navigator.pushNamed.arguments} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _handleOpenCalendar() { |
| /// navigator.pushNamedAndRemoveUntil('/calendar', ModalRoute.withName('/')); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| Future<T> pushNamedAndRemoveUntil<T extends Object>( |
| String newRouteName, |
| RoutePredicate predicate, { |
| Object arguments, |
| }) { |
| return pushAndRemoveUntil<T>(_routeNamed<T>(newRouteName, arguments: arguments), predicate); |
| } |
| |
| /// Push the given route onto the navigator. |
| /// |
| /// {@macro flutter.widgets.navigator.push} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _openPage() { |
| /// navigator.push(MaterialPageRoute(builder: (BuildContext context) => MyPage())); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| Future<T> push<T extends Object>(Route<T> route) { |
| assert(!_debugLocked); |
| assert(() { _debugLocked = true; return true; }()); |
| assert(route != null); |
| assert(route._navigator == null); |
| 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); |
| route.didChangePrevious(oldRoute); |
| } |
| for (NavigatorObserver observer in widget.observers) |
| observer.didPush(route, oldRoute); |
| assert(() { _debugLocked = false; return true; }()); |
| _afterNavigation(); |
| return route.popped; |
| } |
| |
| void _afterNavigation() { |
| if (!kReleaseMode) { |
| // This event is used by performance tools that show stats for the |
| // time interval since the last navigation event occurred ensuring that |
| // stats only reflect the current page. |
| // These tools do not need to know exactly what the new route is so no |
| // attempt is made to describe the current route as part of the event. |
| developer.postEvent('Flutter.Navigation', <String, dynamic>{}); |
| } |
| _cancelActivePointers(); |
| } |
| |
| /// 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. |
| /// |
| /// {@macro flutter.widgets.navigator.pushReplacement} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _doOpenPage() { |
| /// navigator.pushReplacement( |
| /// MaterialPageRoute(builder: (BuildContext context) => MyHomePage())); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| Future<T> pushReplacement<T extends Object, TO extends Object>(Route<T> newRoute, { TO result }) { |
| assert(!_debugLocked); |
| assert(() { _debugLocked = true; return true; }()); |
| final Route<dynamic> oldRoute = _history.last; |
| assert(oldRoute != null && oldRoute._navigator == this); |
| assert(oldRoute.overlayEntries.isNotEmpty); |
| assert(newRoute._navigator == null); |
| assert(newRoute.overlayEntries.isEmpty); |
| 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(); |
| } |
| }); |
| newRoute.didChangeNext(null); |
| if (index > 0) { |
| _history[index - 1].didChangeNext(newRoute); |
| newRoute.didChangePrevious(_history[index - 1]); |
| } |
| for (NavigatorObserver observer in widget.observers) |
| observer.didReplace(newRoute: newRoute, oldRoute: oldRoute); |
| assert(() { _debugLocked = false; return true; }()); |
| _afterNavigation(); |
| return newRoute.popped; |
| } |
| |
| /// Push the given route onto the navigator, and then remove all the previous |
| /// routes until the `predicate` returns true. |
| /// |
| /// {@macro flutter.widgets.navigator.pushAndRemoveUntil} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _resetAndOpenPage() { |
| /// navigator.pushAndRemoveUntil( |
| /// MaterialPageRoute(builder: (BuildContext context) => MyHomePage()), |
| /// ModalRoute.withName('/'), |
| /// ); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| Future<T> pushAndRemoveUntil<T extends Object>(Route<T> newRoute, RoutePredicate predicate) { |
| assert(!_debugLocked); |
| assert(() { _debugLocked = true; return true; }()); |
| 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); |
| 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(); |
| } |
| }); |
| 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); |
| } |
| assert(() { _debugLocked = false; return true; }()); |
| _afterNavigation(); |
| return newRoute.popped; |
| } |
| |
| /// Replaces a route on the navigator with a new route. |
| /// |
| /// {@macro flutter.widgets.navigator.replace} |
| /// |
| /// 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 |
| void replace<T extends Object>({ @required Route<dynamic> oldRoute, @required Route<T> newRoute }) { |
| assert(!_debugLocked); |
| assert(oldRoute != null); |
| assert(newRoute != null); |
| if (oldRoute == newRoute) |
| 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); |
| newRoute.didChangePrevious(_history[index - 1]); |
| } |
| for (NavigatorObserver observer in widget.observers) |
| observer.didReplace(newRoute: newRoute, oldRoute: oldRoute); |
| oldRoute.dispose(); |
| assert(() { _debugLocked = false; return true; }()); |
| } |
| |
| /// 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} |
| /// |
| /// See also: |
| /// |
| /// * [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; |
| assert(route._navigator == this); |
| final RoutePopDisposition disposition = await route.willPop(); |
| if (disposition != RoutePopDisposition.bubble && mounted) { |
| if (disposition == RoutePopDisposition.pop) |
| pop(result); |
| return true; |
| } |
| return false; |
| } |
| |
| /// Pop the top-most route off the navigator. |
| /// |
| /// {@macro flutter.widgets.navigator.pop} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage for closing a route is as follows: |
| /// |
| /// ```dart |
| /// void _handleClose() { |
| /// navigator.pop(); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// {@tool sample} |
| /// |
| /// A dialog box might be closed with a result: |
| /// |
| /// ```dart |
| /// void _handleAccept() { |
| /// navigator.pop(true); // dialog returns true |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| bool pop<T extends Object>([ T result ]) { |
| assert(!_debugLocked); |
| assert(() { _debugLocked = true; return true; }()); |
| final Route<dynamic> route = _history.last; |
| assert(route._navigator == this); |
| bool debugPredictedWouldPop; |
| assert(() { debugPredictedWouldPop = !route.willHandlePopInternally; return true; }()); |
| if (route.didPop(result ?? route.currentResult)) { |
| assert(debugPredictedWouldPop); |
| if (_history.length > 1) { |
| _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); |
| } else { |
| assert(() { _debugLocked = false; return true; }()); |
| return false; |
| } |
| } else { |
| assert(!debugPredictedWouldPop); |
| } |
| assert(() { _debugLocked = false; return true; }()); |
| _afterNavigation(); |
| return true; |
| } |
| |
| /// Calls [pop] repeatedly until the predicate returns true. |
| /// |
| /// {@macro flutter.widgets.navigator.popUntil} |
| /// |
| /// {@tool sample} |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// void _doLogout() { |
| /// navigator.popUntil(ModalRoute.withName('/login')); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| void popUntil(RoutePredicate predicate) { |
| while (!predicate(_history.last)) |
| pop(); |
| } |
| |
| /// Immediately remove `route` from the navigator, and [Route.dispose] it. |
| /// |
| /// {@macro flutter.widgets.navigator.removeRoute} |
| void removeRoute(Route<dynamic> route) { |
| assert(route != null); |
| assert(!_debugLocked); |
| assert(() { _debugLocked = true; return true; }()); |
| 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; |
| _history.removeAt(index); |
| previousRoute?.didChangeNext(nextRoute); |
| nextRoute?.didChangePrevious(previousRoute); |
| for (NavigatorObserver observer in widget.observers) |
| observer.didRemove(route, previousRoute); |
| route.dispose(); |
| assert(() { _debugLocked = false; return true; }()); |
| _afterNavigation(); |
| } |
| |
| /// 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; }()); |
| } |
| |
| /// 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] |
| /// will return true. |
| void finalizeRoute(Route<dynamic> route) { |
| _poppedRoutes.remove(route); |
| route.dispose(); |
| } |
| |
| /// 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; |
| |
| /// The navigator is being controlled by a user gesture. |
| /// |
| /// For example, called when the user beings an iOS back gesture. |
| /// |
| /// When the gesture finishes, call [didStopUserGesture]. |
| void didStartUserGesture() { |
| _userGesturesInProgress += 1; |
| if (_userGesturesInProgress == 1) { |
| final Route<dynamic> route = _history.last; |
| final Route<dynamic> previousRoute = !route.willHandlePopInternally && _history.length > 1 |
| ? _history[_history.length - 2] |
| : null; |
| // Don't operate the _history list since the gesture may be cancelled. |
| // In case of a back swipe, the gesture controller will call .pop() itself. |
| |
| for (NavigatorObserver observer in widget.observers) |
| observer.didStartUserGesture(route, previousRoute); |
| } |
| } |
| |
| /// A user gesture completed. |
| /// |
| /// Notifies the navigator that a gesture regarding which the navigator was |
| /// previously notified with [didStartUserGesture] has completed. |
| void didStopUserGesture() { |
| assert(_userGesturesInProgress > 0); |
| _userGesturesInProgress -= 1; |
| if (_userGesturesInProgress == 0) { |
| for (NavigatorObserver observer in widget.observers) |
| observer.didStopUserGesture(); |
| } |
| } |
| |
| final Set<int> _activePointers = Set<int>(); |
| |
| void _handlePointerDown(PointerDownEvent event) { |
| _activePointers.add(event.pointer); |
| } |
| |
| void _handlePointerUpOrCancel(PointerEvent event) { |
| _activePointers.remove(event.pointer); |
| } |
| |
| void _cancelActivePointers() { |
| // TODO(abarth): This mechanism is far from perfect. See https://github.com/flutter/flutter/issues/4770 |
| 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; |
| // We do this in setState so that we'll reset the absorbing value back |
| // to false on the next frame. |
| }); |
| } |
| _activePointers.toList().forEach(WidgetsBinding.instance.cancelPointer); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| assert(!_debugLocked); |
| assert(_history.isNotEmpty); |
| return Listener( |
| onPointerDown: _handlePointerDown, |
| onPointerUp: _handlePointerUpOrCancel, |
| onPointerCancel: _handlePointerUpOrCancel, |
| child: AbsorbPointer( |
| absorbing: false, // it's mutated directly by _cancelActivePointers above |
| child: FocusScope( |
| node: focusScopeNode, |
| autofocus: true, |
| child: Overlay( |
| key: _overlayKey, |
| initialEntries: _initialOverlayEntries, |
| ), |
| ), |
| ), |
| ); |
| } |
| } |