Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
2f04ba91
Unverified
Commit
2f04ba91
authored
May 06, 2020
by
chunhtai
Committed by
GitHub
May 06, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixes the navigator pages update crashes when there is still route wa… (#55998)
parent
c2b7342c
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
179 additions
and
95 deletions
+179
-95
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+108
-85
navigator_test.dart
packages/flutter/test/widgets/navigator_test.dart
+71
-10
No files found.
packages/flutter/lib/src/widgets/navigator.dart
View file @
2f04ba91
...
@@ -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
is
Entering
;
bool
get
is
WaitingForEnteringDecision
;
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.is
Entering
] is true) in indicate that the
/// route (where [RouteTransitionRecord.is
WaitingForEnteringDecision
] 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.is
Entering
] is true) in indicate that the
/// route (where [RouteTransitionRecord.is
WaitingForEnteringDecision
] 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.is
Entering
) {
/// if (pageRoute.is
WaitingForEnteringDecision
) {
/// 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
.
_debugWaitingForExit
Decision
);
assert
(!
exitingPageRoute
.
isWaitingForExiting
Decision
);
if
(
pageRouteToPagelessRoutes
.
containsKey
(
exitingPageRoute
))
{
if
(
pageRouteToPagelessRoutes
.
containsKey
(
exitingPageRoute
))
{
for
(
final
RouteTransitionRecord
pagelessRoute
in
pageRouteToPagelessRoutes
[
exitingPageRoute
])
{
for
(
final
RouteTransitionRecord
pagelessRoute
in
pageRouteToPagelessRoutes
[
exitingPageRoute
])
{
assert
(!
pagelessRoute
.
_debugWaitingForExit
Decision
);
assert
(!
pagelessRoute
.
isWaitingForExiting
Decision
);
}
}
}
}
}
}
...
@@ -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
.
is
Entering
&&
!
routeEntry
.
_debugWaitingForExit
Decision
);
assert
(!
routeEntry
.
is
WaitingForEnteringDecision
&&
!
routeEntry
.
isWaitingForExiting
Decision
);
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.is
Entering] set to true, this route requires explicit
/// [RouteTransitionRecord.is
WaitingForEnteringDecision] 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
.
_debugWaitingForExit
Decision
);
assert
(
pagelessRoute
.
isWaitingForExiting
Decision
);
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
.
is
Entering
)
{
if
(
pageRoute
.
is
WaitingForEnteringDecision
)
{
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 wa
nt to run install, didAdd, etc; a route created by onGenerateInitialRoutes or by the initial widget.pages
adding
,
// we'll wa
iting 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
||
_debugWaitingForExit
Decision
,
!
hasPage
||
isWaitingForExiting
Decision
,
'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
||
_debugWaitingForExit
Decision
,
!
hasPage
||
isWaitingForExiting
Decision
,
'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
(
is
Entering
&&
!
_debugWaitingForExit
Decision
,
is
WaitingForEnteringDecision
&&
!
isWaitingForExiting
Decision
,
'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
(
is
Entering
&&
!
_debugWaitingForExit
Decision
,
is
WaitingForEnteringDecision
&&
!
isWaitingForExiting
Decision
,
'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
(
!
is
Entering
&&
_debugWaitingForExitDecision
,
!
is
WaitingForEnteringDecision
&&
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
);
_
debugWaitingForExit
Decision
=
false
;
_
isWaitingForExiting
Decision
=
false
;
}
}
@override
@override
void
markForComplete
([
dynamic
result
])
{
void
markForComplete
([
dynamic
result
])
{
assert
(
assert
(
!
is
Entering
&&
_debugWaitingForExitDecision
,
!
is
WaitingForEnteringDecision
&&
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
);
_
debugWaitingForExit
Decision
=
false
;
_
isWaitingForExiting
Decision
=
false
;
}
}
@override
@override
void
markForRemove
()
{
void
markForRemove
()
{
assert
(
assert
(
!
is
Entering
&&
_debugWaitingForExitDecision
,
!
is
WaitingForEnteringDecision
&&
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
();
_
debugWaitingForExit
Decision
=
false
;
_
isWaitingForExiting
Decision
=
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
;
}
}
...
...
packages/flutter/test/widgets/navigator_test.dart
View file @
2f04ba91
...
@@ -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
.
is
Entering
)
{
if
(
pageRoute
.
is
WaitingForEnteringDecision
)
{
pageRoute
.
markForAdd
();
pageRoute
.
markForAdd
();
}
}
results
.
add
(
pageRoute
);
results
.
add
(
pageRoute
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment