NavigatorTransaction

To make it easier to avoid pushing twice in one frame, provide a
transaction mechanism for the navigator.
This commit is contained in:
Hixie 2015-11-30 13:41:31 -08:00
parent 97cca4d47b
commit f9ea1ce815
29 changed files with 263 additions and 156 deletions

View file

@ -60,7 +60,7 @@ class FeedFragmentState extends State<FeedFragment> {
setState(() {
_fitnessMode = value;
});
Navigator.of(context).pop();
Navigator.pop(context);
}
void _showDrawer() {
@ -91,8 +91,7 @@ class FeedFragmentState extends State<FeedFragment> {
}
void _handleShowSettings() {
Navigator.of(context)..pop()
..pushNamed('/settings');
Navigator.popAndPushNamed(context, '/settings');
}
// TODO(jackson): We should be localizing
@ -190,7 +189,7 @@ class FeedFragmentState extends State<FeedFragment> {
void _handleActionButtonPressed() {
showDialog(context: context, child: new AddItemDialog()).then((routeName) {
if (routeName != null)
Navigator.of(context).pushNamed(routeName);
Navigator.pushNamed(context, routeName);
});
}
@ -249,13 +248,13 @@ class AddItemDialogState extends State<AddItemDialog> {
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
Navigator.pop(context);
}
),
new FlatButton(
child: new Text('ADD'),
onPressed: () {
Navigator.of(context).pop(_addItemRoute);
Navigator.pop(context, _addItemRoute);
}
),
]

View file

