Commit ffe14b0d authored by Adam Barth's avatar Adam Barth Committed by GitHub

Navigator.push and Navigator.pushNamed should return Futures (#6333)

These futures complete when the route is popped off the navigator. This
generalizes and simplifies a mechanism already in place for dialogs and
menus.

Fixes #5283
parent cedbfd48
...@@ -296,18 +296,10 @@ class _ShrineHomeState extends State<ShrineHome> { ...@@ -296,18 +296,10 @@ class _ShrineHomeState extends State<ShrineHome> {
static final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>(); static final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
static final GridDelegate gridDelegate = new ShrineGridDelegate(); static final GridDelegate gridDelegate = new ShrineGridDelegate();
void handleCompletedOrder(Order completedOrder) { Future<Null> showOrderPage(Product product) async {
assert(completedOrder.product != null);
if (completedOrder.quantity == 0)
_shoppingCart.remove(completedOrder.product);
}
void showOrderPage(Product product) {
final Order order = _shoppingCart[product] ?? new Order(product: product); final Order order = _shoppingCart[product] ?? new Order(product: product);
final Completer<Order> completer = new Completer<Order>(); final Order completedOrder = await Navigator.push(context, new ShrineOrderRoute(
Navigator.push(context, new ShrineOrderRoute(
order: order, order: order,
completer: completer,
builder: (BuildContext context) { builder: (BuildContext context) {
return new OrderPage( return new OrderPage(
order: order, order: order,
...@@ -316,7 +308,9 @@ class _ShrineHomeState extends State<ShrineHome> { ...@@ -316,7 +308,9 @@ class _ShrineHomeState extends State<ShrineHome> {
); );
} }
)); ));
completer.future.then(handleCompletedOrder); assert(completedOrder.product != null);
if (completedOrder.quantity == 0)
_shoppingCart.remove(completedOrder.product);
} }
@override @override
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../shrine_demo.dart' show ShrinePageRoute; import '../shrine_demo.dart' show ShrinePageRoute;
...@@ -226,9 +224,8 @@ class ShrineOrderRoute extends ShrinePageRoute<Order> { ...@@ -226,9 +224,8 @@ class ShrineOrderRoute extends ShrinePageRoute<Order> {
ShrineOrderRoute({ ShrineOrderRoute({
this.order, this.order,
WidgetBuilder builder, WidgetBuilder builder,
Completer<Order> completer,
RouteSettings settings: const RouteSettings() RouteSettings settings: const RouteSettings()
}) : super(builder: builder, completer: completer, settings: settings) { }) : super(builder: builder, settings: settings) {
assert(order != null); assert(order != null);
} }
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'shrine/shrine_home.dart' show ShrineHome; import 'shrine/shrine_home.dart' show ShrineHome;
...@@ -26,9 +24,8 @@ Widget buildShrine(Widget child) { ...@@ -26,9 +24,8 @@ Widget buildShrine(Widget child) {
class ShrinePageRoute<T> extends MaterialPageRoute<T> { class ShrinePageRoute<T> extends MaterialPageRoute<T> {
ShrinePageRoute({ ShrinePageRoute({
WidgetBuilder builder, WidgetBuilder builder,
Completer<T> completer,
RouteSettings settings: const RouteSettings() RouteSettings settings: const RouteSettings()
}) : super(builder: builder, completer: completer, settings: settings); }) : super(builder: builder, settings: settings);
@override @override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) { Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
......
...@@ -204,10 +204,9 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { ...@@ -204,10 +204,9 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
class _ModalBottomSheetRoute<T> extends PopupRoute<T> { class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
_ModalBottomSheetRoute({ _ModalBottomSheetRoute({
Completer<T> completer,
this.builder, this.builder,
this.theme, this.theme,
}) : super(completer: completer); });
final WidgetBuilder builder; final WidgetBuilder builder;
final ThemeData theme; final ThemeData theme;
...@@ -260,11 +259,8 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> { ...@@ -260,11 +259,8 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
Future<dynamic/*=T*/> showModalBottomSheet/*<T>*/({ BuildContext context, WidgetBuilder builder }) { Future<dynamic/*=T*/> showModalBottomSheet/*<T>*/({ BuildContext context, WidgetBuilder builder }) {
assert(context != null); assert(context != null);
assert(builder != null); assert(builder != null);
final Completer<dynamic/*=T*/> completer = new Completer<dynamic/*=T*/>(); return Navigator.push(context, new _ModalBottomSheetRoute<dynamic/*=T*/>(
Navigator.push(context, new _ModalBottomSheetRoute<dynamic/*=T*/>(
completer: completer,
builder: builder, builder: builder,
theme: Theme.of(context, shadowThemeOnly: true), theme: Theme.of(context, shadowThemeOnly: true),
)); ));
return completer.future;
} }
...@@ -278,10 +278,9 @@ class SimpleDialog extends StatelessWidget { ...@@ -278,10 +278,9 @@ class SimpleDialog extends StatelessWidget {
class _DialogRoute<T> extends PopupRoute<T> { class _DialogRoute<T> extends PopupRoute<T> {
_DialogRoute({ _DialogRoute({
Completer<T> completer,
this.child, this.child,
this.theme, this.theme,
}) : super(completer: completer); });
final Widget child; final Widget child;
final ThemeData theme; final ThemeData theme;
...@@ -324,11 +323,8 @@ class _DialogRoute<T> extends PopupRoute<T> { ...@@ -324,11 +323,8 @@ class _DialogRoute<T> extends PopupRoute<T> {
/// * [Dialog] /// * [Dialog]
/// * <https://www.google.com/design/spec/components/dialogs.html> /// * <https://www.google.com/design/spec/components/dialogs.html>
Future<dynamic/*=T*/> showDialog/*<T>*/({ BuildContext context, Widget child }) { Future<dynamic/*=T*/> showDialog/*<T>*/({ BuildContext context, Widget child }) {
Completer<dynamic/*=T*/> completer = new Completer<dynamic/*=T*/>(); return Navigator.push(context, new _DialogRoute<dynamic/*=T*/>(
Navigator.push(context, new _DialogRoute<dynamic/*=T*/>(
completer: completer,
child: child, child: child,
theme: Theme.of(context, shadowThemeOnly: true), theme: Theme.of(context, shadowThemeOnly: true),
)); ));
return completer.future;
} }
...@@ -288,14 +288,13 @@ class _DropdownRouteResult<T> { ...@@ -288,14 +288,13 @@ class _DropdownRouteResult<T> {
class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> { class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
_DropdownRoute({ _DropdownRoute({
Completer<_DropdownRouteResult<T>> completer,
this.items, this.items,
this.buttonRect, this.buttonRect,
this.selectedIndex, this.selectedIndex,
this.elevation: 8, this.elevation: 8,
this.theme, this.theme,
TextStyle style, TextStyle style,
}) : _style = style, super(completer: completer) { }) : _style = style {
assert(style != null); assert(style != null);
} }
...@@ -502,7 +501,6 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> { ...@@ -502,7 +501,6 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> {
final Rect itemRect = itemBox.localToGlobal(Point.origin) & itemBox.size; final Rect itemRect = itemBox.localToGlobal(Point.origin) & itemBox.size;
final Completer<_DropdownRouteResult<T>> completer = new Completer<_DropdownRouteResult<T>>(); final Completer<_DropdownRouteResult<T>> completer = new Completer<_DropdownRouteResult<T>>();
_currentRoute = new _DropdownRoute<T>( _currentRoute = new _DropdownRoute<T>(
completer: completer,
items: config.items, items: config.items,
buttonRect: _kMenuHorizontalPadding.inflateRect(itemRect), buttonRect: _kMenuHorizontalPadding.inflateRect(itemRect),
selectedIndex: _selectedIndex, selectedIndex: _selectedIndex,
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'material.dart'; import 'material.dart';
import 'theme.dart'; import 'theme.dart';
...@@ -167,10 +165,9 @@ class MaterialPageRoute<T> extends PageRoute<T> { ...@@ -167,10 +165,9 @@ class MaterialPageRoute<T> extends PageRoute<T> {
/// Creates a page route for use in a material design app. /// Creates a page route for use in a material design app.
MaterialPageRoute({ MaterialPageRoute({
this.builder, this.builder,
Completer<T> completer,
RouteSettings settings: const RouteSettings(), RouteSettings settings: const RouteSettings(),
this.maintainState: true, this.maintainState: true,
}) : super(completer: completer, settings: settings) { }) : super(settings: settings) {
assert(builder != null); assert(builder != null);
assert(opaque); assert(opaque);
} }
......
...@@ -370,13 +370,12 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { ...@@ -370,13 +370,12 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
class _PopupMenuRoute<T> extends PopupRoute<T> { class _PopupMenuRoute<T> extends PopupRoute<T> {
_PopupMenuRoute({ _PopupMenuRoute({
Completer<T> completer,
this.position, this.position,
this.items, this.items,
this.initialValue, this.initialValue,
this.elevation, this.elevation,
this.theme this.theme
}) : super(completer: completer); });
final RelativeRect position; final RelativeRect position;
final List<PopupMenuEntry<T>> items; final List<PopupMenuEntry<T>> items;
...@@ -439,16 +438,13 @@ Future<dynamic/*=T*/> showMenu/*<T>*/({ ...@@ -439,16 +438,13 @@ Future<dynamic/*=T*/> showMenu/*<T>*/({
}) { }) {
assert(context != null); assert(context != null);
assert(items != null && items.length > 0); assert(items != null && items.length > 0);
Completer<dynamic/*=T*/> completer = new Completer<dynamic/*=T*/>(); return Navigator.push(context, new _PopupMenuRoute<dynamic/*=T*/>(
Navigator.push(context, new _PopupMenuRoute<dynamic/*=T*/>(
completer: completer,
position: position, position: position,
items: items, items: items,
initialValue: initialValue, initialValue: initialValue,
elevation: elevation, elevation: elevation,
theme: Theme.of(context, shadowThemeOnly: true), theme: Theme.of(context, shadowThemeOnly: true),
)); ));
return completer.future;
} }
/// A callback that is passed the value of the PopupMenuItem that caused /// A callback that is passed the value of the PopupMenuItem that caused
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
...@@ -32,6 +34,12 @@ abstract class Route<T> { ...@@ -32,6 +34,12 @@ abstract class Route<T> {
/// will initialize its [Focus] to this key. /// will initialize its [Focus] to this key.
GlobalKey get focusKey => null; GlobalKey get focusKey => null;
/// A future that completes when this route is popped off the navigator.
///
/// The future completes with the value given to [Navigator.pop], if any.
Future<T> get popped => _popCompleter.future;
final Completer<T> _popCompleter = new Completer<T>();
/// Called when the route is inserted into the navigator. /// Called when the route is inserted into the navigator.
/// ///
/// Use this to populate overlayEntries and add them to the overlay /// Use this to populate overlayEntries and add them to the overlay
...@@ -40,9 +48,13 @@ abstract class Route<T> { ...@@ -40,9 +48,13 @@ abstract class Route<T> {
/// responsible for _removing_ the entries and this way it's symmetric.) /// responsible for _removing_ the entries and this way it's symmetric.)
/// ///
/// The overlay argument will be null if this is the first route inserted. /// The overlay argument will be null if this is the first route inserted.
@protected
@mustCallSuper
void install(OverlayEntry insertionPoint) { } void install(OverlayEntry insertionPoint) { }
/// Called after install() when the route is pushed onto the navigator. /// Called after install() when the route is pushed onto the navigator.
@protected
@mustCallSuper
void didPush() { } void didPush() { }
/// When this route is popped (see [Navigator.pop]) if the result isn't /// When this route is popped (see [Navigator.pop]) if the result isn't
...@@ -50,6 +62,8 @@ abstract class Route<T> { ...@@ -50,6 +62,8 @@ abstract class Route<T> {
T get currentResult => null; T get currentResult => null;
/// Called after install() when the route replaced another in the navigator. /// Called after install() when the route replaced another in the navigator.
@protected
@mustCallSuper
void didReplace(Route<dynamic> oldRoute) { } void didReplace(Route<dynamic> oldRoute) { }
/// A request was made to pop this route. If the route can handle it /// A request was made to pop this route. If the route can handle it
...@@ -59,19 +73,28 @@ abstract class Route<T> { ...@@ -59,19 +73,28 @@ abstract class Route<T> {
/// ///
/// If this is called, the Navigator will not call dispose(). It is the /// If this is called, the Navigator will not call dispose(). It is the
/// responsibility of the Route to later call dispose(). /// responsibility of the Route to later call dispose().
bool didPop(T result) => true; @protected
@mustCallSuper
bool didPop(T result) {
_popCompleter.complete(result);
return true;
}
/// Whether calling didPop() would return false. /// Whether calling didPop() would return false.
bool get willHandlePopInternally => false; bool get willHandlePopInternally => false;
/// The given route, which came after this one, has been popped off the /// The given route, which came after this one, has been popped off the
/// navigator. /// navigator.
@protected
@mustCallSuper
void didPopNext(Route<dynamic> nextRoute) { } void didPopNext(Route<dynamic> nextRoute) { }
/// This route's next route has changed to the given new route. This is called /// 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 /// 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. /// cases when didPopNext() would be called, so long as it is in the history.
/// nextRoute will be null if there's no next route. /// nextRoute will be null if there's no next route.
@protected
@mustCallSuper
void didChangeNext(Route<dynamic> nextRoute) { } void didChangeNext(Route<dynamic> nextRoute) { }
/// The route should remove its overlays and free any other resources. /// The route should remove its overlays and free any other resources.
...@@ -79,6 +102,7 @@ abstract class Route<T> { ...@@ -79,6 +102,7 @@ abstract class Route<T> {
/// A call to didPop() implies that the Route should call dispose() itself, /// 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 /// but it is possible for dispose() to be called directly (e.g. if the route
/// is replaced, or if the navigator itself is disposed). /// is replaced, or if the navigator itself is disposed).
@mustCallSuper
void dispose() { } void dispose() { }
/// If the route's transition can be popped via a user gesture (e.g. the iOS /// If the route's transition can be popped via a user gesture (e.g. the iOS
...@@ -248,8 +272,10 @@ class Navigator extends StatefulWidget { ...@@ -248,8 +272,10 @@ class Navigator extends StatefulWidget {
/// ///
/// The route name will be passed to that navigator's [onGenerateRoute] /// The route name will be passed to that navigator's [onGenerateRoute]
/// callback. The returned route will be pushed into the navigator. /// callback. The returned route will be pushed into the navigator.
static void pushNamed(BuildContext context, String routeName) { ///
Navigator.of(context).pushNamed(routeName); /// Returns a Future that completes when the pushed route is popped.
static Future<dynamic> pushNamed(BuildContext context, String routeName) {
return Navigator.of(context).pushNamed(routeName);
} }
/// Push a route onto the navigator that most tightly encloses the given context. /// Push a route onto the navigator that most tightly encloses the given context.
...@@ -258,8 +284,10 @@ class Navigator extends StatefulWidget { ...@@ -258,8 +284,10 @@ class Navigator extends StatefulWidget {
/// The route will have didPush() and didChangeNext() called on it; the /// The route will have didPush() and didChangeNext() called on it; the
/// previous route, if any, will have didChangeNext() called on it; and the /// previous route, if any, will have didChangeNext() called on it; and the
/// Navigator observer, if any, will have didPush() called on it. /// Navigator observer, if any, will have didPush() called on it.
static void push(BuildContext context, Route<dynamic> route) { ///
Navigator.of(context).push(route); /// Returns a Future that completes when the pushed route is popped.
static Future<dynamic> push(BuildContext context, Route<dynamic> route) {
return Navigator.of(context).push(route);
} }
/// Pop a route off the navigator that most tightly encloses the given context. /// Pop a route off the navigator that most tightly encloses the given context.
...@@ -269,10 +297,10 @@ class Navigator extends StatefulWidget { ...@@ -269,10 +297,10 @@ class Navigator extends StatefulWidget {
/// (if any) is notified using its didPop() method, and the previous route is /// (if any) is notified using its didPop() method, and the previous route is
/// notified using [Route.didChangeNext]. /// notified using [Route.didChangeNext].
/// ///
/// If non-null, [result] will be used as the result of the route. Routes /// 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 /// 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 /// 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 /// type of `result`, if provided, must match the type argument of the class
/// of the current route. (In practice, this is usually "dynamic".) /// of the current route. (In practice, this is usually "dynamic".)
/// ///
/// Returns true if a route was popped; returns false if there are no further /// Returns true if a route was popped; returns false if there are no further
...@@ -299,11 +327,20 @@ class Navigator extends StatefulWidget { ...@@ -299,11 +327,20 @@ class Navigator extends StatefulWidget {
} }
/// Executes a simple transaction that both pops the current route off and /// 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. /// pushes a named route into the navigator that most tightly encloses the
static void popAndPushNamed(BuildContext context, String routeName) { /// given context.
Navigator.of(context) ///
..pop() /// If non-null, `result` will be used as the result of the route that is
..pushNamed(routeName); /// popped. 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 a Future that completes when the pushed route is popped.
static Future<dynamic> popAndPushNamed(BuildContext context, String routeName, { dynamic result }) {
NavigatorState navigator = Navigator.of(context);
navigator.pop(result);
return navigator.pushNamed(routeName);
} }
/// The state from the closest instance of this class that encloses the given context. /// The state from the closest instance of this class that encloses the given context.
...@@ -378,7 +415,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -378,7 +415,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// Looks up the route with the given name using [Navigator.onGenerateRoute], /// Looks up the route with the given name using [Navigator.onGenerateRoute],
/// and then [push]es that route. /// and then [push]es that route.
void pushNamed(String name) { ///
/// Returns a Future that completes when the pushed route is popped.
Future<dynamic> pushNamed(String name) {
assert(!_debugLocked); assert(!_debugLocked);
assert(name != null); assert(name != null);
RouteSettings settings = new RouteSettings(name: name); RouteSettings settings = new RouteSettings(name: name);
...@@ -388,7 +427,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -388,7 +427,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
route = config.onUnknownRoute(settings); route = config.onUnknownRoute(settings);
assert(route != null); assert(route != null);
} }
push(route); return push(route);
} }
/// Adds the given route to the navigator's history, and transitions to it. /// Adds the given route to the navigator's history, and transitions to it.
...@@ -400,7 +439,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -400,7 +439,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// ///
/// Ongoing gestures within the current route are canceled when a new route is /// Ongoing gestures within the current route are canceled when a new route is
/// pushed. /// pushed.
void push(Route<dynamic> route) { ///
/// Returns a Future that completes when the pushed route is popped.
Future<dynamic> push(Route<dynamic> route) {
assert(!_debugLocked); assert(!_debugLocked);
assert(() { _debugLocked = true; return true; }); assert(() { _debugLocked = true; return true; });
assert(route != null); assert(route != null);
...@@ -418,6 +459,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -418,6 +459,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
}); });
assert(() { _debugLocked = false; return true; }); assert(() { _debugLocked = false; return true; });
_cancelActivePointers(); _cancelActivePointers();
return route.popped;
} }
/// Replaces a route that is not currently visible with a new route. /// Replaces a route that is not currently visible with a new route.
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'basic.dart'; import 'basic.dart';
import 'navigator.dart'; import 'navigator.dart';
import 'overlay.dart'; import 'overlay.dart';
...@@ -13,9 +11,8 @@ import 'routes.dart'; ...@@ -13,9 +11,8 @@ import 'routes.dart';
abstract class PageRoute<T> extends ModalRoute<T> { abstract class PageRoute<T> extends ModalRoute<T> {
/// Creates a modal route that replaces the entire screen. /// Creates a modal route that replaces the entire screen.
PageRoute({ PageRoute({
Completer<T> completer,
RouteSettings settings: const RouteSettings() RouteSettings settings: const RouteSettings()
}) : super(completer: completer, settings: settings); }) : super(settings: settings);
@override @override
bool get opaque => true; bool get opaque => true;
......
...@@ -32,21 +32,23 @@ abstract class OverlayRoute<T> extends Route<T> { ...@@ -32,21 +32,23 @@ abstract class OverlayRoute<T> extends Route<T> {
assert(_overlayEntries.isEmpty); assert(_overlayEntries.isEmpty);
_overlayEntries.addAll(createOverlayEntries()); _overlayEntries.addAll(createOverlayEntries());
navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint); navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
super.install(insertionPoint);
} }
/// A request was made to pop this route. If the route can handle it /// Controls whether [didPop] calls [finished].
/// 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().
///
/// If this is called, the Navigator will not call dispose(). It is the
/// responsibility of the Route to later call dispose().
/// ///
/// Subclasses shouldn't call this if they want to delay the finished() call. /// If true, this route removes its overlay entries during [didPop].
/// Subclasses can override this getter if they want to delay the [finished]
/// call (for example to animate the route's exit before removing it from the
/// overlay).
@protected
bool get finishedWhenPopped => true;
@override @override
bool didPop(T result) { bool didPop(T result) {
finished(); if (finishedWhenPopped)
return true; finished();
return super.didPop(result);
} }
/// Clears out the overlay entries. /// Clears out the overlay entries.
...@@ -56,6 +58,7 @@ abstract class OverlayRoute<T> extends Route<T> { ...@@ -56,6 +58,7 @@ abstract class OverlayRoute<T> extends Route<T> {
/// overlay removal. /// overlay removal.
/// ///
/// Do not call this method outside of this context. /// Do not call this method outside of this context.
@protected
void finished() { void finished() {
for (OverlayEntry entry in _overlayEntries) for (OverlayEntry entry in _overlayEntries)
entry.remove(); entry.remove();
...@@ -65,6 +68,7 @@ abstract class OverlayRoute<T> extends Route<T> { ...@@ -65,6 +68,7 @@ abstract class OverlayRoute<T> extends Route<T> {
@override @override
void dispose() { void dispose() {
finished(); finished();
super.dispose();
} }
} }
...@@ -72,26 +76,20 @@ abstract class OverlayRoute<T> extends Route<T> { ...@@ -72,26 +76,20 @@ abstract class OverlayRoute<T> extends Route<T> {
abstract class TransitionRoute<T> extends OverlayRoute<T> { abstract class TransitionRoute<T> extends OverlayRoute<T> {
/// Creates a route with entrance and exit transitions. /// Creates a route with entrance and exit transitions.
TransitionRoute({ TransitionRoute({
Completer<T> popCompleter,
Completer<T> transitionCompleter Completer<T> transitionCompleter
}) : _popCompleter = popCompleter, }) : _transitionCompleter = transitionCompleter;
_transitionCompleter = transitionCompleter;
/// The same as the default constructor but callable with mixins. /// The same as the default constructor but callable with mixins.
TransitionRoute.explicit( TransitionRoute.explicit(
Completer<T> popCompleter,
Completer<T> transitionCompleter Completer<T> transitionCompleter
) : this(popCompleter: popCompleter, transitionCompleter: transitionCompleter); ) : this(transitionCompleter: transitionCompleter);
/// This future completes once the animation has been dismissed. For
/// ModalRoutes, this will be after the completer that's passed in, since that
/// one completes before the animation even starts, as soon as the route is
/// popped.
Future<T> get popped => _popCompleter?.future;
final Completer<T> _popCompleter;
/// This future completes only once the transition itself has finished, after /// This future completes only once the transition itself has finished, after
/// the overlay entries have been removed from the navigator's overlay. /// the overlay entries have been removed from the navigator's overlay.
///
/// This future completes once the animation has been dismissed. That will be
/// after [popped], because [popped] completes before the animation even
/// starts, as soon as the route is popped.
Future<T> get completed => _transitionCompleter?.future; Future<T> get completed => _transitionCompleter?.future;
final Completer<T> _transitionCompleter; final Completer<T> _transitionCompleter;
...@@ -104,6 +102,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -104,6 +102,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
/// the opaque route will not be built to save resources. /// the opaque route will not be built to save resources.
bool get opaque; bool get opaque;
@override
bool get finishedWhenPopped => false;
/// The animation that drives the route's transition and the previous route's /// The animation that drives the route's transition and the previous route's
/// forward transition. /// forward transition.
Animation<double> get animation => _animation; Animation<double> get animation => _animation;
...@@ -192,8 +193,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -192,8 +193,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
bool didPop(T result) { bool didPop(T result) {
_result = result; _result = result;
_controller.reverse(); _controller.reverse();
_popCompleter?.complete(_result); return super.didPop(result);
return true;
} }
@override @override
...@@ -455,9 +455,8 @@ class _ModalScopeState extends State<_ModalScope> { ...@@ -455,9 +455,8 @@ class _ModalScopeState extends State<_ModalScope> {
abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> { abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
/// Creates a route that blocks interaction with previous routes. /// Creates a route that blocks interaction with previous routes.
ModalRoute({ ModalRoute({
Completer<T> completer,
this.settings: const RouteSettings() this.settings: const RouteSettings()
}) : super.explicit(completer, null); }) : super.explicit(null);
// The API for general users of this class // The API for general users of this class
...@@ -678,9 +677,6 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -678,9 +677,6 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// A modal route that overlays a widget over the current route. /// A modal route that overlays a widget over the current route.
abstract class PopupRoute<T> extends ModalRoute<T> { abstract class PopupRoute<T> extends ModalRoute<T> {
/// Creates a modal route that overlays a widget over the current route.
PopupRoute({ Completer<T> completer }) : super(completer: completer);
@override @override
bool get opaque => false; bool get opaque => false;
......
...@@ -35,16 +35,19 @@ class TestRoute extends LocalHistoryRoute<String> { ...@@ -35,16 +35,19 @@ class TestRoute extends LocalHistoryRoute<String> {
_entries.add(entry); _entries.add(entry);
navigator.overlay?.insert(entry, above: insertionPoint); navigator.overlay?.insert(entry, above: insertionPoint);
routes.add(this); routes.add(this);
super.install(insertionPoint);
} }
@override @override
void didPush() { void didPush() {
log('didPush'); log('didPush');
super.didPush();
} }
@override @override
void didReplace(@checked TestRoute oldRoute) { void didReplace(@checked TestRoute oldRoute) {
log('didReplace ${oldRoute.name}'); log('didReplace ${oldRoute.name}');
super.didReplace(oldRoute);
} }
@override @override
...@@ -59,11 +62,13 @@ class TestRoute extends LocalHistoryRoute<String> { ...@@ -59,11 +62,13 @@ class TestRoute extends LocalHistoryRoute<String> {
@override @override
void didPopNext(@checked TestRoute nextRoute) { void didPopNext(@checked TestRoute nextRoute) {
log('didPopNext ${nextRoute.name}'); log('didPopNext ${nextRoute.name}');
super.didPopNext(nextRoute);
} }
@override @override
void didChangeNext(@checked TestRoute nextRoute) { void didChangeNext(@checked TestRoute nextRoute) {
log('didChangeNext ${nextRoute?.name}'); log('didChangeNext ${nextRoute?.name}');
super.didChangeNext(nextRoute);
} }
@override @override
...@@ -72,6 +77,7 @@ class TestRoute extends LocalHistoryRoute<String> { ...@@ -72,6 +77,7 @@ class TestRoute extends LocalHistoryRoute<String> {
_entries.forEach((OverlayEntry entry) { entry.remove(); }); _entries.forEach((OverlayEntry entry) { entry.remove(); });
_entries.clear(); _entries.clear();
routes.remove(this); routes.remove(this);
super.dispose();
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment