navigator.dart 22.5 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
import 'framework.dart';
Adam Barth's avatar
Adam Barth committed
6
import 'overlay.dart';
7

8 9 10 11 12 13
/// 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.
Hixie's avatar
Hixie committed
14
abstract class Route<T> {
15 16 17 18
  /// The navigator that the route is in, if any.
  NavigatorState get navigator => _navigator;
  NavigatorState _navigator;

19
  /// The overlay entries for this route.
20 21 22
  List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];

  /// Called when the route is inserted into the navigator.
23
  ///
Hixie's avatar
Hixie committed
24 25 26 27
  /// 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.)
28 29
  ///
  /// The overlay argument will be null if this is the first route inserted.
Hixie's avatar
Hixie committed
30
  void install(OverlayEntry insertionPoint) { }
31 32 33

  /// Called after install() when the route is pushed onto the navigator.
  void didPush() { }
Hixie's avatar
Hixie committed
34

35 36 37 38
  /// 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;

39
  /// Called after install() when the route replaced another in the navigator.
40
  void didReplace(Route<dynamic> oldRoute) { }
41

Hixie's avatar
Hixie committed
42 43 44 45
  /// 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. Returning false will prevent the
  /// default behavior of NavigatorState.pop().
46 47 48
  ///
  /// If this is called, the Navigator will not call dispose(). It is the
  /// responsibility of the Route to later call dispose().
Hixie's avatar
Hixie committed
49
  bool didPop(T result) => true;
50

Hixie's avatar
Hixie committed
51 52 53
  /// Whether calling didPop() would return false.
  bool get willHandlePopInternally => false;

54
  /// The given route, which came after this one, has been popped off the
55
  /// navigator.
56
  void didPopNext(Route<dynamic> nextRoute) { }
57

Hixie's avatar
Hixie committed
58 59 60 61
  /// 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, except for
  /// cases when didPopNext() would be called, so long as it is in the history.
  /// nextRoute will be null if there's no next route.
62
  void didChangeNext(Route<dynamic> nextRoute) { }
63

64 65 66 67 68 69 70 71 72 73 74 75 76 77
  /// The route should remove its overlays and free any other resources.
  ///
  /// A call to didPop() implies that the Route should call dispose() itself,
  /// but it is possible for dispose() to be called directly (e.g. if the route
  /// is replaced, or if the navigator itself is disposed).
  void dispose() { }

  /// Whether this route is the top-most route on the navigator.
  bool get isCurrent {
    if (_navigator == null)
      return false;
    assert(_navigator._history.contains(this));
    return _navigator._history.last == this;
  }
Adam Barth's avatar
Adam Barth committed
78 79
}

80
/// Data that might be useful in constructing a [Route].
81 82
class RouteSettings {
  const RouteSettings({
83 84 85 86 87
    this.name,
    this.mostValuableKeys,
    this.isInitialRoute: false
  });

88
  /// The name of the route (e.g., "/settings").
Adam Barth's avatar
Adam Barth committed
89
  final String name;
90 91 92 93 94 95 96

  /// The set of keys that are most relevant for constructoring [Hero]
  /// transitions. For example, if the current route contains a list of music
  /// albums and the user triggered this navigation by tapping one of the
  /// albums, the most valuable album cover is the one associated with the album
  /// the user tapped and is the one that should heroically transition when
  /// opening the details page for that album.
Adam Barth's avatar
Adam Barth committed
97
  final Set<Key> mostValuableKeys;
98 99 100 101

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

104
  @override
105 106 107 108 109 110 111 112 113
  String toString() {
    String result = '"$name"';
    if (mostValuableKeys != null && mostValuableKeys.isNotEmpty) {
      result += '; keys:';
      for (Key key in mostValuableKeys)
        result += ' $key';
    }
    return result;
  }
Adam Barth's avatar
Adam Barth committed
114 115
}

116
/// Creates a route for the given route settings.
117
typedef Route<dynamic> RouteFactory(RouteSettings settings);
118 119

/// A callback in during which you can perform a number of navigator operations (e.g., pop, push) that happen atomically.
Hixie's avatar
Hixie committed
120
typedef void NavigatorTransactionCallback(NavigatorTransaction transaction);
121

122
/// An interface for observing the behavior of a [Navigator].
123
class NavigatorObserver {
124
  /// The navigator that the observer is observing, if any.
125
  NavigatorState get navigator => _navigator;
126
  NavigatorState _navigator;
127 128

  /// The [Navigator] pushed the given route.
129
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) { }
130 131

  /// THe [Navigator] popped the given route.
132
  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) { }
133 134
}

135 136 137 138 139 140 141 142
/// 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 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.
143
class Navigator extends StatefulWidget {
144 145
  Navigator({
    Key key,
146
    this.initialRoute,
Adam Barth's avatar
Adam Barth committed
147
    this.onGenerateRoute,
148 149
    this.onUnknownRoute,
    this.observer
150
  }) : super(key: key) {
Adam Barth's avatar
Adam Barth committed
151
    assert(onGenerateRoute != null);
152 153
  }

154
  /// The name of the first route to show.
155
  final String initialRoute;
156 157

  /// Called to generate a route for a given [RouteSettings].
Adam Barth's avatar
Adam Barth committed
158
  final RouteFactory onGenerateRoute;
159 160 161 162 163 164 165 166 167

  /// 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
168
  final RouteFactory onUnknownRoute;
169 170

  /// An observer for this navigator.
171
  final NavigatorObserver observer;
Adam Barth's avatar
Adam Barth committed
172

173
  /// The default name for the initial route.
Adam Barth's avatar
Adam Barth committed
174
  static const String defaultRouteName = '/';
175

176 177 178 179 180 181 182
  /// Push a named route onto the navigator that most tightly encloses the given context.
  ///
  /// The route name will be passed to that navigator's [onGenerateRoute]
  /// callback. The returned route will be pushed into the navigator. The set of
  /// most valuable keys will be used to construct an appropriate [Hero] transition.
  ///
  /// Uses [openTransaction()]. Only one transaction will be executed per frame.
Hixie's avatar
Hixie committed
183 184 185 186 187 188
  static void pushNamed(BuildContext context, String routeName, { Set<Key> mostValuableKeys }) {
    openTransaction(context, (NavigatorTransaction transaction) {
      transaction.pushNamed(routeName, mostValuableKeys: mostValuableKeys);
    });
  }

189 190 191 192 193 194 195 196
  /// Push a route onto the navigator that most tightly encloses the given context.
  ///
  /// Adds the given route to the Navigator's history, and transitions to it.
  /// The route will have didPush() and didChangeNext() called on it; the
  /// previous route, if any, will have didChangeNext() called on it; and the
  /// Navigator observer, if any, will have didPush() called on it.
  ///
  /// Uses [openTransaction()]. Only one transaction will be executed per frame.
197
  static void push(BuildContext context, Route<dynamic> route) {
Hixie's avatar
Hixie committed
198
    openTransaction(context, (NavigatorTransaction transaction) {
199
      transaction.push(route);
Hixie's avatar
Hixie committed
200 201 202
    });
  }

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
  /// Pop a route off the navigator that most tightly encloses the given context.
  ///
  /// Tries to removes the current route, calling its didPop() method. If that
  /// method returns false, then nothing else happens. Otherwise, the observer
  /// (if any) is notified using its didPop() method, and the previous route is
  /// notified using [Route.didChangeNext].
  ///
  /// If non-null, [result] will be used as the result of the route. 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 current route. (In practice, this is usually "dynamic".)
  ///
  /// Returns true if a route was popped; returns false if there are no further
  /// previous routes.
  ///
  /// Uses [openTransaction()]. Only one transaction will be executed per frame.
Hixie's avatar
Hixie committed
220 221 222 223 224 225 226
  static bool pop(BuildContext context, [ dynamic result ]) {
    bool returnValue;
    openTransaction(context, (NavigatorTransaction transaction) {
      returnValue = transaction.pop(result);
    });
    return returnValue;
  }
227

228 229 230 231
  /// Calls pop() repeatedly until the given route is the current route.
  /// If it is already the current route, nothing happens.
  ///
  /// Uses [openTransaction()]. Only one transaction will be executed per frame.
232
  static void popUntil(BuildContext context, Route<dynamic> targetRoute) {
Hixie's avatar
Hixie committed
233 234 235 236
    openTransaction(context, (NavigatorTransaction transaction) {
      transaction.popUntil(targetRoute);
    });
  }
Hixie's avatar
Hixie committed
237

238 239 240 241 242
  /// Whether the navigator that most tightly encloses the given context can be popped.
  ///
  /// 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.
Hixie's avatar
Hixie committed
243
  static bool canPop(BuildContext context) {
Ian Hickson's avatar
Ian Hickson committed
244
    NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
245
    return navigator != null && navigator.canPop();
Hixie's avatar
Hixie committed
246 247
  }

248 249 250 251
  /// Executes a simple transaction that both pops the current route off and
  /// pushes a named route into the navigator that most tightly encloses the given context.
  ///
  /// Uses [openTransaction()]. Only one transaction will be executed per frame.
Hixie's avatar
Hixie committed
252 253 254 255 256 257 258
  static void popAndPushNamed(BuildContext context, String routeName, { Set<Key> mostValuableKeys }) {
    openTransaction(context, (NavigatorTransaction transaction) {
      transaction.pop();
      transaction.pushNamed(routeName, mostValuableKeys: mostValuableKeys);
    });
  }

259 260 261 262 263 264
  /// Calls callback immediately to create a navigator transaction.
  ///
  /// To avoid race conditions, a navigator will execute at most one operation
  /// per animation frame. If you wish to perform a compound change to the
  /// navigator's state, you can use a navigator transaction to execute all the
  /// changes atomically by making the changes inside the given callback.
Hixie's avatar
Hixie committed
265
  static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
Ian Hickson's avatar
Ian Hickson committed
266
    NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
267
    assert(() {
268
      if (navigator == null) {
269
        throw new FlutterError(
270 271 272 273
          'openTransaction called with a context that does not include a Navigator.\n'
          'The context passed to the Navigator.openTransaction() method must be that of a widget that is a descendant of a Navigator widget.'
        );
      }
274 275
      return true;
    });
Hixie's avatar
Hixie committed
276 277
    navigator.openTransaction(callback);
  }