@ -55,14 +55,15 @@ class MealFragmentState extends State<MealFragment> {
void _handleSave() {
config.onCreated(new Meal(when: new DateTime.now(), description: _description));
Navigator.of(context).pop();
Navigator.pop(context);
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/close",
onPressed: Navigator.of(context).pop),
onPressed: () => Navigator.pop(context)
),
center: new Text('New Meal'),
right: <Widget>[
// TODO(abarth): Should this be a FlatButton?

View file

@ -77,14 +77,15 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
));
}
config.onCreated(new Measurement(when: _when, weight: parsedWeight));
Navigator.of(context).pop();
Navigator.pop(context);
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/close",
onPressed: Navigator.of(context).pop),
onPressed: () => Navigator.pop(context)
),
center: new Text('New Measurement'),
right: <Widget>[
// TODO(abarth): Should this be a FlatButton?

View file

@ -28,7 +28,7 @@ class SettingsFragmentState extends State<SettingsFragment> {
return new ToolBar(
left: new IconButton(
icon: "navigation/arrow_back",
onPressed: () => Navigator.of(context).pop()
onPressed: () => Navigator.pop(context)
),
center: new Text('Settings')
);
@ -47,7 +47,7 @@ class SettingsFragmentState extends State<SettingsFragment> {
void _handleGoalWeightChanged(String goalWeight) {
// TODO(jackson): Looking for null characters to detect enter key is a hack
if (goalWeight.endsWith("\u{0}")) {
Navigator.of(context).pop(double.parse(goalWeight.replaceAll("\u{0}", "")));
Navigator.pop(context, double.parse(goalWeight.replaceAll("\u{0}", "")));
} else {
setState(() {
try {
@ -74,13 +74,13 @@ class SettingsFragmentState extends State<SettingsFragment> {
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
Navigator.pop(context);
}
),
new FlatButton(
child: new Text('SAVE'),
onPressed: () {
Navigator.of(context).pop(_goalWeight);
Navigator.pop(context, _goalWeight);
}
),
]

View file

@ -21,7 +21,7 @@ class GalleryPage extends StatelessComponent {
for (WidgetDemo demo in demos) {
items.add(new DrawerItem(
onPressed: () {
Navigator.of(context).pushNamed(demo.routeName);
Navigator.pushNamed(context, demo.routeName);
},
child: new Text(demo.title)
));

View file

@ -41,7 +41,7 @@ class StockHomeState extends State<StockHome> {
}
void _handleSearchEnd() {
Navigator.of(context).pop();
Navigator.pop(context);
}
void _handleSearchQueryChanged(String query) {
@ -92,13 +92,13 @@ class StockHomeState extends State<StockHome> {
new FlatButton(
child: new Text('USE IT'),
onPressed: () {
Navigator.of(context).pop(false);
Navigator.pop(context, false);
}
),
new FlatButton(
child: new Text('OH WELL'),
onPressed: () {
Navigator.of(context).pop(false);
Navigator.pop(context, false);
}
),
]
@ -142,8 +142,7 @@ class StockHomeState extends State<StockHome> {
}
void _handleShowSettings() {
Navigator.of(context)..pop()
..pushNamed('/settings');
Navigator.popAndPushNamed(context, '/settings');
}
Widget buildToolBar() {
@ -207,7 +206,7 @@ class StockHomeState extends State<StockHome> {
onOpen: (Stock stock, Key arrowKey) {
Set<Key> mostValuableKeys = new Set<Key>();
mostValuableKeys.add(arrowKey);
Navigator.of(context).pushNamed('/stock/${stock.symbol}', mostValuableKeys: mostValuableKeys);
Navigator.pushNamed(context, '/stock/${stock.symbol}', mostValuableKeys: mostValuableKeys);
},
onShow: (Stock stock, Key arrowKey) {
scaffoldKey.currentState.showBottomSheet((BuildContext context) => new StockSymbolBottomSheet(stock: stock));

View file

@ -23,10 +23,9 @@ Future showStockMenu({BuildContext context, bool autorefresh, ValueChanged<bool>
new Checkbox(
value: autorefresh,
onChanged: (bool value) {
Navigator.of(context).setState(() {
autorefresh = value;
});
Navigator.of(context).pop(_MenuItems.autorefreshCheckbox);
// TODO(ianh): https://github.com/flutter/flutter/issues/187
autorefresh = value;
Navigator.pop(context, _MenuItems.autorefreshCheckbox);
}
)
]
@ -43,9 +42,8 @@ Future showStockMenu({BuildContext context, bool autorefresh, ValueChanged<bool>
]
)) {
case _MenuItems.autorefresh:
Navigator.of(context).setState(() {
autorefresh = !autorefresh;
});
// TODO(ianh): https://github.com/flutter/flutter/issues/187
autorefresh = !autorefresh;
continue autorefreshNotify;
autorefreshNotify:
case _MenuItems.autorefreshCheckbox:
@ -75,7 +73,7 @@ Future showStockMenu({BuildContext context, bool autorefresh, ValueChanged<bool>
new FlatButton(
child: new Text('OH WELL'),
onPressed: () {
Navigator.of(context).pop(false);
Navigator.pop(context, false);
}
),
]

View file

@ -44,13 +44,13 @@ class StockSettingsState extends State<StockSettings> {
new FlatButton(
child: new Text('NO THANKS'),
onPressed: () {
Navigator.of(context).pop(false);
Navigator.pop(context, false);
}
),
new FlatButton(
child: new Text('AGREE'),
onPressed: () {
Navigator.of(context).pop(true);
Navigator.pop(context, true);
}
),
]
@ -72,7 +72,7 @@ class StockSettingsState extends State<StockSettings> {
return new ToolBar(
left: new IconButton(
icon: 'navigation/arrow_back',
onPressed: () => Navigator.of(context).pop()
onPressed: () => Navigator.pop(context)
),
center: new Text('Settings')
);

View file

@ -58,7 +58,7 @@ class StockSymbolPage extends StatelessComponent {
left: new IconButton(
icon: 'navigation/arrow_back',
onPressed: () {
Navigator.of(context).pop();
Navigator.pop(context);
}
),
center: new Text(stock.name)

View file

@ -15,11 +15,11 @@ class Home extends StatelessComponent {
),
new RaisedButton(
child: new Text('GO SHOPPING'),
onPressed: () => Navigator.of(context).pushNamed('/shopping')
onPressed: () => Navigator.pushNamed(context, '/shopping')
),
new RaisedButton(
child: new Text('START ADVENTURE'),
onPressed: () => Navigator.of(context).pushNamed('/adventure')
onPressed: () => Navigator.pushNamed(context, '/adventure')
)
],
padding: const EdgeDims.all(30.0)
@ -41,11 +41,11 @@ class Shopping extends StatelessComponent {
),
new RaisedButton(
child: new Text('RETURN HOME'),
onPressed: () => Navigator.of(context).pop()
onPressed: () => Navigator.pop(context)
),
new RaisedButton(
child: new Text('GO TO DUNGEON'),
onPressed: () => Navigator.of(context).pushNamed('/adventure')
onPressed: () => Navigator.pushNamed(context, '/adventure')
)
],
padding: const EdgeDims.all(30.0)
@ -67,7 +67,7 @@ class Adventure extends StatelessComponent {
),
new RaisedButton(
child: new Text('RUN!!!'),
onPressed: () => Navigator.of(context).pop()
onPressed: () => Navigator.pop(context)
)
],
padding: const EdgeDims.all(30.0)

View file

@ -119,7 +119,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () { Navigator.of(context).pop(); },
onTap: () => Navigator.pop(context),
child: new BuilderTransition(
performance: config.route.performance,
variables: <AnimatedValue<double>>[_layout.childTop],
@ -130,7 +130,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
token: _layout.childTop.value,
child: new BottomSheet(
performance: config.route.performance,
onClosing: () { Navigator.of(context).pop(); },
onClosing: () => Navigator.pop(context),
childHeight: _layout.childTop.end,
builder: config.route.builder
)
@ -167,7 +167,7 @@ Future showModalBottomSheet({ BuildContext context, WidgetBuilder builder }) {
assert(context != null);
assert(builder != null);
final Completer completer = new Completer();
Navigator.of(context).push(new _ModalBottomSheetRoute(
Navigator.push(context, new _ModalBottomSheetRoute(
completer: completer,
builder: builder
));

View file

@ -40,11 +40,11 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
}
void _handleCancel() {
Navigator.of(context).pop();
Navigator.pop(context);
}
void _handleOk() {
Navigator.of(context).pop(_selectedDate);
Navigator.pop(context, _selectedDate);
}
Widget build(BuildContext context) {

View file

@ -140,6 +140,6 @@ class _DialogRoute<T> extends PopupRoute<T> {
Future showDialog({ BuildContext context, Widget child }) {
Completer completer = new Completer();
Navigator.of(context).push(new _DialogRoute(completer: completer, child: child));
Navigator.push(context, new _DialogRoute(completer: completer, child: child));
return completer.future;
}

View file

@ -71,7 +71,7 @@ class _DrawerRoute extends OverlayRoute {
_state = _DrawerState.closed;
switch (previousState) {
case _DrawerState.showing:
Navigator.of(context).pop();
Navigator.pop(context);
break;
case _DrawerState.popped:
finished();
@ -220,5 +220,5 @@ class _DrawerControllerState extends State<_DrawerController> {
}
void showDrawer({ BuildContext context, Widget child, int elevation: 16 }) {
Navigator.of(context).push(new _DrawerRoute(child: child, elevation: elevation));
Navigator.push(context, new _DrawerRoute(child: child, elevation: elevation));
}

View file

@ -93,9 +93,7 @@ class _DropDownMenu<T> extends StatusTransitionComponent {
padding: _kMenuHorizontalPadding,
child: route.items[itemIndex]
),
onTap: () {
Navigator.of(context).pop(route.items[itemIndex].value);
}
onTap: () => Navigator.pop(context, route.items[itemIndex].value)
)
));
}
@ -117,7 +115,7 @@ class _DropDownMenu<T> extends StatusTransitionComponent {
reverseCurve: const Interval(0.0, 0.001)
);
final RenderBox renderBox = Navigator.of(context).context.findRenderObject();
final RenderBox renderBox = route.navigator.context.findRenderObject();
final Size navigatorSize = renderBox.size;
final RelativeRect menuRect = new RelativeRect.fromSize(route.rect, navigatorSize);
@ -216,7 +214,7 @@ class DropDownButton<T> extends StatelessComponent {
final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject();
final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size;
final Completer completer = new Completer<T>();
Navigator.of(context).push(new _DropDownRoute<T>(
Navigator.push(context, new _DropDownRoute<T>(
completer: completer,
items: items,
selectedIndex: selectedIndex,

View file

@ -81,8 +81,10 @@ class _MaterialAppState extends State<MaterialApp> implements BindingObserver {
assert(mounted);
NavigatorState navigator = _navigator.currentState;
assert(navigator != null);
if (!navigator.pop())
activity.finishCurrentActivity();
navigator.openTransaction((NavigatorTransaction transaction) {
if (!transaction.pop())
activity.finishCurrentActivity();
});
return true;
}

View file

@ -38,7 +38,7 @@ class _PopupMenu<T> extends StatelessComponent {
performance: route.performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end)),
child: new InkWell(
onTap: () { Navigator.of(context).pop(route.items[i].value); },
onTap: () => Navigator.pop(context, route.items[i].value),
child: route.items[i]
))
);
@ -114,7 +114,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int elevation: 8 }) {
Completer completer = new Completer();
Navigator.of(context).push(new _PopupMenuRoute(
Navigator.push(context, new _PopupMenuRoute(
completer: completer,
position: position,
items: items,

View file

@ -36,11 +36,11 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
}
void _handleCancel() {
Navigator.of(context).pop();
Navigator.pop(context);
}
void _handleOk() {
Navigator.of(context).pop(_selectedTime);
Navigator.pop(context, _selectedTime);
}
Widget build(BuildContext context) {

View file

@ -11,7 +11,6 @@ import 'package:flutter/services.dart';
import 'basic.dart';
import 'binding.dart';
import 'framework.dart';
import 'navigator.dart';
import 'overlay.dart';
typedef bool DragTargetWillAccept<T>(T data);
@ -165,7 +164,7 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
new _DragAvatar<T>(
pointer: pointer,
router: router,
overlay: Navigator.of(context).overlay,
overlay: Overlay.of(context),
data: config.data,
initialPosition: position,
dragStartPoint: dragStartPoint,

View file

@ -23,7 +23,7 @@ class ModalBarrier extends StatelessComponent {
return new Listener(
onPointerDown: (_) {
if (dismissable)
Navigator.of(context).pop();
Navigator.pop(context);
},
behavior: HitTestBehavior.opaque,
child: new ConstrainedBox(

View file

@ -14,13 +14,13 @@ abstract class Route<T> {
/// Called when the route is inserted into the navigator.
///
/// Use this to populate overlayEntries and add them to the 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.
/// 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 overlay argument will be null if this is the first route inserted.
void install(OverlayState overlay, OverlayEntry insertionPoint) { }
void install(OverlayEntry insertionPoint) { }
/// Called after install() when the route is pushed onto the navigator.
void didPush() { }
@ -81,6 +81,7 @@ class NamedRouteSettings {
}
typedef Route RouteFactory(NamedRouteSettings settings);
typedef void NavigatorTransactionCallback(NavigatorTransaction transaction);
class NavigatorObserver {
/// The navigator that the observer is observing, if any.
@ -108,7 +109,37 @@ class Navigator extends StatefulComponent {
static const String defaultRouteName = '/';
static NavigatorState of(BuildContext context) => context.ancestorStateOfType(NavigatorState);
static void pushNamed(BuildContext context, String routeName, { Set<Key> mostValuableKeys }) {
openTransaction(context, (NavigatorTransaction transaction) {
transaction.pushNamed(routeName, mostValuableKeys: mostValuableKeys);
});
}
static void push(BuildContext context, Route route, { Set<Key> mostValuableKeys }) {
openTransaction(context, (NavigatorTransaction transaction) {
transaction.push(route, mostValuableKeys: mostValuableKeys);
});
}
static bool pop(BuildContext context, [ dynamic result ]) {
bool returnValue;
openTransaction(context, (NavigatorTransaction transaction) {
returnValue = transaction.pop(result);
});
return returnValue;
}
static void popAndPushNamed(BuildContext context, String routeName, { Set<Key> mostValuableKeys }) {
openTransaction(context, (NavigatorTransaction transaction) {
transaction.pop();
transaction.pushNamed(routeName, mostValuableKeys: mostValuableKeys);
});
}
static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
NavigatorState navigator = context.ancestorStateOfType(NavigatorState);
navigator.openTransaction(callback);
}
NavigatorState createState() => new NavigatorState();
}
@ -121,7 +152,7 @@ class NavigatorState extends State<Navigator> {
super.initState();
assert(config.observer == null || config.observer.navigator == null);
config.observer?._navigator = this;
push(config.onGenerateRoute(new NamedRouteSettings(
_push(config.onGenerateRoute(new NamedRouteSettings(
name: config.initialRoute ?? Navigator.defaultRouteName
)));
}
@ -146,9 +177,10 @@ class NavigatorState extends State<Navigator> {
assert(() { _debugLocked = false; return true; });
}
// Used by Routes and NavigatorObservers
OverlayState get overlay => _overlayKey.currentState;
OverlayEntry get _currentOverlay {
OverlayEntry get _currentOverlayEntry {
for (Route route in _history.reversed) {
if (route.overlayEntries.isNotEmpty)
return route.overlayEntries.last;
@ -158,17 +190,17 @@ class NavigatorState extends State<Navigator> {
bool _debugLocked = false; // used to prevent re-entrant calls to push, pop, and friends
void pushNamed(String name, { Set<Key> mostValuableKeys }) {
void _pushNamed(String name, { Set<Key> mostValuableKeys }) {
assert(!_debugLocked);
assert(name != null);
NamedRouteSettings settings = new NamedRouteSettings(
name: name,
mostValuableKeys: mostValuableKeys
);
push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings));
_push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings));
}
void push(Route route, { Set<Key> mostValuableKeys }) {
void _push(Route route, { Set<Key> mostValuableKeys }) {
assert(!_debugLocked);
assert(() { _debugLocked = true; return true; });
assert(route != null);
@ -176,7 +208,7 @@ class NavigatorState extends State<Navigator> {
setState(() {
Route oldRoute = _history.isNotEmpty ? _history.last : null;
route._navigator = this;
route.install(overlay, _currentOverlay);
route.install(_currentOverlayEntry);
_history.add(route);
route.didPush();
if (oldRoute != null)
@ -186,18 +218,7 @@ class NavigatorState extends State<Navigator> {
assert(() { _debugLocked = false; return true; });
}
/// Replaces one given route with another, but does not call didPush/didPop.
/// Instead, this calls install() on the new route, then didReplace() on the
/// new route passing the old route, then dispose() on the old route.
///
/// The old route must have overlays, otherwise we won't know where to insert
/// the overlays of the new route. The old route must not be currently visible
/// (i.e. a later route have overlays 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.
void replace({ Route oldRoute, Route newRoute }) {
void _replace({ Route oldRoute, Route newRoute }) {
assert(!_debugLocked);
assert(oldRoute != null);
assert(newRoute != null);
@ -213,7 +234,7 @@ class NavigatorState extends State<Navigator> {
int index = _history.indexOf(oldRoute);
assert(index >= 0);
newRoute._navigator = this;
newRoute.install(overlay, oldRoute.overlayEntries.last);
newRoute.install(oldRoute.overlayEntries.last);
_history[index] = newRoute;
newRoute.didReplace(oldRoute);
if (index > 0)
@ -224,24 +245,14 @@ class NavigatorState extends State<Navigator> {
assert(() { _debugLocked = false; return true; });
}
/// 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 overlays.
void replaceRouteBefore({ Route anchorRoute, Route newRoute }) {
void _replaceRouteBefore({ Route anchorRoute, Route newRoute }) {
assert(anchorRoute != null);
assert(anchorRoute._navigator == this);
assert(_history.indexOf(anchorRoute) > 0);
replace(oldRoute: _history[_history.indexOf(anchorRoute)-1], newRoute: newRoute);
_replace(oldRoute: _history[_history.indexOf(anchorRoute)-1], newRoute: newRoute);
}
/// Removes the route prior to the given anchorRoute without notifying
/// neighbouring routes or the navigator observer, if any.
void removeRouteBefore(Route anchorRoute) {
void _removeRouteBefore(Route anchorRoute) {
assert(!_debugLocked);
assert(() { _debugLocked = true; return true; });
assert(anchorRoute._navigator == this);
@ -258,16 +269,7 @@ class NavigatorState extends State<Navigator> {
assert(() { _debugLocked = false; return true; });
}
/// Removes the current route, notifying the observer (if any), and the
/// previous routes (using [Route.didPopNext]).
///
/// The type of the result argument, 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.
bool pop([dynamic result]) {
bool _pop([dynamic result]) {
assert(!_debugLocked);
assert(() { _debugLocked = true; return true; });
Route route = _history.last;
@ -292,20 +294,123 @@ class NavigatorState extends State<Navigator> {
return true;
}
/// Calls pop() repeatedly until the given route is the current route.
/// If it is already the current route, nothing happens.
void popUntil(Route targetRoute) {
void _popUntil(Route targetRoute) {
assert(_history.contains(targetRoute));
while (!targetRoute.isCurrent)
pop();
_pop();
}
bool _hadTransaction = true;
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;
}
Widget build(BuildContext context) {
assert(!_debugLocked);
assert(_history.isNotEmpty);
_hadTransaction = false;
return new Overlay(
key: _overlayKey,
initialEntries: _history.first.overlayEntries
);
}
}
class NavigatorTransaction {
NavigatorTransaction._(this._navigator) {
assert(_navigator != null);
}
NavigatorState _navigator;
bool _debugOpen = true;
/// Invokes the Navigator's onGenerateRoute callback to create a route with
/// the given name, then calls [push()] with that route.
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.
/// The route will have didPush() called on it; the previous route, if any,
/// will have didPushNext() called on it; and the Navigator observer, if any,
/// will have didPush() called on it.
void push(Route route, { Set<Key> mostValuableKeys }) {
assert(_debugOpen);
_navigator._push(route, mostValuableKeys: mostValuableKeys);
}
/// Replaces one given route with another, but does not call didPush/didPop.
/// Instead, this calls install() on the new route, then didReplace() on the
/// new route passing the old route, then dispose() on the old route. The
/// 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.
void replace({ Route oldRoute, Route newRoute }) {
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.
void replaceRouteBefore({ Route anchorRoute, Route newRoute }) {
assert(_debugOpen);
_navigator._replaceRouteBefore(anchorRoute: anchorRoute, newRoute: newRoute);
}
/// Removes the route prior to the given anchorRoute without notifying
/// neighbouring routes or the navigator observer, if any.
void removeRouteBefore(Route anchorRoute) {
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
/// notified using [Route.didPopNext].
///
/// The type of the result argument, 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.
bool pop([dynamic result]) {
assert(_debugOpen);
return _navigator._pop(result);
}
/// Calls pop() repeatedly until the given route is the current route.
/// If it is already the current route, nothing happens.
void popUntil(Route targetRoute) {
assert(_debugOpen);
_navigator._popUntil(targetRoute);
}
void _debugClose() {
assert(_debugOpen);
_debugOpen = false;
}
}

View file

@ -44,6 +44,8 @@ class Overlay extends StatefulComponent {
final List<OverlayEntry> initialEntries;
static OverlayState of(BuildContext context) => context.ancestorStateOfType(OverlayState);
OverlayState createState() => new OverlayState();
}

View file

@ -23,11 +23,11 @@ abstract class OverlayRoute<T> extends Route<T> {
List<OverlayEntry> get overlayEntries => _overlayEntries;
final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
void install(OverlayState overlay, OverlayEntry insertionPoint) {
void install(OverlayEntry insertionPoint) {
assert(_overlayEntries.isEmpty);
for (WidgetBuilder builder in builders)
_overlayEntries.add(new OverlayEntry(builder: builder));
overlay?.insertAll(_overlayEntries, above: insertionPoint);
navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
}
// Subclasses shouldn't call this if they want to delay the finished() call.
@ -108,9 +108,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
}
}
void install(OverlayState overlay, OverlayEntry insertionPoint) {
void install(OverlayEntry insertionPoint) {
_performance = createPerformance();
super.install(overlay, insertionPoint);
super.install(insertionPoint);
}
void didPush() {

View file

@ -29,7 +29,7 @@ void main() {
expect(tester.findText('drawer'), isNotNull);
tester.pump(new Duration(seconds: 1)); // animation done
expect(tester.findText('drawer'), isNotNull);
Navigator.of(context).pop();
Navigator.pop(context);
tester.pump(); // drawer should be starting to animate away
expect(tester.findText('drawer'), isNotNull);
tester.pump(new Duration(seconds: 1)); // animation done

View file

@ -15,13 +15,13 @@ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
new Container(height: 100.0, width: 100.0),
new Card(child: new Hero(tag: 'a', child: new Container(height: 100.0, width: 100.0, key: firstKey))),
new Container(height: 100.0, width: 100.0),
new FlatButton(child: new Text('button'), onPressed: () => Navigator.of(args.context).pushNamed('/two')),
new FlatButton(child: new Text('button'), onPressed: () => Navigator.pushNamed(args.context, '/two')),
]),
'/two': (RouteArguments args) => new Block([
new Container(height: 150.0, width: 150.0),
new Card(child: new Hero(tag: 'a', child: new Container(height: 150.0, width: 150.0, key: secondKey))),
new Container(height: 150.0, width: 150.0),
new FlatButton(child: new Text('button'), onPressed: () => Navigator.of(args.context).pop()),
new FlatButton(child: new Text('button'), onPressed: () => Navigator.pop(args.context)),
]),
};

View file

@ -10,7 +10,7 @@ class FirstComponent extends StatelessComponent {
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
Navigator.of(context).pushNamed('/second');
Navigator.pushNamed(context, '/second');
},
child: new Container(
decoration: new BoxDecoration(
@ -29,7 +29,7 @@ class SecondComponent extends StatefulComponent {
class SecondComponentState extends State<SecondComponent> {
Widget build(BuildContext context) {
return new GestureDetector(
onTap: Navigator.of(context).pop,
onTap: () => Navigator.pop(context),
child: new Container(
decoration: new BoxDecoration(
backgroundColor: new Color(0xFFFF00FF)

View file

@ -16,10 +16,11 @@ class TestOverlayRoute extends OverlayRoute {
void main() {
test('Check onstage/offstage handling around transitions', () {
testWidgets((WidgetTester tester) {
GlobalKey containerKey = new GlobalKey();
GlobalKey containerKey1 = new GlobalKey();
GlobalKey containerKey2 = new GlobalKey();
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
'/': (_) => new Container(key: containerKey, child: new Text('Home')),
'/settings': (_) => new Container(child: new Text('Settings')),
'/': (_) => new Container(key: containerKey1, child: new Text('Home')),
'/settings': (_) => new Container(key: containerKey2, child: new Text('Settings')),
};
tester.pumpWidget(new MaterialApp(routes: routes));
@ -28,9 +29,7 @@ void main() {
expect(tester.findText('Settings'), isNull);
expect(tester.findText('Overlay'), isNull);
NavigatorState navigator = Navigator.of(containerKey.currentContext);
navigator.pushNamed('/settings');
Navigator.pushNamed(containerKey1.currentContext, '/settings');
tester.pump();
@ -50,7 +49,7 @@ void main() {
expect(tester.findText('Settings'), isOnStage);
expect(tester.findText('Overlay'), isNull);
navigator.push(new TestOverlayRoute());
Navigator.push(containerKey2.currentContext, new TestOverlayRoute());
tester.pump();
@ -64,7 +63,7 @@ void main() {
expect(tester.findText('Settings'), isOnStage);
expect(tester.findText('Overlay'), isOnStage);
navigator.pop();
Navigator.pop(containerKey2.currentContext);
tester.pump();
expect(tester.findText('Home'), isNull);
@ -77,7 +76,7 @@ void main() {
expect(tester.findText('Settings'), isOnStage);
expect(tester.findText('Overlay'), isNull);
navigator.pop();
Navigator.pop(containerKey2.currentContext);
tester.pump();
expect(tester.findText('Home'), isOnStage);

View file

@ -74,7 +74,9 @@ void main() {
expect(tester.findText('16'), isNull);
expect(tester.findText('100'), isNull);
navigatorKey.currentState.pushNamed('/second');
navigatorKey.currentState.openTransaction(
(NavigatorTransaction transaction) => transaction.pushNamed('/second')
);
tester.pump(); // navigating always takes two frames
tester.pump(new Duration(seconds: 1));
@ -89,7 +91,9 @@ void main() {
expect(tester.findText('10'), isNull);
expect(tester.findText('100'), isNull);
navigatorKey.currentState.pop();
navigatorKey.currentState.openTransaction(
(NavigatorTransaction transaction) => transaction.pop()
);
tester.pump(); // navigating always takes two frames
tester.pump(new Duration(seconds: 1));

View file

@ -22,14 +22,14 @@ class TestRoute extends Route<String> {
results.add('$name: $s');
}
void install(OverlayState overlay, OverlayEntry insertionPoint) {
void install(OverlayEntry insertionPoint) {
log('install');
OverlayEntry entry = new OverlayEntry(
builder: (BuildContext context) => new Container(),
opaque: true
);
_entries.add(entry);
overlay?.insert(entry, above: insertionPoint);
navigator.overlay?.insert(entry, above: insertionPoint);
routes.add(this);
}
@ -73,11 +73,11 @@ class TestRoute extends Route<String> {
void runNavigatorTest(
WidgetTester tester,
NavigatorState host,
void test(NavigatorState transaction),
NavigatorTransactionCallback test,
List<String> expectations
) {
expect(host, isNotNull);
test(host);
host.openTransaction(test);
expect(results, equals(expectations));
results.clear();
tester.pump();
@ -95,7 +95,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
},
[
'initial: install',
@ -106,7 +106,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.push(second = new TestRoute('second'));
},
[
@ -118,7 +118,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.push(new TestRoute('third'));
},
[
@ -130,7 +130,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.replace(oldRoute: second, newRoute: new TestRoute('two'));
},
[
@ -143,7 +143,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.pop('hello');
},
[
@ -155,7 +155,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.pop('good bye');
},
[
@ -182,7 +182,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
},
[
'first: install',
@ -193,7 +193,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.push(second = new TestRoute('second'));
},
[
@ -205,7 +205,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.push(new TestRoute('third'));
},
[
@ -217,7 +217,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.removeRouteBefore(second);
},
[
@ -227,7 +227,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.pop('good bye');
},
[
@ -239,7 +239,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.push(new TestRoute('three'));
},
[
@ -252,7 +252,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.push(four = new TestRoute('four'));
},
[
@ -264,7 +264,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.removeRouteBefore(four);
},
[
@ -274,7 +274,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.pop('the end');
},
[
@ -301,7 +301,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
},
[
'A: install',
@ -311,7 +311,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.push(new TestRoute('B'));
},
[
@ -324,7 +324,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.push(routeC = new TestRoute('C'));
},
[
@ -337,7 +337,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.replaceRouteBefore(anchorRoute: routeC, newRoute: routeB = new TestRoute('b'));
},
[
@ -350,7 +350,7 @@ void main() {
runNavigatorTest(
tester,
host,
(NavigatorState transaction) {
(NavigatorTransaction transaction) {
transaction.popUntil(routeB);
},
[