Commit 88e74693 authored by Hixie's avatar Hixie

Clean up the Navigator API again.

* Split didPush() into didPush() and install(), so that we can install
  the overlays without triggering the push logic. This will be used in a
  subsequent patch to implement route replacement.

* Split didPop() into didPop() and dispose(), so that we can remove
  overlays without triggering the pop logic. Also for a subsequent patch
  that implements replacement.

* Clean up _navigator on the routes when the Navigator itself is
  disposed.

* Drop the forwarding logic on willPushNext() -- now didPushNext() --
  and didPopNext(), since we no longer have StateRoutes to get in the
  way.

* Implement isCurrent more broadly and without having to keep track of
  state.

* Provide some toString()s on NamedRouteSettings and ModalRoutes.

* Make OverlayState.initState() use the insertAl functionality.

* Make OverlayRoute.builders abstract since that way you'll catch when
  you forget to do it. If you don't want overlays, don't inherit from
  this class.

* Made handleStatusChanged() on TransitionRoute public so that it can be
  overridden by subclasses.
parent e260d7b7
...@@ -10,32 +10,61 @@ abstract class Route<T> { ...@@ -10,32 +10,61 @@ abstract class Route<T> {
NavigatorState get navigator => _navigator; NavigatorState get navigator => _navigator;
NavigatorState _navigator; NavigatorState _navigator;
List<OverlayEntry> get overlayEntries; List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
void didPush(OverlayState overlay, OverlayEntry insertionPoint) { }
/// Called when the route is inserted into the navigator.
/// Use this to install any overlays.
void install(OverlayState overlay, OverlayEntry insertionPoint) { }
/// Called after install() when the route is pushed onto the navigator.
void didPush() { }
/// 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
/// internally (e.g. because it has its own stack of internal state) then /// internally (e.g. because it has its own stack of internal state) then
/// return false, otherwise return true. Returning false will prevent the /// return false, otherwise return true. Returning false will prevent the
/// default behavior of NavigatorState.pop(). /// 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().
bool didPop(T result) => true; bool didPop(T result) => true;
/// The given route has been pushed onto the navigator after this route. /// The given route has been pushed onto the navigator after this route.
/// Return true if the route before this one should be notified also. The void didPushNext(Route nextRoute) { }
/// first route to return false will be the one passed to the
/// NavigatorObserver's didPush() as the previousRoute.
bool willPushNext(Route nextRoute) => 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. Return true if the route before this one should be notified /// navigator.
/// also. The first route to return false will be the one passed to the void didPopNext(Route nextRoute) { }
/// NavigatorObserver's didPush() as the previousRoute.
bool didPopNext(Route nextRoute) => false; /// 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;
}
} }
class NamedRouteSettings { class NamedRouteSettings {
const NamedRouteSettings({ this.name, this.mostValuableKeys }); const NamedRouteSettings({ this.name, this.mostValuableKeys });
final String name; final String name;
final Set<Key> mostValuableKeys; final Set<Key> mostValuableKeys;
String toString() {
String result = '"$name"';
if (mostValuableKeys != null && mostValuableKeys.isNotEmpty) {
result += '; keys:';
for (Key key in mostValuableKeys)
result += ' $key';
}
return result;
}
} }
typedef Route RouteFactory(NamedRouteSettings settings); typedef Route RouteFactory(NamedRouteSettings settings);
...@@ -93,8 +122,15 @@ class NavigatorState extends State<Navigator> { ...@@ -93,8 +122,15 @@ class NavigatorState extends State<Navigator> {
} }
void dispose() { void dispose() {
assert(!_debugLocked);
assert(() { _debugLocked = true; return true; });
config.observer?._navigator = null; config.observer?._navigator = null;
for (Route route in _history) {
route.dispose();
route._navigator = null;
}
super.dispose(); super.dispose();
assert(() { _debugLocked = false; return true; });
} }
OverlayState get overlay => _overlayKey.currentState; OverlayState get overlay => _overlayKey.currentState;
...@@ -125,13 +161,14 @@ class NavigatorState extends State<Navigator> { ...@@ -125,13 +161,14 @@ class NavigatorState extends State<Navigator> {
assert(route != null); assert(route != null);
assert(route._navigator == null); assert(route._navigator == null);
setState(() { setState(() {
int index = _history.length-1; Route oldRoute = _history.isNotEmpty ? _history.last : null;
while (index >= 0 && _history[index].willPushNext(route))
index -= 1;
route._navigator = this; route._navigator = this;
route.didPush(overlay, _currentOverlay); route.install(overlay, _currentOverlay);
config.observer?.didPush(route, index >= 0 ? _history[index] : null);
_history.add(route); _history.add(route);
route.didPush();
if (oldRoute != null)
oldRoute.didPushNext(route);
config.observer?.didPush(route, oldRoute);
}); });
assert(() { _debugLocked = false; return true; }); assert(() { _debugLocked = false; return true; });
} }
...@@ -153,14 +190,12 @@ class NavigatorState extends State<Navigator> { ...@@ -153,14 +190,12 @@ class NavigatorState extends State<Navigator> {
if (route.didPop(result)) { if (route.didPop(result)) {
if (_history.length > 1) { if (_history.length > 1) {
setState(() { setState(() {
// We use setState to guarantee that we'll rebuild, since the routes can't // We use setState to guarantee that we'll rebuild, since the routes
// do that for themselves, even if they have changed their own state (e.g. // can't do that for themselves, even if they have changed their own
// ModalScope.isCurrent). // state (e.g. ModalScope.isCurrent).
_history.removeLast(); _history.removeLast();
int index = _history.length-1; _history.last.didPopNext(route);
while (index >= 0 && _history[index].didPopNext(route)) config.observer?.didPop(route, _history.last);
index -= 1;
config.observer?.didPop(route, index >= 0 ? _history[index] : null);
route._navigator = null; route._navigator = null;
}); });
} else { } else {
......
...@@ -52,8 +52,7 @@ class OverlayState extends State<Overlay> { ...@@ -52,8 +52,7 @@ class OverlayState extends State<Overlay> {
void initState() { void initState() {
super.initState(); super.initState();
for (OverlayEntry entry in config.initialEntries) insertAll(config.initialEntries);
insert(entry);
} }
void insert(OverlayEntry entry, { OverlayEntry above }) { void insert(OverlayEntry entry, { OverlayEntry above }) {
......
...@@ -18,12 +18,13 @@ import 'status_transitions.dart'; ...@@ -18,12 +18,13 @@ import 'status_transitions.dart';
const _kTransparent = const Color(0x00000000); const _kTransparent = const Color(0x00000000);
abstract class OverlayRoute<T> extends Route<T> { abstract class OverlayRoute<T> extends Route<T> {
List<WidgetBuilder> get builders => const <WidgetBuilder>[]; List<WidgetBuilder> get builders;
List<OverlayEntry> get overlayEntries => _overlayEntries; List<OverlayEntry> get overlayEntries => _overlayEntries;
final List<OverlayEntry> _overlayEntries = <OverlayEntry>[]; final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
void didPush(OverlayState overlay, OverlayEntry insertionPoint) { void install(OverlayState overlay, OverlayEntry insertionPoint) {
assert(_overlayEntries.isEmpty);
for (WidgetBuilder builder in builders) for (WidgetBuilder builder in builders)
_overlayEntries.add(new OverlayEntry(builder: builder)); _overlayEntries.add(new OverlayEntry(builder: builder));
overlay?.insertAll(_overlayEntries, above: insertionPoint); overlay?.insertAll(_overlayEntries, above: insertionPoint);
...@@ -47,6 +48,10 @@ abstract class OverlayRoute<T> extends Route<T> { ...@@ -47,6 +48,10 @@ abstract class OverlayRoute<T> extends Route<T> {
entry.remove(); entry.remove();
_overlayEntries.clear(); _overlayEntries.clear();
} }
void dispose() {
finished();
}
} }
abstract class TransitionRoute<T> extends OverlayRoute<T> { abstract class TransitionRoute<T> extends OverlayRoute<T> {
...@@ -79,7 +84,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -79,7 +84,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
T _result; T _result;
void _handleStatusChanged(PerformanceStatus status) { void handleStatusChanged(PerformanceStatus status) {
switch (status) { switch (status) {
case PerformanceStatus.completed: case PerformanceStatus.completed:
if (overlayEntries.isNotEmpty) if (overlayEntries.isNotEmpty)
...@@ -98,11 +103,15 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -98,11 +103,15 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
} }
} }
void didPush(OverlayState overlay, OverlayEntry insertionPoint) { void install(OverlayState overlay, OverlayEntry insertionPoint) {
_performance = createPerformance() _performance = createPerformance();
..addStatusListener(_handleStatusChanged) super.install(overlay, insertionPoint);
..forward(); }
super.didPush(overlay, insertionPoint);
void didPush() {
_performance.addStatusListener(handleStatusChanged);
_performance.forward();
super.didPush();
} }
bool didPop(T result) { bool didPop(T result) {
...@@ -117,6 +126,11 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -117,6 +126,11 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
_transitionCompleter?.complete(_result); _transitionCompleter?.complete(_result);
} }
void dispose() {
_performance.stop();
super.dispose();
}
String get debugLabel => '$runtimeType'; String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $_performance)'; String toString() => '$runtimeType(performance: $_performance)';
} }
...@@ -269,9 +283,6 @@ abstract class ModalRoute<T> extends LocalHistoryRoute<T> { ...@@ -269,9 +283,6 @@ abstract class ModalRoute<T> extends LocalHistoryRoute<T> {
return widget?.route; return widget?.route;
} }
bool get isCurrent => _isCurrent;
bool _isCurrent = false;
// The API for subclasses to override - used by _ModalScope // The API for subclasses to override - used by _ModalScope
...@@ -314,33 +325,6 @@ abstract class ModalRoute<T> extends LocalHistoryRoute<T> { ...@@ -314,33 +325,6 @@ abstract class ModalRoute<T> extends LocalHistoryRoute<T> {
// Internals // Internals
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
assert(!_isCurrent);
_isCurrent = true;
super.didPush(overlay, insertionPoint);
}
bool didPop(T result) {
assert(_isCurrent);
if (super.didPop(result)) {
_isCurrent = false;
return true;
}
return false;
}
bool willPushNext(Route nextRoute) {
assert(_isCurrent);
_isCurrent = false;
return false;
}
bool didPopNext(Route nextRoute) {
assert(!_isCurrent);
_isCurrent = true;
return false;
}
final GlobalKey<StatusTransitionState> _scopeKey = new GlobalKey<StatusTransitionState>(); final GlobalKey<StatusTransitionState> _scopeKey = new GlobalKey<StatusTransitionState>();
final GlobalKey _subtreeKey = new GlobalKey(); final GlobalKey _subtreeKey = new GlobalKey();
final PageStorageBucket _storageBucket = new PageStorageBucket(); final PageStorageBucket _storageBucket = new PageStorageBucket();
...@@ -375,15 +359,16 @@ abstract class ModalRoute<T> extends LocalHistoryRoute<T> { ...@@ -375,15 +359,16 @@ abstract class ModalRoute<T> extends LocalHistoryRoute<T> {
_buildModalScope _buildModalScope
]; ];
String toString() => '$runtimeType($settings, performance: $_performance)';
} }
/// 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> {
PopupRoute({ Completer<T> completer }) : super(completer: completer); PopupRoute({ Completer<T> completer }) : super(completer: completer);
bool get opaque => false; bool get opaque => false;
bool willPushNext(Route nextRoute) { void didPushNext(Route nextRoute) {
assert(nextRoute is! PageRoute); assert(nextRoute is! PageRoute);
return super.willPushNext(nextRoute); super.didPushNext(nextRoute);
} }
} }
......
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