Adam Barth's avatar
Adam Barth committed
278

279
  @override
280
  NavigatorState createState() => new NavigatorState();
281 282
}

283
/// The state for a [Navigator] widget.
284
class NavigatorState extends State<Navigator> {
Adam Barth's avatar
Adam Barth committed
285
  final GlobalKey<OverlayState> _overlayKey = new GlobalKey<OverlayState>();
286
  final List<Route<dynamic>> _history = new List<Route<dynamic>>();
287

288
  @override
289 290
  void initState() {
    super.initState();
291 292
    assert(config.observer == null || config.observer.navigator == null);
    config.observer?._navigator = this;
293
    _push(config.onGenerateRoute(new RouteSettings(
294 295
      name: config.initialRoute ?? Navigator.defaultRouteName,
      isInitialRoute: true
296
    )));
297 298
  }

299
  @override
300 301 302 303 304 305 306 307
  void didUpdateConfig(Navigator oldConfig) {
    if (oldConfig.observer != config.observer) {
      oldConfig.observer?._navigator = null;
      assert(config.observer == null || config.observer.navigator == null);
      config.observer?._navigator = this;
    }
  }

308
  @override
309
  void dispose() {
310 311
    assert(!_debugLocked);
    assert(() { _debugLocked = true; return true; });
312
    config.observer?._navigator = null;
313
    for (Route<dynamic> route in _history) {
314 315 316
      route.dispose();
      route._navigator = null;
    }
317
    super.dispose();
318
    assert(() { _debugLocked = false; return true; });
319 320
  }

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

Hixie's avatar
Hixie committed
324
  OverlayEntry get _currentOverlayEntry {
325
    for (Route<dynamic> route in _history.reversed) {
Adam Barth's avatar
Adam Barth committed
326 327
      if (route.overlayEntries.isNotEmpty)
        return route.overlayEntries.last;
328
    }
Adam Barth's avatar
Adam Barth committed
329
    return null;
330 331
  }

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

Hixie's avatar
Hixie committed
334
  void _pushNamed(String name, { Set<Key> mostValuableKeys }) {
335
    assert(!_debugLocked);
336
    assert(name != null);
337
    RouteSettings settings = new RouteSettings(
Adam Barth's avatar
Adam Barth committed
338 339
      name: name,
      mostValuableKeys: mostValuableKeys
Hixie's avatar
Hixie committed
340
    );
341
    Route<dynamic> route = config.onGenerateRoute(settings);
342 343 344 345 346 347
    if (route == null) {
      assert(config.onUnknownRoute != null);
      route = config.onUnknownRoute(settings);
      assert(route != null);
    }
    _push(route);
348 349
  }

350
  void _push(Route<dynamic> route) {
351 352 353 354
    assert(!_debugLocked);
    assert(() { _debugLocked = true; return true; });
    assert(route != null);
    assert(route._navigator == null);
Hixie's avatar
Hixie committed
355
    setState(() {
356
      Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
357
      route._navigator = this;
Hixie's avatar
Hixie committed
358
      route.install(_currentOverlayEntry);
359
      _history.add(route);
360
      route.didPush();
Hixie's avatar
Hixie committed
361
      route.didChangeNext(null);
362
      if (oldRoute != null)
Hixie's avatar
Hixie committed
363
        oldRoute.didChangeNext(route);
364
      config.observer?.didPush(route, oldRoute);
Hixie's avatar
Hixie committed
365
    });
366
    assert(() { _debugLocked = false; return true; });
Hixie's avatar
Hixie committed
367 368
  }

369
  void _replace({ Route<dynamic> oldRoute, Route<dynamic> newRoute }) {
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
    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));
    setState(() {
      int index = _history.indexOf(oldRoute);
      assert(index >= 0);
      newRoute._navigator = this;
Hixie's avatar
Hixie committed
385
      newRoute.install(oldRoute.overlayEntries.last);
386 387
      _history[index] = newRoute;
      newRoute.didReplace(oldRoute);
Hixie's avatar
Hixie committed
388 389 390 391
      if (index + 1 < _history.length)
        newRoute.didChangeNext(_history[index + 1]);
      else
        newRoute.didChangeNext(null);
392
      if (index > 0)
Hixie's avatar
Hixie committed
393
        _history[index - 1].didChangeNext(newRoute);
394 395 396 397 398 399
      oldRoute.dispose();
      oldRoute._navigator = null;
    });
    assert(() { _debugLocked = false; return true; });
  }

