Unverified Commit 2f04ba91 authored by chunhtai's avatar chunhtai Committed by GitHub

Fixes the navigator pages update crashes when there is still route wa… (#55998)

parent c2b7342c
...@@ -612,26 +612,31 @@ abstract class RouteTransitionRecord { ...@@ -612,26 +612,31 @@ abstract class RouteTransitionRecord {
/// Retrieves the wrapped [Route]. /// Retrieves the wrapped [Route].
Route<dynamic> get route; Route<dynamic> get route;
/// Whether this route is entering the screen. /// Whether this route is waiting for the decision on how to enter the screen.
/// ///
/// If this property is true, this route requires an explicit decision on how /// If this property is true, this route requires an explicit decision on how
/// to transition into the screen. Such a decision should be made in the /// to transition into the screen. Such a decision should be made in the
/// [TransitionDelegate.resolve]. /// [TransitionDelegate.resolve].
bool get isEntering; bool get isWaitingForEnteringDecision;
bool _debugWaitingForExitDecision = false; /// Whether this route is waiting for the decision on how to exit the screen.
///
/// If this property is true, this route requires an explicit decision on how
/// to transition off the screen. Such a decision should be made in the
/// [TransitionDelegate.resolve].
bool get isWaitingForExitingDecision;
/// Marks the [route] to be pushed with transition. /// Marks the [route] to be pushed with transition.
/// ///
/// During [TransitionDelegate.resolve], this can be called on an entering /// During [TransitionDelegate.resolve], this can be called on an entering
/// route (where [RouteTransitionRecord.isEntering] is true) in indicate that the /// route (where [RouteTransitionRecord.isWaitingForEnteringDecision] is true) in indicate that the
/// route should be pushed onto the [Navigator] with an animated transition. /// route should be pushed onto the [Navigator] with an animated transition.
void markForPush(); void markForPush();
/// Marks the [route] to be added without transition. /// Marks the [route] to be added without transition.
/// ///
/// During [TransitionDelegate.resolve], this can be called on an entering /// During [TransitionDelegate.resolve], this can be called on an entering
/// route (where [RouteTransitionRecord.isEntering] is true) in indicate that the /// route (where [RouteTransitionRecord.isWaitingForEnteringDecision] is true) in indicate that the
/// route should be added onto the [Navigator] without an animated transition. /// route should be added onto the [Navigator] without an animated transition.
void markForAdd(); void markForAdd();
...@@ -685,13 +690,14 @@ abstract class RouteTransitionRecord { ...@@ -685,13 +690,14 @@ abstract class RouteTransitionRecord {
/// final List<RouteTransitionRecord> results = <RouteTransitionRecord>[]; /// final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
/// ///
/// for (final RouteTransitionRecord pageRoute in newPageRouteHistory) { /// for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
/// if (pageRoute.isEntering) { /// if (pageRoute.isWaitingForEnteringDecision) {
/// pageRoute.markForAdd(); /// pageRoute.markForAdd();
/// } /// }
/// results.add(pageRoute); /// results.add(pageRoute);
/// ///
/// } /// }
/// for (final RouteTransitionRecord exitingPageRoute in locationToExitingPageRoute.values) { /// for (final RouteTransitionRecord exitingPageRoute in locationToExitingPageRoute.values) {
/// if (exitingPageRoute.isWaitingForExitingDecision) {
/// exitingPageRoute.markForRemove(); /// exitingPageRoute.markForRemove();
/// final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute]; /// final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
/// if (pagelessRoutes != null) { /// if (pagelessRoutes != null) {
...@@ -699,6 +705,7 @@ abstract class RouteTransitionRecord { ...@@ -699,6 +705,7 @@ abstract class RouteTransitionRecord {
/// pagelessRoute.markForRemove(); /// pagelessRoute.markForRemove();
/// } /// }
/// } /// }
/// }
/// results.add(exitingPageRoute); /// results.add(exitingPageRoute);
/// ///
/// } /// }
...@@ -758,10 +765,10 @@ abstract class TransitionDelegate<T> { ...@@ -758,10 +765,10 @@ abstract class TransitionDelegate<T> {
final Set<RouteTransitionRecord> exitingPageRoutes = locationToExitingPageRoute.values.toSet(); final Set<RouteTransitionRecord> exitingPageRoutes = locationToExitingPageRoute.values.toSet();
// Firstly, verifies all exiting routes have been marked. // Firstly, verifies all exiting routes have been marked.
for (final RouteTransitionRecord exitingPageRoute in exitingPageRoutes) { for (final RouteTransitionRecord exitingPageRoute in exitingPageRoutes) {
assert(!exitingPageRoute._debugWaitingForExitDecision); assert(!exitingPageRoute.isWaitingForExitingDecision);
if (pageRouteToPagelessRoutes.containsKey(exitingPageRoute)) { if (pageRouteToPagelessRoutes.containsKey(exitingPageRoute)) {
for (final RouteTransitionRecord pagelessRoute in pageRouteToPagelessRoutes[exitingPageRoute]) { for (final RouteTransitionRecord pagelessRoute in pageRouteToPagelessRoutes[exitingPageRoute]) {
assert(!pagelessRoute._debugWaitingForExitDecision); assert(!pagelessRoute.isWaitingForExitingDecision);
} }
} }
} }
...@@ -771,7 +778,7 @@ abstract class TransitionDelegate<T> { ...@@ -771,7 +778,7 @@ abstract class TransitionDelegate<T> {
for (final _RouteEntry routeEntry in resultsToVerify.cast<_RouteEntry>()) { for (final _RouteEntry routeEntry in resultsToVerify.cast<_RouteEntry>()) {
assert(routeEntry != null); assert(routeEntry != null);
assert(!routeEntry.isEntering && !routeEntry._debugWaitingForExitDecision); assert(!routeEntry.isWaitingForEnteringDecision && !routeEntry.isWaitingForExitingDecision);
if ( if (
indexOfNextRouteInNewHistory >= newPageRouteHistory.length || indexOfNextRouteInNewHistory >= newPageRouteHistory.length ||
routeEntry != newPageRouteHistory[indexOfNextRouteInNewHistory] routeEntry != newPageRouteHistory[indexOfNextRouteInNewHistory]
...@@ -785,7 +792,9 @@ abstract class TransitionDelegate<T> { ...@@ -785,7 +792,9 @@ abstract class TransitionDelegate<T> {
assert( assert(
indexOfNextRouteInNewHistory == newPageRouteHistory.length && indexOfNextRouteInNewHistory == newPageRouteHistory.length &&
exitingPageRoutes.isEmpty exitingPageRoutes.isEmpty,
'The merged result from the $runtimeType.resolve does not include all '
'required routes. Do you remember to merge all exiting routes?'
); );
return true; return true;
}()); }());
...@@ -799,34 +808,44 @@ abstract class TransitionDelegate<T> { ...@@ -799,34 +808,44 @@ abstract class TransitionDelegate<T> {
/// The `newPageRouteHistory` list contains all page-based routes in the order /// The `newPageRouteHistory` list contains all page-based routes in the order
/// that will be on the [Navigator]'s history stack after this update /// that will be on the [Navigator]'s history stack after this update
/// completes. If a route in `newPageRouteHistory` has its /// completes. If a route in `newPageRouteHistory` has its
/// [RouteTransitionRecord.isEntering] set to true, this route requires explicit /// [RouteTransitionRecord.isWaitingForEnteringDecision] set to true, this
/// decision on how it should transition onto the Navigator. To make a /// route requires explicit decision on how it should transition onto the
/// decision, call [RouteTransitionRecord.markForPush] or /// Navigator. To make a decision, call [RouteTransitionRecord.markForPush] or
/// [RouteTransitionRecord.markForAdd]. /// [RouteTransitionRecord.markForAdd].
/// ///
/// The `locationToExitingPageRoute` contains the pages-based routes that /// The `locationToExitingPageRoute` contains the pages-based routes that
/// are removed from the routes history after page update and require explicit /// are removed from the routes history after page update. This map records
/// decision on how to transition off the screen. This map records page-based /// page-based routes to be removed with the location of the route in the
/// routes to be removed with the location of the route in the original route /// original route history before the update. The keys are the locations
/// history before the update. The keys are the locations represented by the /// represented by the page-based routes that are directly below the removed
/// page-based routes that are directly below the removed routes, and the value /// routes, and the value are the page-based routes to be removed. The
/// are the page-based routes to be removed. The location is null if the route /// location is null if the route to be removed is the bottom most route. If
/// to be removed is the bottom most route. To make a decision for a removed /// a route in `locationToExitingPageRoute` has its
/// route, call [RouteTransitionRecord.markForPop], /// [RouteTransitionRecord.isWaitingForExitingDecision] set to true, this
/// route requires explicit decision on how it should transition off the
/// Navigator. To make a decision for a removed route, call
/// [RouteTransitionRecord.markForPop],
/// [RouteTransitionRecord.markForComplete] or /// [RouteTransitionRecord.markForComplete] or
/// [RouteTransitionRecord.markForRemove]. /// [RouteTransitionRecord.markForRemove]. It is possible that decisions are
/// not required for routes in the `locationToExitingPageRoute`. This can
/// happen if the routes have already been popped in earlier page updates and
/// are still waiting for popping animations to finish. In such case, those
/// routes are still included in the `locationToExitingPageRoute` with their
/// [RouteTransitionRecord.isWaitingForExitingDecision] set to false and no
/// decisions are required.
/// ///
/// The `pageRouteToPagelessRoutes` records the page-based routes and their /// The `pageRouteToPagelessRoutes` records the page-based routes and their
/// associated pageless routes. If a page-based route is to be removed, its /// associated pageless routes. If a page-based route is waiting for exiting
/// associated pageless routes also require explicit decisions on how to /// decision, its associated pageless routes also require explicit decisions
/// transition off the screen. /// on how to transition off the screen.
/// ///
/// Once all the decisions have been made, this method must merge the removed /// Once all the decisions have been made, this method must merge the removed
/// routes and the `newPageRouteHistory` and return the merged result. The /// routes (whether or not they require decisions) and the
/// order in the result will be the order the [Navigator] uses for updating /// `newPageRouteHistory` and return the merged result. The order in the
/// the route history. The return list must preserve the same order of routes /// result will be the order the [Navigator] uses for updating the route
/// in `newPageRouteHistory`. The removed routes, however, can be inserted /// history. The return list must preserve the same order of routes in
/// into the return list freely as long as all of them are included. /// `newPageRouteHistory`. The removed routes, however, can be inserted into
/// the return list freely as long as all of them are included.
/// ///
/// For example, consider the following case. /// For example, consider the following case.
/// ///
...@@ -836,9 +855,9 @@ abstract class TransitionDelegate<T> { ...@@ -836,9 +855,9 @@ abstract class TransitionDelegate<T> {
/// ///
/// The following outputs are valid. /// The following outputs are valid.
/// ///
/// result = [A, B ,C ,D ,E] is valid /// result = [A, B ,C ,D ,E] is valid.
/// result = [D, A, B ,C ,E] is also valid because exiting route can be /// result = [D, A, B ,C ,E] is also valid because exiting route can be
/// inserted in any place /// inserted in any place.
/// ///
/// The following outputs are invalid. /// The following outputs are invalid.
/// ///
...@@ -892,7 +911,7 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> { ...@@ -892,7 +911,7 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
final RouteTransitionRecord exitingPageRoute = locationToExitingPageRoute[location]; final RouteTransitionRecord exitingPageRoute = locationToExitingPageRoute[location];
if (exitingPageRoute == null) if (exitingPageRoute == null)
return; return;
assert(exitingPageRoute._debugWaitingForExitDecision); if (exitingPageRoute.isWaitingForExitingDecision) {
final bool hasPagelessRoute = pageRouteToPagelessRoutes.containsKey(exitingPageRoute); final bool hasPagelessRoute = pageRouteToPagelessRoutes.containsKey(exitingPageRoute);
final bool isLastExitingPageRoute = isLast && !locationToExitingPageRoute.containsKey(exitingPageRoute); final bool isLastExitingPageRoute = isLast && !locationToExitingPageRoute.containsKey(exitingPageRoute);
if (isLastExitingPageRoute && !hasPagelessRoute) { if (isLastExitingPageRoute && !hasPagelessRoute) {
...@@ -900,12 +919,10 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> { ...@@ -900,12 +919,10 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
} else { } else {
exitingPageRoute.markForComplete(exitingPageRoute.route.currentResult); exitingPageRoute.markForComplete(exitingPageRoute.route.currentResult);
} }
results.add(exitingPageRoute);
if (hasPagelessRoute) { if (hasPagelessRoute) {
final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute]; final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) { for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
assert(pagelessRoute._debugWaitingForExitDecision); assert(pagelessRoute.isWaitingForExitingDecision);
if (isLastExitingPageRoute && pagelessRoute == pagelessRoutes.last) { if (isLastExitingPageRoute && pagelessRoute == pagelessRoutes.last) {
pagelessRoute.markForPop(pagelessRoute.route.currentResult); pagelessRoute.markForPop(pagelessRoute.route.currentResult);
} else { } else {
...@@ -913,6 +930,9 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> { ...@@ -913,6 +930,9 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
} }
} }
} }
}
results.add(exitingPageRoute);
// It is possible there is another exiting route above this exitingPageRoute. // It is possible there is another exiting route above this exitingPageRoute.
handleExitingRoute(exitingPageRoute, isLast); handleExitingRoute(exitingPageRoute, isLast);
} }
...@@ -922,7 +942,7 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> { ...@@ -922,7 +942,7 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
for (final RouteTransitionRecord pageRoute in newPageRouteHistory) { for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
final bool isLastIteration = newPageRouteHistory.last == pageRoute; final bool isLastIteration = newPageRouteHistory.last == pageRoute;
if (pageRoute.isEntering) { if (pageRoute.isWaitingForEnteringDecision) {
if (!locationToExitingPageRoute.containsKey(pageRoute) && isLastIteration) { if (!locationToExitingPageRoute.containsKey(pageRoute) && isLastIteration) {
pageRoute.markForPush(); pageRoute.markForPush();
} else { } else {
...@@ -2248,7 +2268,7 @@ enum _RouteLifecycle { ...@@ -2248,7 +2268,7 @@ enum _RouteLifecycle {
// routes that are present: // routes that are present:
// //
add, // we'll want to run install, didAdd, etc; a route created by onGenerateInitialRoutes or by the initial widget.pages add, // we'll want to run install, didAdd, etc; a route created by onGenerateInitialRoutes or by the initial widget.pages
adding, // we'll want to run install, didAdd, etc; a route created by onGenerateInitialRoutes or by the initial widget.pages adding, // we'll waiting for the future from didPush of top-most route to complete
// routes that are ready for transition. // routes that are ready for transition.
push, // we'll want to run install, didPush, etc; a route added via push() and friends push, // we'll want to run install, didPush, etc; a route added via push() and friends
pushReplace, // we'll want to run install, didPush, etc; a route added via pushReplace() and friends pushReplace, // we'll want to run install, didPush, etc; a route added via pushReplace() and friends
...@@ -2407,7 +2427,7 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2407,7 +2427,7 @@ class _RouteEntry extends RouteTransitionRecord {
// Route is removed without being completed. // Route is removed without being completed.
void remove({ bool isReplaced = false }) { void remove({ bool isReplaced = false }) {
assert( assert(
!hasPage || _debugWaitingForExitDecision, !hasPage || isWaitingForExitingDecision,
'A page-based route cannot be completed using imperative api, provide a ' 'A page-based route cannot be completed using imperative api, provide a '
'new list without the corresponding Page to Navigator.pages instead. ' 'new list without the corresponding Page to Navigator.pages instead. '
); );
...@@ -2421,7 +2441,7 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2421,7 +2441,7 @@ class _RouteEntry extends RouteTransitionRecord {
// Route completes with `result` and is removed. // Route completes with `result` and is removed.
void complete<T>(T result, { bool isReplaced = false }) { void complete<T>(T result, { bool isReplaced = false }) {
assert( assert(
!hasPage || _debugWaitingForExitDecision, !hasPage || isWaitingForExitingDecision,
'A page-based route cannot be completed using imperative api, provide a ' 'A page-based route cannot be completed using imperative api, provide a '
'new list without the corresponding Page to Navigator.pages instead. ' 'new list without the corresponding Page to Navigator.pages instead. '
); );
...@@ -2485,12 +2505,18 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2485,12 +2505,18 @@ class _RouteEntry extends RouteTransitionRecord {
} }
@override @override
bool get isEntering => currentState == _RouteLifecycle.staging; bool get isWaitingForEnteringDecision => currentState == _RouteLifecycle.staging;
@override
bool get isWaitingForExitingDecision => _isWaitingForExitingDecision;
bool _isWaitingForExitingDecision = false;
void markNeedsExitingDecision() => _isWaitingForExitingDecision = true;
@override @override
void markForPush() { void markForPush() {
assert( assert(
isEntering && !_debugWaitingForExitDecision, isWaitingForEnteringDecision && !isWaitingForExitingDecision,
'This route cannot be marked for push. Either a decision has already been ' 'This route cannot be marked for push. Either a decision has already been '
'made or it does not require an explicit decision on how to transition in.' 'made or it does not require an explicit decision on how to transition in.'
); );
...@@ -2500,7 +2526,7 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2500,7 +2526,7 @@ class _RouteEntry extends RouteTransitionRecord {
@override @override
void markForAdd() { void markForAdd() {
assert( assert(
isEntering && !_debugWaitingForExitDecision, isWaitingForEnteringDecision && !isWaitingForExitingDecision,
'This route cannot be marked for add. Either a decision has already been ' 'This route cannot be marked for add. Either a decision has already been '
'made or it does not require an explicit decision on how to transition in.' 'made or it does not require an explicit decision on how to transition in.'
); );
...@@ -2510,36 +2536,36 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2510,36 +2536,36 @@ class _RouteEntry extends RouteTransitionRecord {
@override @override
void markForPop([dynamic result]) { void markForPop([dynamic result]) {
assert( assert(
!isEntering && _debugWaitingForExitDecision, !isWaitingForEnteringDecision && isWaitingForExitingDecision && isPresent,
'This route cannot be marked for pop. Either a decision has already been ' 'This route cannot be marked for pop. Either a decision has already been '
'made or it does not require an explicit decision on how to transition out.' 'made or it does not require an explicit decision on how to transition out.'
); );
pop<dynamic>(result); pop<dynamic>(result);
_debugWaitingForExitDecision = false; _isWaitingForExitingDecision = false;
} }
@override @override
void markForComplete([dynamic result]) { void markForComplete([dynamic result]) {
assert( assert(
!isEntering && _debugWaitingForExitDecision, !isWaitingForEnteringDecision && isWaitingForExitingDecision && isPresent,
'This route cannot be marked for complete. Either a decision has already ' 'This route cannot be marked for complete. Either a decision has already '
'been made or it does not require an explicit decision on how to transition ' 'been made or it does not require an explicit decision on how to transition '
'out.' 'out.'
); );
complete<dynamic>(result); complete<dynamic>(result);
_debugWaitingForExitDecision = false; _isWaitingForExitingDecision = false;
} }
@override @override
void markForRemove() { void markForRemove() {
assert( assert(
!isEntering && _debugWaitingForExitDecision, !isWaitingForEnteringDecision && isWaitingForExitingDecision && isPresent,
'This route cannot be marked for remove. Either a decision has already ' 'This route cannot be marked for remove. Either a decision has already '
'been made or it does not require an explicit decision on how to transition ' 'been made or it does not require an explicit decision on how to transition '
'out.' 'out.'
); );
remove(); remove();
_debugWaitingForExitDecision = false; _isWaitingForExitingDecision = false;
} }
} }
...@@ -2839,13 +2865,11 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -2839,13 +2865,11 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes
.putIfAbsent( .putIfAbsent(
previousOldPageRouteEntry, previousOldPageRouteEntry,
() => <_RouteEntry>[] () => <_RouteEntry>[],
); );
pagelessRoutes.add(potentialEntryToRemove); pagelessRoutes.add(potentialEntryToRemove);
assert(() { if (previousOldPageRouteEntry.isWaitingForExitingDecision)
potentialEntryToRemove._debugWaitingForExitDecision = previousOldPageRouteEntry._debugWaitingForExitDecision; potentialEntryToRemove.markNeedsExitingDecision();
return true;
}());
continue; continue;
} }
...@@ -2857,10 +2881,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -2857,10 +2881,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
pageKeyToOldEntry.containsKey(potentialPageToRemove.key) pageKeyToOldEntry.containsKey(potentialPageToRemove.key)
) { ) {
locationToExitingPageRoute[previousOldPageRouteEntry] = potentialEntryToRemove; locationToExitingPageRoute[previousOldPageRouteEntry] = potentialEntryToRemove;
assert(() { // We only need a decision if it has not already been popped.
potentialEntryToRemove._debugWaitingForExitDecision = true; if (potentialEntryToRemove.isPresent)
return true; potentialEntryToRemove.markNeedsExitingDecision();
}());
} }
previousOldPageRouteEntry = potentialEntryToRemove; previousOldPageRouteEntry = potentialEntryToRemove;
} }
......
...@@ -2367,6 +2367,66 @@ void main() { ...@@ -2367,6 +2367,66 @@ void main() {
expect(find.text('forth'), findsOneWidget); expect(find.text('forth'), findsOneWidget);
}); });
testWidgets('can repush a page that was previously popped before it has finished popping', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
List<Page<dynamic>> myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'),
];
bool onPopPage(Route<dynamic> route, dynamic result) {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result);
}
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// Pops the second page route.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// Re-push the second page again before it finishes popping.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// It should not crash the app.
expect(tester.takeException(), isNull);
await tester.pumpAndSettle();
expect(find.text('second'), findsOneWidget);
});
testWidgets('can update pages before a route has finished popping', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
List<Page<dynamic>> myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'),
];
bool onPopPage(Route<dynamic> route, dynamic result) {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result);
}
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// Pops the second page route.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// Updates the pages again before second page finishes popping.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// It should not crash the app.
expect(tester.takeException(), isNull);
await tester.pumpAndSettle();
expect(find.text('initial'), findsOneWidget);
});
}); });
} }
...@@ -2415,23 +2475,24 @@ class AlwaysRemoveTransitionDelegate extends TransitionDelegate<void> { ...@@ -2415,23 +2475,24 @@ class AlwaysRemoveTransitionDelegate extends TransitionDelegate<void> {
return; return;
final RouteTransitionRecord exitingPageRoute = locationToExitingPageRoute[location]; final RouteTransitionRecord exitingPageRoute = locationToExitingPageRoute[location];
if (exitingPageRoute.isWaitingForExitingDecision) {
final bool hasPagelessRoute = pageRouteToPagelessRoutes.containsKey(exitingPageRoute); final bool hasPagelessRoute = pageRouteToPagelessRoutes.containsKey(exitingPageRoute);
exitingPageRoute.markForRemove(); exitingPageRoute.markForRemove();
results.add(exitingPageRoute);
if (hasPagelessRoute) { if (hasPagelessRoute) {
final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute]; final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) { for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
pagelessRoute.markForRemove(); pagelessRoute.markForRemove();
} }
} }
}
results.add(exitingPageRoute);
handleExitingRoute(exitingPageRoute); handleExitingRoute(exitingPageRoute);
} }
handleExitingRoute(null); handleExitingRoute(null);
for (final RouteTransitionRecord pageRoute in newPageRouteHistory) { for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
if (pageRoute.isEntering) { if (pageRoute.isWaitingForEnteringDecision) {
pageRoute.markForAdd(); pageRoute.markForAdd();
} }
results.add(pageRoute); results.add(pageRoute);
......
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