400
  void _replaceRouteBefore({ Route<dynamic> anchorRoute, Route<dynamic> newRoute }) {
401 402 403
    assert(anchorRoute != null);
    assert(anchorRoute._navigator == this);
    assert(_history.indexOf(anchorRoute) > 0);
Hixie's avatar
Hixie committed
404
    _replace(oldRoute: _history[_history.indexOf(anchorRoute)-1], newRoute: newRoute);
405
  }
406

407
  void _removeRouteBefore(Route<dynamic> anchorRoute) {
408 409 410 411 412
    assert(!_debugLocked);
    assert(() { _debugLocked = true; return true; });
    assert(anchorRoute._navigator == this);
    int index = _history.indexOf(anchorRoute) - 1;
    assert(index >= 0);
413
    Route<dynamic> targetRoute = _history[index];
414 415 416 417
    assert(targetRoute._navigator == this);
    assert(targetRoute.overlayEntries.isEmpty || !overlay.debugIsVisible(targetRoute.overlayEntries.last));
    setState(() {
      _history.removeAt(index);
418
      Route<dynamic> newRoute = index < _history.length ? _history[index] : null;
Hixie's avatar
Hixie committed
419 420
      if (index > 0)
        _history[index - 1].didChangeNext(newRoute);
421 422 423 424 425 426
      targetRoute.dispose();
      targetRoute._navigator = null;
    });
    assert(() { _debugLocked = false; return true; });
  }

Hixie's avatar
Hixie committed
427
  bool _pop([dynamic result]) {
428 429
    assert(!_debugLocked);
    assert(() { _debugLocked = true; return true; });
430
    Route<dynamic> route = _history.last;
Hixie's avatar
Hixie committed
431
    assert(route._navigator == this);
Hixie's avatar
Hixie committed
432 433
    bool debugPredictedWouldPop;
    assert(() { debugPredictedWouldPop = !route.willHandlePopInternally; return true; });
434
    if (route.didPop(result ?? route.currentResult)) {
Hixie's avatar
Hixie committed
435
      assert(debugPredictedWouldPop);
Hixie's avatar
Hixie committed
436 437
      if (_history.length > 1) {
        setState(() {
438 439 440
          // We use setState to guarantee that we'll rebuild, since the routes
          // can't do that for themselves, even if they have changed their own
          // state (e.g. ModalScope.isCurrent).
Hixie's avatar
Hixie committed
441
          _history.removeLast();
442 443
          _history.last.didPopNext(route);
          config.observer?.didPop(route, _history.last);
Hixie's avatar
Hixie committed
444 445 446 447 448 449
          route._navigator = null;
        });
      } else {
        assert(() { _debugLocked = false; return true; });
        return false;
      }
Hixie's avatar
Hixie committed
450 451
    } else {
      assert(!debugPredictedWouldPop);
Hixie's avatar
Hixie committed
452
    }
453
    assert(() { _debugLocked = false; return true; });
Hixie's avatar
Hixie committed
454
    return true;
Hixie's avatar
Hixie committed
455 456
  }

457
  void _popUntil(Route<dynamic> targetRoute) {
458 459
    assert(_history.contains(targetRoute));
    while (!targetRoute.isCurrent)
Hixie's avatar
Hixie committed
460 461 462
      _pop();
  }

463 464 465 466
  /// Whether this navigator can be popped.
  ///
  /// The only route that cannot be popped off the navigator is the initial
  /// route.
Hixie's avatar
Hixie committed
467 468 469 470 471
  bool canPop() {
    assert(_history.length > 0);
    return _history.length > 1 || _history[0].willHandlePopInternally;
  }

Hixie's avatar
Hixie committed
472 473
  bool _hadTransaction = true;

474 475 476 477 478 479
  /// Calls callback immediately to create a navigator transaction.
  ///
  /// To avoid race conditions, a navigator will execute at most one operation
  /// per animation frame. If you wish to perform a compound change to the
  /// navigator's state, you can use a navigator transaction to execute all the
  /// changes atomically by making the changes inside the given callback.
Hixie's avatar
Hixie committed
480 481 482 483 484 485 486 487 488 489 490
  bool openTransaction(NavigatorTransactionCallback callback) {
    assert(callback != null);
    if (_hadTransaction)
      return false;
    _hadTransaction = true;
    NavigatorTransaction transaction = new NavigatorTransaction._(this);
    setState(() {
      callback(transaction);
    });
    assert(() { transaction._debugClose(); return true; });
    return true;
491 492
  }

493
  @override
Adam Barth's avatar
Adam Barth committed
494
  Widget build(BuildContext context) {
495
    assert(!_debugLocked);
496
    assert(_history.isNotEmpty);
Hixie's avatar
Hixie committed
497
    _hadTransaction = false;
Adam Barth's avatar
Adam Barth committed
498 499
    return new Overlay(
      key: _overlayKey,
500
      initialEntries: _history.first.overlayEntries
501 502 503
    );
  }
}
Hixie's avatar
Hixie committed
504

505
/// A sequence of [Navigator] operations that are executed atomically.
Hixie's avatar
Hixie committed
506 507 508 509 510 511
class NavigatorTransaction {
  NavigatorTransaction._(this._navigator) {
    assert(_navigator != null);
  }
  NavigatorState _navigator;
  bool _debugOpen = true;
512

513 514 515
  /// The route name will be passed to the navigator's [onGenerateRoute]
  /// callback. The returned route will be pushed into the navigator. The set of
  /// most valuable keys will be used to construct an appropriate [Hero] transition.
Hixie's avatar
Hixie committed
516 517 518 519 520 521
  void pushNamed(String name, { Set<Key> mostValuableKeys }) {
    assert(_debugOpen);
    _navigator._pushNamed(name, mostValuableKeys: mostValuableKeys);
  }

  /// Adds the given route to the Navigator's history, and transitions to it.
Hixie's avatar
Hixie committed
522 523 524
  /// The route will have didPush() and didChangeNext() called on it; the
  /// previous route, if any, will have didChangeNext() called on it; and the
  /// Navigator observer, if any, will have didPush() called on it.
525
  void push(Route<dynamic> route) {
Hixie's avatar
Hixie committed
526
    assert(_debugOpen);
527
    _navigator._push(route);
Hixie's avatar
Hixie committed
528 529
  }

Hixie's avatar
Hixie committed
530 531
  /// Replaces one given route with another. Calls install(), didReplace(), and
  /// didChangeNext() on the new route, then dispose() on the old route. The
Hixie's avatar
Hixie committed
532 533 534 535 536 537 538 539 540
  /// navigator is not informed of the replacement.
  ///
  /// The old route must have overlay entries, otherwise we won't know where to
  /// insert the entries of the new route. The old route must not be currently
  /// visible (i.e. a later route have overlay entries that are currently
  /// opaque), otherwise the replacement would have a jarring effect.
  ///
  /// It is safe to call this redundantly (replacing a route with itself). Such
  /// calls are ignored.
541
  void replace({ Route<dynamic> oldRoute, Route<dynamic> newRoute }) {
Hixie's avatar
Hixie committed
542 543 544 545 546 547 548 549 550 551 552 553
    assert(_debugOpen);
    _navigator._replace(oldRoute: oldRoute, newRoute: newRoute);
  }

  /// Like replace(), but affects the route before the given anchorRoute rather
  /// than the anchorRoute itself.
  ///
  /// If newRoute is already the route before anchorRoute, then the call is
  /// ignored.
  ///
  /// The conditions described for [replace()] apply; for instance, the route
  /// before anchorRoute must have overlay entries.
554
  void replaceRouteBefore({ Route<dynamic> anchorRoute, Route<dynamic> newRoute }) {
Hixie's avatar
Hixie committed
555 556 557 558
    assert(_debugOpen);
    _navigator._replaceRouteBefore(anchorRoute: anchorRoute, newRoute: newRoute);
  }

Hixie's avatar
Hixie committed
559 560
  /// Removes the route prior to the given anchorRoute, and calls didChangeNext
  /// on the route prior to that one, if any. The observer is not notified.
561
  void removeRouteBefore(Route<dynamic> anchorRoute) {
Hixie's avatar
Hixie committed
562 563 564 565 566 567 568
    assert(_debugOpen);
    _navigator._removeRouteBefore(anchorRoute);
  }

  /// Tries to removes the current route, calling its didPop() method. If that
  /// method returns false, then nothing else happens. Otherwise, the observer
  /// (if any) is notified using its didPop() method, and the previous route is
Hixie's avatar
Hixie committed
569
  /// notified using [Route.didChangeNext].
Hixie's avatar
Hixie committed
570
  ///
571 572 573 574 575 576
  /// If non-null, [result] will be used as the result of the route, otherwise
  /// the route's [Route.currentValue] will be used. 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 current
  /// route. (In practice, this is usually "dynamic".)
Hixie's avatar
Hixie committed
577 578 579 580 581 582 583
  ///
  /// Returns true if a route was popped; returns false if there are no further
  /// previous routes.
  bool pop([dynamic result]) {
    assert(_debugOpen);
    return _navigator._pop(result);
  }
584

Hixie's avatar
Hixie committed
585 586
  /// Calls pop() repeatedly until the given route is the current route.
  /// If it is already the current route, nothing happens.
587
  void popUntil(Route<dynamic> targetRoute) {
Hixie's avatar
Hixie committed
588 589 590 591 592 593 594 595
    assert(_debugOpen);
    _navigator._popUntil(targetRoute);
  }

  void _debugClose() {
    assert(_debugOpen);
    _debugOpen = false;
  }
596
}