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
aee9e94c
Unverified
Commit
aee9e94c
authored
Mar 30, 2020
by
chunhtai
Committed by
GitHub
Mar 30, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "Implements the navigator page api (#50362)" (#53610)
This reverts commit
9a6eb7de
.
parent
0d4b4fb5
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
45 additions
and
1735 deletions
+45
-1735
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+45
-1019
navigator_test.dart
packages/flutter/test/widgets/navigator_test.dart
+0
-716
No files found.
packages/flutter/lib/src/widgets/navigator.dart
View file @
aee9e94c
...
@@ -36,11 +36,6 @@ import 'ticker_provider.dart';
...
@@ -36,11 +36,6 @@ import 'ticker_provider.dart';
/// * [Navigator], which is where all the [Route]s end up.
/// * [Navigator], which is where all the [Route]s end up.
typedef
RouteFactory
=
Route
<
dynamic
>
Function
(
RouteSettings
settings
);
typedef
RouteFactory
=
Route
<
dynamic
>
Function
(
RouteSettings
settings
);
/// Creates a route for the given context and route settings.
///
/// Used by [CustomBuilderPage.routeBuilder].
typedef
RouteBuilder
<
T
>
=
Route
<
T
>
Function
(
BuildContext
context
,
RouteSettings
settings
);
/// Creates a series of one or more routes.
/// Creates a series of one or more routes.
///
///
/// Used by [Navigator.onGenerateInitialRoutes].
/// Used by [Navigator.onGenerateInitialRoutes].
...
@@ -55,15 +50,6 @@ typedef RoutePredicate = bool Function(Route<dynamic> route);
...
@@ -55,15 +50,6 @@ typedef RoutePredicate = bool Function(Route<dynamic> route);
/// [ModalRoute.removeScopedWillPopCallback], and [WillPopScope].
/// [ModalRoute.removeScopedWillPopCallback], and [WillPopScope].
typedef
WillPopCallback
=
Future
<
bool
>
Function
();
typedef
WillPopCallback
=
Future
<
bool
>
Function
();
/// Signature for the [Navigator.onPopPage] callback.
///
/// This callback must call [Route.didPop] on the specified route and must
/// properly update the pages list the next time it is passed into
/// [Navigator.pages] so that it no longer includes the corresponding [Page].
/// (Otherwise, the page will be interpreted as a new page to show when the
/// [Navigator.pages] list is next updated.)
typedef
PopPageCallback
=
bool
Function
(
Route
<
dynamic
>
route
,
dynamic
result
);
/// Indicates whether the current route should be popped.
/// Indicates whether the current route should be popped.
///
///
/// Used as the return value for [Route.willPop].
/// Used as the return value for [Route.willPop].
...
@@ -104,13 +90,6 @@ enum RoutePopDisposition {
...
@@ -104,13 +90,6 @@ enum RoutePopDisposition {
/// See [MaterialPageRoute] for a route that replaces the entire screen with a
/// See [MaterialPageRoute] for a route that replaces the entire screen with a
/// platform-adaptive transition.
/// platform-adaptive transition.
///
///
/// A route can belong to a page if the [settings] are a subclass of [Page]. A
/// page-based route, as opposite to pageless route, is created from
/// [Page.createRoute] during [Navigator.pages] updates. The page associated
/// with this route may change during the lifetime of the route. If the
/// [Navigator] updates the page of this route, it calls [changedInternalState]
/// to notify the route that the page has been updated.
///
/// The type argument `T` is the route's return type, as used by
/// The type argument `T` is the route's return type, as used by
/// [currentResult], [popped], and [didPop]. The type `void` may be used if the
/// [currentResult], [popped], and [didPop]. The type `void` may be used if the
/// route does not return a value.
/// route does not return a value.
...
@@ -119,7 +98,7 @@ abstract class Route<T> {
...
@@ -119,7 +98,7 @@ abstract class Route<T> {
///
///
/// If the [settings] are not provided, an empty [RouteSettings] object is
/// If the [settings] are not provided, an empty [RouteSettings] object is
/// used instead.
/// used instead.
Route
({
RouteSettings
settings
})
:
_
settings
=
settings
??
const
RouteSettings
();
Route
({
RouteSettings
settings
})
:
settings
=
settings
??
const
RouteSettings
();
/// The navigator that the route is in, if any.
/// The navigator that the route is in, if any.
NavigatorState
get
navigator
=>
_navigator
;
NavigatorState
get
navigator
=>
_navigator
;
...
@@ -128,26 +107,7 @@ abstract class Route<T> {
...
@@ -128,26 +107,7 @@ abstract class Route<T> {
/// The settings for this route.
/// The settings for this route.
///
///
/// See [RouteSettings] for details.
/// See [RouteSettings] for details.
///
final
RouteSettings
settings
;
/// The settings can change during the route's lifetime. If the settings
/// change, the route's overlays will be marked dirty (see
/// [changedInternalState]).
///
/// If the route is created from a [Page] in the [Navigator.pages] list, then
/// this will be a [Page] subclass, and it will be updated each time its
/// corresponding [Page] in the [Navigator.pages] has changed. Once the
/// [Route] is removed from the history, this value stops updating (and
/// remains with its last value).
RouteSettings
get
settings
=>
_settings
;
RouteSettings
_settings
;
void
_updateSettings
(
RouteSettings
newSettings
)
{
assert
(
newSettings
!=
null
);
if
(
_settings
!=
newSettings
)
{
_settings
=
newSettings
;
changedInternalState
();
}
}
/// The overlay entries of this route.
/// The overlay entries of this route.
///
///
...
@@ -287,6 +247,7 @@ abstract class Route<T> {
...
@@ -287,6 +247,7 @@ abstract class Route<T> {
///
///
/// See [popped], [didComplete], and [currentResult] for a discussion of the
/// See [popped], [didComplete], and [currentResult] for a discussion of the
/// `result` argument.
/// `result` argument.
@protected
@mustCallSuper
@mustCallSuper
bool
didPop
(
T
result
)
{
bool
didPop
(
T
result
)
{
didComplete
(
result
);
didComplete
(
result
);
...
@@ -486,82 +447,6 @@ class RouteSettings {
...
@@ -486,82 +447,6 @@ class RouteSettings {
String
toString
()
=>
'
${objectRuntimeType(this, 'RouteSettings')}
("
$name
",
$arguments
)'
;
String
toString
()
=>
'
${objectRuntimeType(this, 'RouteSettings')}
("
$name
",
$arguments
)'
;
}
}
/// Describes the configuration of a [Route].
///
/// The type argument `T` is the corresponding [Route]'s return type, as
/// used by [Route.currentResult], [Route.popped], and [Route.didPop].
///
/// See also:
///
/// * [Navigator.pages], which accepts a list of [Page]s and updates its routes
/// history.
/// * [CustomBuilderPage], a [Page] subclass that provides the API to build a
/// customized route.
abstract
class
Page
<
T
>
extends
RouteSettings
{
/// Creates a page and initializes [key] for subclasses.
///
/// The [arguments] argument must not be null.
const
Page
({
this
.
key
,
String
name
,
Object
arguments
,
})
:
super
(
name:
name
,
arguments:
arguments
);
/// The key associated with this page.
///
/// This key will be used for comparing pages in [canUpdate].
final
LocalKey
key
;
/// Whether this page can be updated with the [other] page.
///
/// Two pages are consider updatable if they have same the [runtimeType] and
/// [key].
bool
canUpdate
(
Page
<
dynamic
>
other
)
{
return
other
.
runtimeType
==
runtimeType
&&
other
.
key
==
key
;
}
/// Creates the [Route] that corresponds to this page.
///
/// The created [Route] must have its [Route.settings] property set to this [Page].
Route
<
T
>
createRoute
(
BuildContext
context
);
@override
String
toString
()
=>
'
${objectRuntimeType(this, 'Page')}
("
$name
",
$key
,
$arguments
)'
;
}
/// A [Page] that builds a customized [Route] based on the [routeBuilder].
///
/// The type argument `T` is the corresponding [Route]'s return type, as
/// used by [Route.currentResult], [Route.popped], and [Route.didPop].
class
CustomBuilderPage
<
T
>
extends
Page
<
T
>
{
/// Creates a page with a custom route builder.
///
/// Use [routeBuilder] to specify the route that will be created from this
/// page.
const
CustomBuilderPage
({
@required
LocalKey
key
,
@required
this
.
routeBuilder
,
String
name
,
Object
arguments
,
})
:
assert
(
key
!=
null
),
assert
(
routeBuilder
!=
null
),
super
(
key:
key
,
name:
name
,
arguments:
arguments
);
/// A builder that will be called during [createRoute] to create a [Route].
///
/// The routes returned from this builder must have their settings equal to
/// the input `settings`.
final
RouteBuilder
<
T
>
routeBuilder
;
@override
Route
<
T
>
createRoute
(
BuildContext
context
)
{
final
Route
<
T
>
route
=
routeBuilder
(
context
,
this
);
assert
(
route
.
settings
==
this
);
return
route
;
}
}
/// An interface for observing the behavior of a [Navigator].
/// An interface for observing the behavior of a [Navigator].
class
NavigatorObserver
{
class
NavigatorObserver
{
/// The navigator that the observer is observing, if any.
/// The navigator that the observer is observing, if any.
...
@@ -606,336 +491,6 @@ class NavigatorObserver {
...
@@ -606,336 +491,6 @@ class NavigatorObserver {
void
didStopUserGesture
()
{
}
void
didStopUserGesture
()
{
}
}
}
/// A [Route] wrapper interface that can be staged for [TransitionDelegate] to
/// decide how its underlying [Route] should transition on or off screen.
abstract
class
RouteTransitionRecord
{
/// Retrieves the wrapped [Route].
Route
<
dynamic
>
get
route
;
/// Whether this route is entering the screen.
///
/// 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
/// [TransitionDelegate.resolve].
bool
get
isEntering
;
bool
_debugWaitingForExitDecision
=
false
;
/// Marks the [route] to be pushed with transition.
///
/// During [TransitionDelegate.resolve], this can be called on an entering
/// route (where [RouteTransitionRecord.isEntering] is true) in indicate that the
/// route should be pushed onto the [Navigator] with an animated transition.
void
markForPush
();
/// Marks the [route] to be added without transition.
///
/// During [TransitionDelegate.resolve], this can be called on an entering
/// route (where [RouteTransitionRecord.isEntering] is true) in indicate that the
/// route should be added onto the [Navigator] without an animated transition.
void
markForAdd
();
/// Marks the [route] to be popped with transition.
///
/// During [TransitionDelegate.resolve], this can be called on an exiting
/// route to indicate that the route should be popped off the [Navigator] with
/// an animated transition.
void
markForPop
([
dynamic
result
]);
/// Marks the [route] to be completed without transition.
///
/// During [TransitionDelegate.resolve], this can be called on an exiting
/// route to indicate that the route should be completed with the provided
/// result and removed from the [Navigator] without an animated transition.
void
markForComplete
([
dynamic
result
]);
/// Marks the [route] to be removed without transition.
///
/// During [TransitionDelegate.resolve], this can be called on an exiting
/// route to indicate that the route should be removed from the [Navigator]
/// without completing and without an animated transition.
void
markForRemove
();
}
/// The delegate that decides how pages added and removed from [Navigator.pages]
/// transition in or out of the screen.
///
/// This abstract class implements the API to be called by [Navigator] when it
/// requires explicit decisions on how the routes transition on or off the screen.
///
/// To make route transition decisions, subclass must implement [resolve].
///
/// {@tool sample --template=freeform}
/// The following example demonstrates how to implement a subclass that always
/// removes or adds routes without animated transitions and puts the removed
/// routes at the top of the list.
///
/// ```dart imports
/// import 'package:flutter/widgets.dart';
/// ```
///
/// ```dart
/// class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
/// @override
/// Iterable<RouteTransitionRecord> resolve({
/// List<RouteTransitionRecord> newPageRouteHistory,
/// Map<RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
/// Map<RouteTransitionRecord, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
/// }) {
/// final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
///
/// for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
/// if (pageRoute.isEntering) {
/// pageRoute.markForAdd();
/// }
/// results.add(pageRoute);
///
/// }
/// for (final RouteTransitionRecord exitingPageRoute in locationToExitingPageRoute.values) {
/// exitingPageRoute.markForRemove();
/// final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
/// if (pagelessRoutes != null) {
/// for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
/// pagelessRoute.markForRemove();
/// }
/// }
/// results.add(exitingPageRoute);
///
/// }
/// return results;
/// }
/// }
///
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Navigator.transitionDelegate], which uses this class to make route
/// transition decisions.
/// * [DefaultTransitionDelegate], which implements the default way to decide
/// how routes transition in or out of the screen.
abstract
class
TransitionDelegate
<
T
>
{
/// Creates a delegate and enables subclass to create a constant class.
const
TransitionDelegate
();
Iterable
<
RouteTransitionRecord
>
_transition
({
List
<
RouteTransitionRecord
>
newPageRouteHistory
,
Map
<
RouteTransitionRecord
,
RouteTransitionRecord
>
locationToExitingPageRoute
,
Map
<
RouteTransitionRecord
,
List
<
RouteTransitionRecord
>>
pageRouteToPagelessRoutes
,
})
{
final
Iterable
<
RouteTransitionRecord
>
results
=
resolve
(
newPageRouteHistory:
newPageRouteHistory
,
locationToExitingPageRoute:
locationToExitingPageRoute
,
pageRouteToPagelessRoutes:
pageRouteToPagelessRoutes
,
);
// Verifies the integrity after the decisions have been made.
//
// Here are the rules:
// - All the entering routes in newPageRouteHistory must either be pushed or
// added.
// - All the exiting routes in locationToExitingPageRoute must either be
// popped, completed or removed.
// - All the pageless routes that belong to exiting routes must either be
// popped, completed or removed.
// - All the entering routes in the result must preserve the same order as
// the entering routes in newPageRouteHistory, and the result must contain
// all exiting routes.
// ex:
//
// newPageRouteHistory = [A, B, C]
//
// locationToExitingPageRoute = {A -> D, C -> E}
//
// results = [A, B ,C ,D ,E] is valid
// results = [D, A, B ,C ,E] is also valid because exiting route can be
// inserted in any place
//
// results = [B, A, C ,D ,E] is invalid because B must be after A.
// results = [A, B, C ,E] is invalid because results must include D.
assert
(()
{
final
List
<
RouteTransitionRecord
>
resultsToVerify
=
results
.
toList
(
growable:
false
);
final
Set
<
RouteTransitionRecord
>
exitingPageRoutes
=
locationToExitingPageRoute
.
values
.
toSet
();
// Firstly, verifies all exiting routes have been marked.
for
(
final
RouteTransitionRecord
exitingPageRoute
in
exitingPageRoutes
)
{
assert
(!
exitingPageRoute
.
_debugWaitingForExitDecision
);
if
(
pageRouteToPagelessRoutes
.
containsKey
(
exitingPageRoute
))
{
for
(
final
RouteTransitionRecord
pagelessRoute
in
pageRouteToPagelessRoutes
[
exitingPageRoute
])
{
assert
(!
pagelessRoute
.
_debugWaitingForExitDecision
);
}
}
}
// Secondly, verifies the order of results matches the newPageRouteHistory
// and contains all the exiting routes.
int
indexOfNextRouteInNewHistory
=
0
;
for
(
final
_RouteEntry
routeEntry
in
resultsToVerify
.
cast
<
_RouteEntry
>())
{
assert
(
routeEntry
!=
null
);
assert
(!
routeEntry
.
isEntering
&&
!
routeEntry
.
_debugWaitingForExitDecision
);
if
(
indexOfNextRouteInNewHistory
>=
newPageRouteHistory
.
length
||
routeEntry
!=
newPageRouteHistory
[
indexOfNextRouteInNewHistory
]
)
{
assert
(
exitingPageRoutes
.
contains
(
routeEntry
));
exitingPageRoutes
.
remove
(
routeEntry
);
}
else
{
indexOfNextRouteInNewHistory
+=
1
;
}
}
assert
(
indexOfNextRouteInNewHistory
==
newPageRouteHistory
.
length
&&
exitingPageRoutes
.
isEmpty
);
return
true
;
}());
return
results
;
}
/// A method that will be called by the [Navigator] to decide how routes
/// transition in or out of the screen when [Navigator.pages] is updated.
///
/// The `newPageRouteHistory` list contains all page-based routes in the order
/// that will be on the [Navigator]'s history stack after this update
/// completes. If a route in `newPageRouteHistory` has its
/// [RouteTransitionRecord.isEntering] set to true, this route requires explicit
/// decision on how it should transition onto the Navigator. To make a
/// decision, call [RouteTransitionRecord.markForPush] or
/// [RouteTransitionRecord.markForAdd].
///
/// The `locationToExitingPageRoute` contains the pages-based routes that
/// are removed from the routes history after page update and require explicit
/// decision on how to transition off the screen. This map records page-based
/// routes to be removed with the location of the route in the original route
/// history before the update. The keys are the locations represented by the
/// page-based routes that are directly below the removed routes, and the value
/// are the page-based routes to be removed. The location is null if the route
/// to be removed is the bottom most route. To make a decision for a removed
/// route, call [RouteTransitionRecord.markForPop],
/// [RouteTransitionRecord.markForComplete] or
/// [RouteTransitionRecord.markForRemove].
///
/// 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 also require explicit decisions on how to
/// transition off the screen.
///
/// Once all the decisions have been made, this method must merge the removed
/// routes and the `newPageRouteHistory` and return the merged result. The
/// order in the result will be the order the [Navigator] uses for updating
/// the route history. The return list must preserve the same order of routes
/// in `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.
///
/// newPageRouteHistory = [A, B, C]
///
/// locationToExitingPageRoute = {A -> D, C -> E}
///
/// The following outputs are valid.
///
/// result = [A, B ,C ,D ,E] is valid
/// result = [D, A, B ,C ,E] is also valid because exiting route can be
/// inserted in any place
///
/// The following outputs are invalid.
///
/// result = [B, A, C ,D ,E] is invalid because B must be after A.
/// result = [A, B, C ,E] is invalid because results must include D.
///
/// See also:
///
/// * [RouteTransitionRecord.markForPush], which makes route enter the screen
/// with an animated transition.
/// * [RouteTransitionRecord.markForAdd], which makes route enter the screen
/// without an animated transition.
/// * [RouteTransitionRecord.markForPop], which makes route exit the screen
/// with an animated transition.
/// * [RouteTransitionRecord.markForRemove], which does not complete the
/// route and makes it exit the screen without an animated transition.
/// * [RouteTransitionRecord.markForComplete], which completes the route and
/// makes it exit the screen without an animated transition.
/// * [DefaultTransitionDelegate.resolve], which implements the default way
/// to decide how routes transition in or out of the screen.
Iterable
<
RouteTransitionRecord
>
resolve
({
List
<
RouteTransitionRecord
>
newPageRouteHistory
,
Map
<
RouteTransitionRecord
,
RouteTransitionRecord
>
locationToExitingPageRoute
,
Map
<
RouteTransitionRecord
,
List
<
RouteTransitionRecord
>>
pageRouteToPagelessRoutes
,
});
}
/// The default implementation of [TransitionDelegate] that the [Navigator] will
/// use if its [Navigator.transitionDelegate] is not specified.
///
/// This transition delegate follows two rules. Firstly, all the entering routes
/// are placed on top of the exiting routes if they are at the same location.
/// Secondly, the top most route will always transition with an animated transition.
/// All the other routes below will either be completed with
/// [Route.currentResult] or added without an animated transition.
class
DefaultTransitionDelegate
<
T
>
extends
TransitionDelegate
<
T
>
{
/// Creates a default transition delegate.
const
DefaultTransitionDelegate
()
:
super
();
@override
Iterable
<
RouteTransitionRecord
>
resolve
({
List
<
RouteTransitionRecord
>
newPageRouteHistory
,
Map
<
RouteTransitionRecord
,
RouteTransitionRecord
>
locationToExitingPageRoute
,
Map
<
RouteTransitionRecord
,
List
<
RouteTransitionRecord
>>
pageRouteToPagelessRoutes
,
})
{
final
List
<
RouteTransitionRecord
>
results
=
<
RouteTransitionRecord
>[];
// This method will handle the exiting route and its corresponding pageless
// route at this location. It will also recursively check if there is any
// other exiting routes above it and handle them accordingly.
void
handleExitingRoute
(
RouteTransitionRecord
location
,
bool
isLast
)
{
final
RouteTransitionRecord
exitingPageRoute
=
locationToExitingPageRoute
[
location
];
if
(
exitingPageRoute
==
null
)
return
;
assert
(
exitingPageRoute
.
_debugWaitingForExitDecision
);
final
bool
hasPagelessRoute
=
pageRouteToPagelessRoutes
.
containsKey
(
exitingPageRoute
);
final
bool
isLastExitingPageRoute
=
isLast
&&
!
locationToExitingPageRoute
.
containsKey
(
exitingPageRoute
);
if
(
isLastExitingPageRoute
&&
!
hasPagelessRoute
)
{
exitingPageRoute
.
markForPop
(
exitingPageRoute
.
route
.
currentResult
);
}
else
{
exitingPageRoute
.
markForComplete
(
exitingPageRoute
.
route
.
currentResult
);
}
results
.
add
(
exitingPageRoute
);
if
(
hasPagelessRoute
)
{
final
List
<
RouteTransitionRecord
>
pagelessRoutes
=
pageRouteToPagelessRoutes
[
exitingPageRoute
];
for
(
final
RouteTransitionRecord
pagelessRoute
in
pagelessRoutes
)
{
assert
(
pagelessRoute
.
_debugWaitingForExitDecision
);
if
(
isLastExitingPageRoute
&&
pagelessRoute
==
pagelessRoutes
.
last
)
{
pagelessRoute
.
markForPop
(
pagelessRoute
.
route
.
currentResult
);
}
else
{
pagelessRoute
.
markForComplete
(
pagelessRoute
.
route
.
currentResult
);
}
}
}
// It is possible there is another exiting route above this exitingPageRoute.
handleExitingRoute
(
exitingPageRoute
,
isLast
);
}
// Handles exiting route in the beginning of list.
handleExitingRoute
(
null
,
newPageRouteHistory
.
isEmpty
);
for
(
final
RouteTransitionRecord
pageRoute
in
newPageRouteHistory
)
{
final
bool
isLastIteration
=
newPageRouteHistory
.
last
==
pageRoute
;
if
(
pageRoute
.
isEntering
)
{
if
(!
locationToExitingPageRoute
.
containsKey
(
pageRoute
)
&&
isLastIteration
)
{
pageRoute
.
markForPush
();
}
else
{
pageRoute
.
markForAdd
();
}
}
results
.
add
(
pageRoute
);
handleExitingRoute
(
pageRoute
,
isLastIteration
);
}
return
results
;
}
}
/// A widget that manages a set of child widgets with a stack discipline.
/// A widget that manages a set of child widgets with a stack discipline.
///
///
/// Many apps have a navigator near the top of their widget hierarchy in order
/// Many apps have a navigator near the top of their widget hierarchy in order
...
@@ -950,9 +505,8 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
...
@@ -950,9 +505,8 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
/// Mobile apps typically reveal their contents via full-screen elements
/// Mobile apps typically reveal their contents via full-screen elements
/// called "screens" or "pages". In Flutter these elements are called
/// called "screens" or "pages". In Flutter these elements are called
/// routes and they're managed by a [Navigator] widget. The navigator
/// routes and they're managed by a [Navigator] widget. The navigator
/// manages a stack of [Route] objects and provides two ways for managing
/// manages a stack of [Route] objects and provides methods for managing
/// the stack, the declarative API [Navigator.pages] or imperative API
/// the stack, like [Navigator.push] and [Navigator.pop].
/// [Navigator.push] and [Navigator.pop].
///
///
/// When your user interface fits this paradigm of a stack, where the user
/// When your user interface fits this paradigm of a stack, where the user
/// should be able to _navigate_ back to an earlier element in the stack,
/// should be able to _navigate_ back to an earlier element in the stack,
...
@@ -964,21 +518,6 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
...
@@ -964,21 +518,6 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
/// used in the [Scaffold.appBar] property) can automatically add a back
/// used in the [Scaffold.appBar] property) can automatically add a back
/// button for user navigation.
/// button for user navigation.
///
///
/// ## Using the Pages API
///
/// The [Navigator] will convert its [Navigator.pages] into a stack of [Route]s
/// if it is provided. A change in [Navigator.pages] will trigger an update to
/// the stack of [Route]s. The [Navigator] will update its routes to match the
/// new configuration of its [Navigator.pages]. To use this API, one can use
/// [CustomBuilderPage] or create a [Page] subclass and defines a list of
/// [Page]s for [Navigator.pages]. A [Navigator.onPopPage] callback is also
/// required to properly clean up the input pages in case of a pop.
///
/// By Default, the [Navigator] will use [DefaultTransitionDelegate] to decide
/// how routes transition in or out of the screen. To customize it, define a
/// [TransitionDelegate] subclass and provide it to the
/// [Navigator.transitionDelegate].
///
/// ### Displaying a full-screen route
/// ### Displaying a full-screen route
///
///
/// Although you can create a navigator directly, it's most common to use the
/// Although you can create a navigator directly, it's most common to use the
...
@@ -1194,7 +733,7 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
...
@@ -1194,7 +733,7 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
/// ```
/// ```
///
///
/// ```dart main
/// ```dart main
/// void main() => runApp(MyApp());
/// void main() => runApp(
new
MyApp());
/// ```
/// ```
///
///
/// ```dart
/// ```dart
...
@@ -1320,77 +859,17 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
...
@@ -1320,77 +859,17 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
class
Navigator
extends
StatefulWidget
{
class
Navigator
extends
StatefulWidget
{
/// Creates a widget that maintains a stack-based history of child widgets.
/// Creates a widget that maintains a stack-based history of child widgets.
///
///
/// The [onGenerateRoute], [pages], [onGenerateInitialRoutes],
/// The [onGenerateRoute] argument must not be null.
/// [transitionDelegate], [observers] arguments must not be null.
///
/// If the [pages] is not empty, the [onPopPage] must not be null.
const
Navigator
({
const
Navigator
({
Key
key
,
Key
key
,
this
.
pages
=
const
<
Page
<
dynamic
>>[],
this
.
onPopPage
,
this
.
initialRoute
,
this
.
initialRoute
,
this
.
onGenerateInitialRoutes
=
Navigator
.
defaultGenerateInitialRoutes
,
this
.
onGenerateInitialRoutes
=
Navigator
.
defaultGenerateInitialRoutes
,
this
.
onGenerateRoute
,
this
.
onGenerateRoute
,
this
.
onUnknownRoute
,
this
.
onUnknownRoute
,
this
.
transitionDelegate
=
const
DefaultTransitionDelegate
<
dynamic
>(),
this
.
observers
=
const
<
NavigatorObserver
>[],
this
.
observers
=
const
<
NavigatorObserver
>[],
})
:
assert
(
pages
!=
null
),
})
:
assert
(
onGenerateInitialRoutes
!=
null
),
assert
(
onGenerateInitialRoutes
!=
null
),
assert
(
transitionDelegate
!=
null
),
assert
(
observers
!=
null
),
super
(
key:
key
);
super
(
key:
key
);
/// The list of pages with which to populate the history.
///
/// Pages are turned into routes using [Page.createRoute] in a manner
/// analogous to how [Widget]s are turned into [Element]s (and [State]s or
/// [RenderObject]s) using [Widget.createElement] (and
/// [StatefulWidget.createState] or [RenderObjectWidget.createRenderObject]).
///
/// When this list is updated, the new list is compared to the previous
/// list and the set of routes is updated accordingly.
///
/// Some [Route]s do not correspond to [Page] objects, namely, those that are
/// added to the history using the [Navigator] API ([push] and friends). A
/// [Route] that does not correspond to a [Page] object is called a pageless
/// route and is tied to the [Route] that _does_ correspond to a [Page] object
/// that is below it in the history.
///
/// Pages that are added or removed may be animated as controlled by the
/// [transitionDelegate]. If a page is removed that had other pageless routes
/// pushed on top of it using [push] and friends, those pageless routes are
/// also removed with or without animation as determined by the
/// [transitionDelegate].
///
/// To use this API, an [onPopPage] callback must also be provided to properly
/// clean up this list if a page has been popped.
///
/// If [initialRoute] is non-null when the widget is first created, then
/// [onGenerateInitialRoutes] is used to generate routes that are above those
/// corresponding to [pages] in the initial history.
final
List
<
Page
<
dynamic
>>
pages
;
/// Called when [pop] is invoked but the current [Route] corresponds to a
/// [Page] found in the [pages] list.
///
/// The `result` argument is the value with which the route is to complete
/// (e.g. the value returned from a dialog).
///
/// This callback is responsible for calling [Route.didPop] and returning
/// whether this pop is successful.
///
/// The [Navigator] widget should be rebuilt with a [pages] list that does not
/// contain the [Page] for the given [Route]. The next time the [pages] list
/// is updated, if the [Page] corresponding to this [Route] is still present,
/// it will be interpreted as a new route to display.
final
PopPageCallback
onPopPage
;
/// The delegate used for deciding how routes transition in or off the screen
/// during the [pages] updates.
///
/// Defaults to [DefaultTransitionDelegate] if not specified, cannot be null.
final
TransitionDelegate
<
dynamic
>
transitionDelegate
;
/// The name of the first route to show.
/// The name of the first route to show.
///
///
/// Defaults to [Navigator.defaultRouteName].
/// Defaults to [Navigator.defaultRouteName].
...
@@ -2201,14 +1680,6 @@ class Navigator extends StatefulWidget {
...
@@ -2201,14 +1680,6 @@ class Navigator extends StatefulWidget {
// The _RouteLifecycle state machine (only goes down):
// The _RouteLifecycle state machine (only goes down):
//
//
// [creation of a _RouteEntry]
// [creation of a _RouteEntry]
// |
// +
// |\
// | \
// | staging
// | /
// |/
// +-+----------+--+-------+
// / | | |
// / | | |
// / | | |
// / | | |
// / | | |
// / | | |
...
@@ -2217,7 +1688,7 @@ class Navigator extends StatefulWidget {
...
@@ -2217,7 +1688,7 @@ class Navigator extends StatefulWidget {
// pushReplace push* add* replace*
// pushReplace push* add* replace*
// \ | | |
// \ | | |
// \ | | /
// \ | | /
// +--pushing#
adding
/
// +--pushing#
|
/
// \ / /
// \ / /
// \ / /
// \ / /
// idle--+-----+
// idle--+-----+
...
@@ -2243,69 +1714,47 @@ class Navigator extends StatefulWidget {
...
@@ -2243,69 +1714,47 @@ class Navigator extends StatefulWidget {
// route entry will exit that state.
// route entry will exit that state.
// # These states await futures or other events, then transition automatically.
// # These states await futures or other events, then transition automatically.
enum
_RouteLifecycle
{
enum
_RouteLifecycle
{
staging
,
// we will wait for transition delegate to decide what to do with this route.
// routes that are and will be 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
// 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
pushing
,
// we're waiting for the future from didPush to complete
pushing
,
// we're waiting for the future from didPush to complete
replace
,
// we'll want to run install, didReplace, etc; a route added via replace() and friends
replace
,
// we'll want to run install, didReplace, etc; a route added via replace() and friends
idle
,
// route is being harmless
idle
,
// route is being harmless
//
// routes that are but will not present:
// routes that are not present:
//
// routes that should be included in route announcement and should still listen to transition changes.
pop
,
// we'll want to call didPop
pop
,
// we'll want to call didPop
remove
,
// we'll want to run didReplace/didRemove etc
remove
,
// we'll want to run didReplace/didRemove etc
// routes
should not be included in route announcement but should still listen to transition changes.
// routes
that are not and will not present:
popping
,
// we're waiting for the route to call finalizeRoute to switch to dispose
popping
,
// we're waiting for the route to call finalizeRoute to switch to dispose
removing
,
// we are waiting for subsequent routes to be done animating, then will switch to dispose
removing
,
// we are waiting for subsequent routes to be done animating, then will switch to dispose
// routes that are completely removed from the navigator and overlay.
dispose
,
// we will dispose the route momentarily
dispose
,
// we will dispose the route momentarily
disposed
,
// we have disposed the route
disposed
,
// we have disposed the route
}
}
typedef
_RouteEntryPredicate
=
bool
Function
(
_RouteEntry
entry
);
typedef
_RouteEntryPredicate
=
bool
Function
(
_RouteEntry
entry
);
class
_RouteEntry
extends
RouteTransitionRecord
{
class
_RouteEntry
{
_RouteEntry
(
_RouteEntry
(
this
.
route
,
{
this
.
route
,
{
@required
_RouteLifecycle
initialState
,
@required
_RouteLifecycle
initialState
,
})
:
assert
(
route
!=
null
),
})
:
assert
(
route
!=
null
),
assert
(
initialState
!=
null
),
assert
(
initialState
!=
null
),
assert
(
assert
(
initialState
==
_RouteLifecycle
.
staging
||
initialState
==
_RouteLifecycle
.
add
||
initialState
==
_RouteLifecycle
.
add
||
initialState
==
_RouteLifecycle
.
push
||
initialState
==
_RouteLifecycle
.
push
||
initialState
==
_RouteLifecycle
.
pushReplace
||
initialState
==
_RouteLifecycle
.
pushReplace
||
initialState
==
_RouteLifecycle
.
replace
initialState
==
_RouteLifecycle
.
replace
),
),
currentState
=
initialState
;
currentState
=
initialState
;
// ignore: prefer_initializing_formals
@override
final
Route
<
dynamic
>
route
;
final
Route
<
dynamic
>
route
;
_RouteLifecycle
currentState
;
_RouteLifecycle
currentState
;
Route
<
dynamic
>
lastAnnouncedNextRoute
;
// last argument to Route.didChangeNext
Route
<
dynamic
>
lastAnnouncedPreviousRoute
;
// last argument to Route.didChangePrevious
Route
<
dynamic
>
lastAnnouncedPreviousRoute
;
// last argument to Route.didChangePrevious
Route
<
dynamic
>
lastAnnouncedPoppedNextRoute
;
// last argument to Route.didPopNext
Route
<
dynamic
>
lastAnnouncedPoppedNextRoute
;
// last argument to Route.didPopNext
Route
<
dynamic
>
lastAnnouncedNextRoute
;
// last argument to Route.didChangeNext
bool
get
hasPage
=>
route
.
settings
is
Page
;
bool
canUpdateFrom
(
Page
<
dynamic
>
page
)
{
void
handleAdd
({
@required
NavigatorState
navigator
,
@required
bool
isNewFirst
,
@required
Route
<
dynamic
>
previous
,
@required
Route
<
dynamic
>
previousPresent
})
{
if
(
currentState
.
index
>
_RouteLifecycle
.
idle
.
index
)
return
false
;
if
(!
hasPage
)
return
false
;
final
Page
<
dynamic
>
routePage
=
route
.
settings
as
Page
<
dynamic
>;
return
page
.
canUpdate
(
routePage
);
}
void
handleAdd
({
@required
NavigatorState
navigator
})
{
assert
(
currentState
==
_RouteLifecycle
.
add
);
assert
(
currentState
==
_RouteLifecycle
.
add
);
assert
(
navigator
!=
null
);
assert
(
navigator
!=
null
);
assert
(
navigator
.
_debugLocked
);
assert
(
navigator
.
_debugLocked
);
...
@@ -2313,7 +1762,13 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -2313,7 +1762,13 @@ class _RouteEntry extends RouteTransitionRecord {
route
.
_navigator
=
navigator
;
route
.
_navigator
=
navigator
;
route
.
install
();
route
.
install
();
assert
(
route
.
overlayEntries
.
isNotEmpty
);
assert
(
route
.
overlayEntries
.
isNotEmpty
);
currentState
=
_RouteLifecycle
.
adding
;
route
.
didAdd
();
currentState
=
_RouteLifecycle
.
idle
;
if
(
isNewFirst
)
{
route
.
didChangeNext
(
null
);
}
for
(
final
NavigatorObserver
observer
in
navigator
.
widget
.
observers
)
observer
.
didPush
(
route
,
previousPresent
);
}
}
void
handlePush
({
@required
NavigatorState
navigator
,
@required
bool
isNewFirst
,
@required
Route
<
dynamic
>
previous
,
@required
Route
<
dynamic
>
previousPresent
})
{
void
handlePush
({
@required
NavigatorState
navigator
,
@required
bool
isNewFirst
,
@required
Route
<
dynamic
>
previous
,
@required
Route
<
dynamic
>
previousPresent
})
{
...
@@ -2383,16 +1838,6 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -2383,16 +1838,6 @@ class _RouteEntry extends RouteTransitionRecord {
bool
doingPop
=
false
;
bool
doingPop
=
false
;
void
didAdd
({
@required
NavigatorState
navigator
,
@required
bool
isNewFirst
,
@required
Route
<
dynamic
>
previous
,
@required
Route
<
dynamic
>
previousPresent
})
{
route
.
didAdd
();
currentState
=
_RouteLifecycle
.
idle
;
if
(
isNewFirst
)
{
route
.
didChangeNext
(
null
);
}
for
(
final
NavigatorObserver
observer
in
navigator
.
widget
.
observers
)
observer
.
didPush
(
route
,
previousPresent
);
}
void
pop
<
T
>(
T
result
)
{
void
pop
<
T
>(
T
result
)
{
assert
(
isPresent
);
assert
(
isPresent
);
doingPop
=
true
;
doingPop
=
true
;
...
@@ -2406,11 +1851,6 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -2406,11 +1851,6 @@ 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
(
!
hasPage
||
_debugWaitingForExitDecision
,
'A page-based route cannot be completed using imperative api, provide a '
'new list without the corresponding Page to Navigator.pages instead. '
);
if
(
currentState
.
index
>=
_RouteLifecycle
.
remove
.
index
)
if
(
currentState
.
index
>=
_RouteLifecycle
.
remove
.
index
)
return
;
return
;
assert
(
isPresent
);
assert
(
isPresent
);
...
@@ -2420,11 +1860,6 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -2420,11 +1860,6 @@ 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
(
!
hasPage
||
_debugWaitingForExitDecision
,
'A page-based route cannot be completed using imperative api, provide a '
'new list without the corresponding Page to Navigator.pages instead. '
);
if
(
currentState
.
index
>=
_RouteLifecycle
.
remove
.
index
)
if
(
currentState
.
index
>=
_RouteLifecycle
.
remove
.
index
)
return
;
return
;
assert
(
isPresent
);
assert
(
isPresent
);
...
@@ -2445,25 +1880,8 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -2445,25 +1880,8 @@ class _RouteEntry extends RouteTransitionRecord {
currentState
=
_RouteLifecycle
.
disposed
;
currentState
=
_RouteLifecycle
.
disposed
;
}
}
bool
get
willBePresent
{
bool
get
willBePresent
=>
currentState
.
index
<=
_RouteLifecycle
.
idle
.
index
;
return
currentState
.
index
<=
_RouteLifecycle
.
idle
.
index
&&
bool
get
isPresent
=>
currentState
.
index
<=
_RouteLifecycle
.
remove
.
index
;
currentState
.
index
>=
_RouteLifecycle
.
add
.
index
;
}
bool
get
isPresent
{
return
currentState
.
index
<=
_RouteLifecycle
.
remove
.
index
&&
currentState
.
index
>=
_RouteLifecycle
.
add
.
index
;
}
bool
get
suitableForAnnouncement
{
return
currentState
.
index
<=
_RouteLifecycle
.
removing
.
index
&&
currentState
.
index
>=
_RouteLifecycle
.
push
.
index
;
}
bool
get
suitableForTransitionAnimation
{
return
currentState
.
index
<=
_RouteLifecycle
.
remove
.
index
&&
currentState
.
index
>=
_RouteLifecycle
.
push
.
index
;
}
bool
shouldAnnounceChangeToNext
(
Route
<
dynamic
>
nextRoute
)
{
bool
shouldAnnounceChangeToNext
(
Route
<
dynamic
>
nextRoute
)
{
assert
(
nextRoute
!=
lastAnnouncedNextRoute
);
assert
(
nextRoute
!=
lastAnnouncedNextRoute
);
...
@@ -2477,76 +1895,17 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -2477,76 +1895,17 @@ class _RouteEntry extends RouteTransitionRecord {
}
}
static
final
_RouteEntryPredicate
isPresentPredicate
=
(
_RouteEntry
entry
)
=>
entry
.
isPresent
;
static
final
_RouteEntryPredicate
isPresentPredicate
=
(
_RouteEntry
entry
)
=>
entry
.
isPresent
;
static
final
_RouteEntryPredicate
suitableForTransitionAnimationPredicate
=
(
_RouteEntry
entry
)
=>
entry
.
suitableForTransitionAnimation
;
static
final
_RouteEntryPredicate
willBePresentPredicate
=
(
_RouteEntry
entry
)
=>
entry
.
willBePresent
;
static
final
_RouteEntryPredicate
willBePresentPredicate
=
(
_RouteEntry
entry
)
=>
entry
.
willBePresent
;
static
_RouteEntryPredicate
isRoutePredicate
(
Route
<
dynamic
>
route
)
{
static
_RouteEntryPredicate
isRoutePredicate
(
Route
<
dynamic
>
route
)
{
return
(
_RouteEntry
entry
)
=>
entry
.
route
==
route
;
return
(
_RouteEntry
entry
)
=>
entry
.
route
==
route
;
}
}
@override
bool
get
isEntering
=>
currentState
==
_RouteLifecycle
.
staging
;
@override
void
markForPush
()
{
assert
(
isEntering
&&
!
_debugWaitingForExitDecision
,
'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.'
);
currentState
=
_RouteLifecycle
.
push
;
}
@override
void
markForAdd
()
{
assert
(
isEntering
&&
!
_debugWaitingForExitDecision
,
'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.'
);
currentState
=
_RouteLifecycle
.
add
;
}
@override
void
markForPop
([
dynamic
result
])
{
assert
(
!
isEntering
&&
_debugWaitingForExitDecision
,
'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.'
);
pop
<
dynamic
>(
result
);
_debugWaitingForExitDecision
=
false
;
}
@override
void
markForComplete
([
dynamic
result
])
{
assert
(
!
isEntering
&&
_debugWaitingForExitDecision
,
'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 '
'out.'
);
complete
<
dynamic
>(
result
);
_debugWaitingForExitDecision
=
false
;
}
@override
void
markForRemove
()
{
assert
(
!
isEntering
&&
_debugWaitingForExitDecision
,
'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 '
'out.'
);
remove
();
_debugWaitingForExitDecision
=
false
;
}
}
}
/// The state for a [Navigator] widget.
/// The state for a [Navigator] widget.
class
NavigatorState
extends
State
<
Navigator
>
with
TickerProviderStateMixin
{
class
NavigatorState
extends
State
<
Navigator
>
with
TickerProviderStateMixin
{
final
GlobalKey
<
OverlayState
>
_overlayKey
=
GlobalKey
<
OverlayState
>();
final
GlobalKey
<
OverlayState
>
_overlayKey
=
GlobalKey
<
OverlayState
>();
List
<
_RouteEntry
>
_history
=
<
_RouteEntry
>[];
final
List
<
_RouteEntry
>
_history
=
<
_RouteEntry
>[];
/// The [FocusScopeNode] for the [FocusScope] that encloses the routes.
/// The [FocusScopeNode] for the [FocusScope] that encloses the routes.
final
FocusScopeNode
focusScopeNode
=
FocusScopeNode
(
debugLabel:
'Navigator Scope'
);
final
FocusScopeNode
focusScopeNode
=
FocusScopeNode
(
debugLabel:
'Navigator Scope'
);
...
@@ -2556,40 +1915,20 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -2556,40 +1915,20 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
@override
@override
void
initState
()
{
void
initState
()
{
super
.
initState
();
super
.
initState
();
assert
(
widget
.
pages
.
isEmpty
||
widget
.
onPopPage
!=
null
,
'The Navigator.onPopPage must be provided to use the Navigator.pages API'
,
);
for
(
final
NavigatorObserver
observer
in
widget
.
observers
)
{
for
(
final
NavigatorObserver
observer
in
widget
.
observers
)
{
assert
(
observer
.
navigator
==
null
);
assert
(
observer
.
navigator
==
null
);
observer
.
_navigator
=
this
;
observer
.
_navigator
=
this
;
}
}
String
initialRoute
=
widget
.
initialRoute
;
// TODO(chunhtai): Uses pages after we add page api.
if
(
widget
.
pages
.
isNotEmpty
)
{
// https://github.com/flutter/flutter/issues/45938
_history
.
addAll
(
_history
.
addAll
(
widget
.
pages
.
map
((
Page
<
dynamic
>
page
)
=>
_RouteEntry
(
widget
.
onGenerateInitialRoutes
(
this
,
widget
.
initialRoute
??
Navigator
.
defaultRouteName
)
page
.
createRoute
(
context
),
.
map
((
Route
<
dynamic
>
route
)
=>
_RouteEntry
(
route
,
initialState:
_RouteLifecycle
.
add
,
initialState:
_RouteLifecycle
.
add
,
))
);
}
else
{
// If there is no page provided, we will need to provide default route
// to initialize the navigator.
initialRoute
=
initialRoute
??
Navigator
.
defaultRouteName
;
}
if
(
initialRoute
!=
null
)
{
_history
.
addAll
(
widget
.
onGenerateInitialRoutes
(
this
,
widget
.
initialRoute
??
Navigator
.
defaultRouteName
).
map
((
Route
<
dynamic
>
route
)
=>
_RouteEntry
(
route
,
initialState:
_RouteLifecycle
.
add
,
),
),
),
)
;
)
,
}
);
assert
(!
_debugLocked
);
assert
(!
_debugLocked
);
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
_flushHistoryUpdates
();
_flushHistoryUpdates
();
...
@@ -2599,10 +1938,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -2599,10 +1938,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
@override
@override
void
didUpdateWidget
(
Navigator
oldWidget
)
{
void
didUpdateWidget
(
Navigator
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
super
.
didUpdateWidget
(
oldWidget
);
assert
(
widget
.
pages
.
isEmpty
||
widget
.
onPopPage
!=
null
,
'The Navigator.onPopPage must be provided to use the Navigator.pages API'
,
);
if
(
oldWidget
.
observers
!=
widget
.
observers
)
{
if
(
oldWidget
.
observers
!=
widget
.
observers
)
{
for
(
final
NavigatorObserver
observer
in
oldWidget
.
observers
)
for
(
final
NavigatorObserver
observer
in
oldWidget
.
observers
)
observer
.
_navigator
=
null
;
observer
.
_navigator
=
null
;
...
@@ -2611,31 +1946,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -2611,31 +1946,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
observer
.
_navigator
=
this
;
observer
.
_navigator
=
this
;
}
}
}
}
if
(
oldWidget
.
pages
!=
widget
.
pages
)
{
assert
(
widget
.
pages
.
isNotEmpty
,
'To use the Navigator.pages, there must be at least one page in the list.'
);
_updatePages
();
}
for
(
final
_RouteEntry
entry
in
_history
)
for
(
final
_RouteEntry
entry
in
_history
)
entry
.
route
.
changedExternalState
();
entry
.
route
.
changedExternalState
();
}
}
void
_debugCheckDuplicatedPageKeys
()
{
assert
((){
final
Set
<
Key
>
keyReservation
=
<
Key
>{};
for
(
final
Page
<
dynamic
>
page
in
widget
.
pages
)
{
if
(
page
.
key
!=
null
)
{
assert
(!
keyReservation
.
contains
(
page
.
key
));
keyReservation
.
add
(
page
.
key
);
}
}
return
true
;
}());
}
@override
@override
void
dispose
()
{
void
dispose
()
{
assert
(!
_debugLocked
);
assert
(!
_debugLocked
);
...
@@ -2663,276 +1977,8 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -2663,276 +1977,8 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
String
_lastAnnouncedRouteName
;
String
_lastAnnouncedRouteName
;
bool
_debugUpdatingPage
=
false
;
void
_updatePages
()
{
assert
(()
{
assert
(!
_debugUpdatingPage
);
_debugCheckDuplicatedPageKeys
();
_debugUpdatingPage
=
true
;
return
true
;
}());
// This attempts to diff the new pages list (widget.pages) with
// the old _RouteEntry[s] list (_history), and produces a new list of
// _RouteEntry[s] to be the new list of _history. This method roughly
// follows the same outline of RenderObjectElement.updateChildren.
//
// The cases it tries to optimize for are:
// - the old list is empty
// - All the pages in the new list can match the page-based routes in the old
// list, and their orders are the same.
// - there is an insertion or removal of one or more page-based route in
// only one place in the list
// If a page-based route with a key is in both lists, it will be synced.
// Page-based routes without keys might be synced but there is no guarantee.
// The general approach is to sync the entire new list backwards, as follows:
// 1. Walk the lists from the bottom, syncing nodes, and record pageless routes,
// until you no longer have matching nodes.
// 2. Walk the lists from the top, without syncing nodes, until you no
// longer have matching nodes. We'll sync these nodes at the end. We
// don't sync them now because we want to sync all the nodes in order
// from beginning to end.
// At this point we narrowed the old and new lists to the point
// where the nodes no longer match.
// 3. Walk the narrowed part of the old list to get the list of
// keys.
// 4. Walk the narrowed part of the new list forwards:
// * Create a new _RouteEntry for non-keyed items and record them for
// transitionDelegate.
// * Sync keyed items with the source if it exists.
// 5. Walk the narrowed part of the old list again to records the
// _RouteEntry[s], as well as pageless routes, needed to be removed for
// transitionDelegate.
// 5. Walk the top of the list again, syncing the nodes and recording
// pageless routes.
// 6. Use transitionDelegate for explicit decisions on how _RouteEntry[s]
// transition in or off the screens.
// 7. Fill pageless routes back into the new history.
bool
needsExplicitDecision
=
false
;
int
newPagesBottom
=
0
;
int
oldEntriesBottom
=
0
;
int
newPagesTop
=
widget
.
pages
.
length
-
1
;
int
oldEntriesTop
=
_history
.
length
-
1
;
final
List
<
_RouteEntry
>
newHistory
=
<
_RouteEntry
>[];
final
Map
<
_RouteEntry
,
List
<
_RouteEntry
>>
pageRouteToPagelessRoutes
=
<
_RouteEntry
,
List
<
_RouteEntry
>>{};
// Updates the bottom of the list.
_RouteEntry
previousOldPageRouteEntry
;
while
(
oldEntriesBottom
<=
oldEntriesTop
)
{
final
_RouteEntry
oldEntry
=
_history
[
oldEntriesBottom
];
assert
(
oldEntry
!=
null
&&
oldEntry
.
currentState
!=
_RouteLifecycle
.
disposed
);
// Records pageless route. The bottom most pageless routes will be
// stored in key = null.
if
(!
oldEntry
.
hasPage
)
{
final
List
<
_RouteEntry
>
pagelessRoutes
=
pageRouteToPagelessRoutes
.
putIfAbsent
(
previousOldPageRouteEntry
,
()
=>
<
_RouteEntry
>[],
);
pagelessRoutes
.
add
(
oldEntry
);
oldEntriesBottom
+=
1
;
continue
;
}
if
(
newPagesBottom
>
newPagesTop
)
break
;
final
Page
<
dynamic
>
newPage
=
widget
.
pages
[
newPagesBottom
];
if
(!
oldEntry
.
canUpdateFrom
(
newPage
))
break
;
previousOldPageRouteEntry
=
oldEntry
;
oldEntry
.
route
.
_updateSettings
(
newPage
);
newHistory
.
add
(
oldEntry
);
newPagesBottom
+=
1
;
oldEntriesBottom
+=
1
;
}
int
pagelessRoutesToSkip
=
0
;
// Scans the top of the list until we found a page-based route that cannot be
// updated.
while
((
oldEntriesBottom
<=
oldEntriesTop
)
&&
(
newPagesBottom
<=
newPagesTop
))
{
final
_RouteEntry
oldEntry
=
_history
[
oldEntriesTop
];
assert
(
oldEntry
!=
null
&&
oldEntry
.
currentState
!=
_RouteLifecycle
.
disposed
);
if
(!
oldEntry
.
hasPage
)
{
// This route might need to be skipped if we can not find a page above.
pagelessRoutesToSkip
+=
1
;
oldEntriesTop
-=
1
;
continue
;
}
final
Page
<
dynamic
>
newPage
=
widget
.
pages
[
newPagesTop
];
if
(!
oldEntry
.
canUpdateFrom
(
newPage
))
break
;
// We found the page for all the consecutive pageless routes below. Those
// pageless routes do not need to be skipped.
pagelessRoutesToSkip
=
0
;
oldEntriesTop
-=
1
;
newPagesTop
-=
1
;
}
// Reverts the pageless routes that cannot be updated.
oldEntriesTop
+=
pagelessRoutesToSkip
;
// Scans middle of the old entries and records the page key to old entry map.
int
oldEntriesBottomToScan
=
oldEntriesBottom
;
final
Map
<
LocalKey
,
_RouteEntry
>
pageKeyToOldEntry
=
<
LocalKey
,
_RouteEntry
>{};
while
(
oldEntriesBottomToScan
<=
oldEntriesTop
)
{
final
_RouteEntry
oldEntry
=
_history
[
oldEntriesBottomToScan
];
oldEntriesBottomToScan
+=
1
;
assert
(
oldEntry
!=
null
&&
oldEntry
.
currentState
!=
_RouteLifecycle
.
disposed
);
// Pageless routes will be recorded when we update the middle of the old
// list.
if
(!
oldEntry
.
hasPage
)
continue
;
assert
(
oldEntry
.
hasPage
);
final
Page
<
dynamic
>
page
=
oldEntry
.
route
.
settings
as
Page
<
dynamic
>;
if
(
page
.
key
==
null
)
continue
;
assert
(!
pageKeyToOldEntry
.
containsKey
(
page
.
key
));
pageKeyToOldEntry
[
page
.
key
]
=
oldEntry
;
}
// Updates the middle of the list.
while
(
newPagesBottom
<=
newPagesTop
)
{
final
Page
<
dynamic
>
nextPage
=
widget
.
pages
[
newPagesBottom
];
newPagesBottom
+=
1
;
if
(
nextPage
.
key
==
null
||
!
pageKeyToOldEntry
.
containsKey
(
nextPage
.
key
)
||
!
pageKeyToOldEntry
[
nextPage
.
key
].
canUpdateFrom
(
nextPage
)
)
{
// There is no matching key in the old history, we need to create a new
// route and wait for the transition delegate to decide how to add
// it into the history.
final
_RouteEntry
newEntry
=
_RouteEntry
(
nextPage
.
createRoute
(
context
),
initialState:
_RouteLifecycle
.
staging
,
);
needsExplicitDecision
=
true
;
assert
(
newEntry
.
route
.
settings
==
nextPage
,
'If a route is created from a page, its must have that page as its '
'settings.'
,
);
newHistory
.
add
(
newEntry
);
}
else
{
// Removes the key from pageKeyToOldEntry to indicate it is taken.
final
_RouteEntry
matchingEntry
=
pageKeyToOldEntry
.
remove
(
nextPage
.
key
);
assert
(
matchingEntry
.
canUpdateFrom
(
nextPage
));
matchingEntry
.
route
.
_updateSettings
(
nextPage
);
newHistory
.
add
(
matchingEntry
);
}
}
// Any remaining old routes that do not have a match will need to be removed.
final
Map
<
RouteTransitionRecord
,
RouteTransitionRecord
>
locationToExitingPageRoute
=
<
RouteTransitionRecord
,
RouteTransitionRecord
>{};
while
(
oldEntriesBottom
<=
oldEntriesTop
)
{
final
_RouteEntry
potentialEntryToRemove
=
_history
[
oldEntriesBottom
];
oldEntriesBottom
+=
1
;
if
(!
potentialEntryToRemove
.
hasPage
)
{
assert
(
previousOldPageRouteEntry
!=
null
);
final
List
<
_RouteEntry
>
pagelessRoutes
=
pageRouteToPagelessRoutes
.
putIfAbsent
(
previousOldPageRouteEntry
,
()
=>
<
_RouteEntry
>[]
);
pagelessRoutes
.
add
(
potentialEntryToRemove
);
assert
(()
{
potentialEntryToRemove
.
_debugWaitingForExitDecision
=
previousOldPageRouteEntry
.
_debugWaitingForExitDecision
;
return
true
;
}());
continue
;
}
final
Page
<
dynamic
>
potentialPageToRemove
=
potentialEntryToRemove
.
route
.
settings
as
Page
<
dynamic
>;
// Marks for transition delegate to remove if this old page does not have
// a key or was not taken during updating the middle of new page.
if
(
potentialPageToRemove
.
key
==
null
||
pageKeyToOldEntry
.
containsKey
(
potentialPageToRemove
.
key
)
)
{
locationToExitingPageRoute
[
previousOldPageRouteEntry
]
=
potentialEntryToRemove
;
assert
(()
{
potentialEntryToRemove
.
_debugWaitingForExitDecision
=
true
;
return
true
;
}());
}
previousOldPageRouteEntry
=
potentialEntryToRemove
;
}
// We've scanned the whole list.
assert
(
oldEntriesBottom
==
oldEntriesTop
+
1
);
assert
(
newPagesBottom
==
newPagesTop
+
1
);
newPagesTop
=
widget
.
pages
.
length
-
1
;
oldEntriesTop
=
_history
.
length
-
1
;
// Verifies we either reach the bottom or the oldEntriesBottom must be updatable
// by newPagesBottom.
assert
(()
{
if
(
oldEntriesBottom
<=
oldEntriesTop
)
return
newPagesBottom
<=
newPagesTop
&&
_history
[
oldEntriesBottom
].
hasPage
&&
_history
[
oldEntriesBottom
].
canUpdateFrom
(
widget
.
pages
[
newPagesBottom
]);
else
return
newPagesBottom
>
newPagesTop
;
}());
// Updates the top of the list.
while
((
oldEntriesBottom
<=
oldEntriesTop
)
&&
(
newPagesBottom
<=
newPagesTop
))
{
final
_RouteEntry
oldEntry
=
_history
[
oldEntriesBottom
];
assert
(
oldEntry
!=
null
&&
oldEntry
.
currentState
!=
_RouteLifecycle
.
disposed
);
if
(!
oldEntry
.
hasPage
)
{
assert
(
previousOldPageRouteEntry
!=
null
);
final
List
<
_RouteEntry
>
pagelessRoutes
=
pageRouteToPagelessRoutes
.
putIfAbsent
(
previousOldPageRouteEntry
,
()
=>
<
_RouteEntry
>[]
);
pagelessRoutes
.
add
(
oldEntry
);
continue
;
}
previousOldPageRouteEntry
=
oldEntry
;
final
Page
<
dynamic
>
newPage
=
widget
.
pages
[
newPagesBottom
];
assert
(
oldEntry
.
canUpdateFrom
(
newPage
));
oldEntry
.
route
.
_updateSettings
(
newPage
);
newHistory
.
add
(
oldEntry
);
oldEntriesBottom
+=
1
;
newPagesBottom
+=
1
;
}
// Finally, uses transition delegate to make explicit decision if needed.
needsExplicitDecision
=
needsExplicitDecision
||
locationToExitingPageRoute
.
isNotEmpty
;
Iterable
<
_RouteEntry
>
results
=
newHistory
;
if
(
needsExplicitDecision
)
{
results
=
widget
.
transitionDelegate
.
_transition
(
newPageRouteHistory:
newHistory
,
locationToExitingPageRoute:
locationToExitingPageRoute
,
pageRouteToPagelessRoutes:
pageRouteToPagelessRoutes
,
).
cast
<
_RouteEntry
>();
}
_history
=
<
_RouteEntry
>[];
// Adds the leading pageless routes if there is any.
if
(
pageRouteToPagelessRoutes
.
containsKey
(
null
))
{
_history
.
addAll
(
pageRouteToPagelessRoutes
[
null
]);
}
for
(
final
_RouteEntry
result
in
results
)
{
_history
.
add
(
result
);
if
(
pageRouteToPagelessRoutes
.
containsKey
(
result
))
{
_history
.
addAll
(
pageRouteToPagelessRoutes
[
result
]);
}
}
assert
(()
{
_debugUpdatingPage
=
false
;
return
true
;}());
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
_flushHistoryUpdates
();
assert
(()
{
_debugLocked
=
false
;
return
true
;
}());
}
void
_flushHistoryUpdates
({
bool
rearrangeOverlay
=
true
})
{
void
_flushHistoryUpdates
({
bool
rearrangeOverlay
=
true
})
{
assert
(
_debugLocked
&&
!
_debugUpdatingPage
);
assert
(
_debugLocked
);
// Clean up the list, sending updates to the routes that changed. Notably,
// Clean up the list, sending updates to the routes that changed. Notably,
// we don't send the didChangePrevious/didChangeNext updates to those that
// we don't send the didChangePrevious/didChangeNext updates to those that
// did not change at this point, because we're not yet sure exactly what the
// did not change at this point, because we're not yet sure exactly what the
...
@@ -2941,7 +1987,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -2941,7 +1987,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
_RouteEntry
next
;
_RouteEntry
next
;
_RouteEntry
entry
=
_history
[
index
];
_RouteEntry
entry
=
_history
[
index
];
_RouteEntry
previous
=
index
>
0
?
_history
[
index
-
1
]
:
null
;
_RouteEntry
previous
=
index
>
0
?
_history
[
index
-
1
]
:
null
;
bool
canRemove
OrAdd
=
false
;
// Whether there is a fully opaque route on top to silently remove or add route underneath.
bool
canRemove
=
false
;
Route
<
dynamic
>
poppedRoute
;
// The route that should trigger didPopNext on the top active route.
Route
<
dynamic
>
poppedRoute
;
// The route that should trigger didPopNext on the top active route.
bool
seenTopActiveRoute
=
false
;
// Whether we've seen the route that would get didPopNext.
bool
seenTopActiveRoute
=
false
;
// Whether we've seen the route that would get didPopNext.
final
List
<
_RouteEntry
>
toBeDisposed
=
<
_RouteEntry
>[];
final
List
<
_RouteEntry
>
toBeDisposed
=
<
_RouteEntry
>[];
...
@@ -2951,21 +1997,12 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -2951,21 +1997,12 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
assert
(
rearrangeOverlay
);
assert
(
rearrangeOverlay
);
entry
.
handleAdd
(
entry
.
handleAdd
(
navigator:
this
,
navigator:
this
,
previous:
previous
?.
route
,
previousPresent:
_getRouteBefore
(
index
-
1
,
_RouteEntry
.
isPresentPredicate
)?.
route
,
isNewFirst:
next
==
null
,
);
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
adding
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
idle
);
continue
;
continue
;
case
_RouteLifecycle
.
adding
:
if
(
canRemoveOrAdd
||
next
==
null
)
{
entry
.
didAdd
(
navigator:
this
,
previous:
previous
?.
route
,
previousPresent:
_getRouteBefore
(
index
-
1
,
_RouteEntry
.
isPresentPredicate
)?.
route
,
isNewFirst:
next
==
null
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
idle
);
continue
;
}
break
;
case
_RouteLifecycle
.
push
:
case
_RouteLifecycle
.
push
:
case
_RouteLifecycle
.
pushReplace
:
case
_RouteLifecycle
.
pushReplace
:
case
_RouteLifecycle
.
replace
:
case
_RouteLifecycle
.
replace
:
...
@@ -2994,7 +2031,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -2994,7 +2031,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
seenTopActiveRoute
=
true
;
seenTopActiveRoute
=
true
;
// This route is idle, so we are allowed to remove subsequent (earlier)
// This route is idle, so we are allowed to remove subsequent (earlier)
// routes that are waiting to be removed silently:
// routes that are waiting to be removed silently:
canRemove
OrAdd
=
true
;
canRemove
=
true
;
break
;
break
;
case
_RouteLifecycle
.
pop
:
case
_RouteLifecycle
.
pop
:
if
(!
seenTopActiveRoute
)
{
if
(!
seenTopActiveRoute
)
{
...
@@ -3007,7 +2044,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -3007,7 +2044,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
previousPresent:
_getRouteBefore
(
index
,
_RouteEntry
.
willBePresentPredicate
)?.
route
,
previousPresent:
_getRouteBefore
(
index
,
_RouteEntry
.
willBePresentPredicate
)?.
route
,
);
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
popping
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
popping
);
canRemoveOrAdd
=
true
;
break
;
break
;
case
_RouteLifecycle
.
popping
:
case
_RouteLifecycle
.
popping
:
// Will exit this state when animation completes.
// Will exit this state when animation completes.
...
@@ -3025,7 +2061,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -3025,7 +2061,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
assert
(
entry
.
currentState
==
_RouteLifecycle
.
removing
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
removing
);
continue
;
continue
;
case
_RouteLifecycle
.
removing
:
case
_RouteLifecycle
.
removing
:
if
(!
canRemove
OrAdd
&&
next
!=
null
)
{
if
(!
canRemove
&&
next
!=
null
)
{
// We aren't allowed to remove this route yet.
// We aren't allowed to remove this route yet.
break
;
break
;
}
}
...
@@ -3037,7 +2073,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -3037,7 +2073,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
entry
=
next
;
entry
=
next
;
break
;
break
;
case
_RouteLifecycle
.
disposed
:
case
_RouteLifecycle
.
disposed
:
case
_RouteLifecycle
.
staging
:
assert
(
false
);
assert
(
false
);
break
;
break
;
}
}
...
@@ -3073,11 +2108,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -3073,11 +2108,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
int
index
=
_history
.
length
-
1
;
int
index
=
_history
.
length
-
1
;
while
(
index
>=
0
)
{
while
(
index
>=
0
)
{
final
_RouteEntry
entry
=
_history
[
index
];
final
_RouteEntry
entry
=
_history
[
index
];
if
(!
entry
.
suitableForAnnouncement
)
{
final
_RouteEntry
next
=
_getRouteAfter
(
index
+
1
,
_RouteEntry
.
isPresentPredicate
);
index
-=
1
;
continue
;
}
final
_RouteEntry
next
=
_getRouteAfter
(
index
+
1
,
_RouteEntry
.
suitableForTransitionAnimationPredicate
);
if
(
next
?.
route
!=
entry
.
lastAnnouncedNextRoute
)
{
if
(
next
?.
route
!=
entry
.
lastAnnouncedNextRoute
)
{
if
(
entry
.
shouldAnnounceChangeToNext
(
next
?.
route
))
{
if
(
entry
.
shouldAnnounceChangeToNext
(
next
?.
route
))
{
...
@@ -3085,7 +2116,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -3085,7 +2116,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
}
}
entry
.
lastAnnouncedNextRoute
=
next
?.
route
;
entry
.
lastAnnouncedNextRoute
=
next
?.
route
;
}
}
final
_RouteEntry
previous
=
_getRouteBefore
(
index
-
1
,
_RouteEntry
.
suitableForTransitionAnimation
Predicate
);
final
_RouteEntry
previous
=
_getRouteBefore
(
index
-
1
,
_RouteEntry
.
isPresent
Predicate
);
if
(
previous
?.
route
!=
entry
.
lastAnnouncedPreviousRoute
)
{
if
(
previous
?.
route
!=
entry
.
lastAnnouncedPreviousRoute
)
{
entry
.
route
.
didChangePrevious
(
previous
?.
route
);
entry
.
route
.
didChangePrevious
(
previous
?.
route
);
entry
.
lastAnnouncedPreviousRoute
=
previous
?.
route
;
entry
.
lastAnnouncedPreviousRoute
=
previous
?.
route
;
...
@@ -3584,12 +2615,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -3584,12 +2615,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
true
;
return
true
;
}());
}());
final
_RouteEntry
entry
=
_history
.
lastWhere
(
_RouteEntry
.
isPresentPredicate
);
final
_RouteEntry
entry
=
_history
.
lastWhere
(
_RouteEntry
.
isPresentPredicate
);
if
(
entry
.
hasPage
)
{
entry
.
pop
<
T
>(
result
);
if
(
widget
.
onPopPage
(
entry
.
route
,
result
))
entry
.
currentState
=
_RouteLifecycle
.
pop
;
}
else
{
entry
.
pop
<
T
>(
result
);
}
if
(
entry
.
currentState
==
_RouteLifecycle
.
pop
)
{
if
(
entry
.
currentState
==
_RouteLifecycle
.
pop
)
{
// Flush the history if the route actually wants to be popped (the pop
// Flush the history if the route actually wants to be popped (the pop
// wasn't handled internally).
// wasn't handled internally).
...
...
packages/flutter/test/widgets/navigator_test.dart
View file @
aee9e94c
...
@@ -1708,666 +1708,6 @@ void main() {
...
@@ -1708,666 +1708,6 @@ void main() {
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
tickCount
,
4
);
expect
(
tickCount
,
4
);
});
});
group
(
'Page api'
,
(){
Widget
buildNavigator
(
List
<
Page
<
dynamic
>>
pages
,
PopPageCallback
onPopPage
,
[
GlobalKey
<
NavigatorState
>
key
,
TransitionDelegate
<
dynamic
>
transitionDelegate
])
{
return
MediaQuery
(
data:
MediaQueryData
.
fromWindow
(
WidgetsBinding
.
instance
.
window
),
child:
Localizations
(
locale:
const
Locale
(
'en'
,
'US'
),
delegates:
const
<
LocalizationsDelegate
<
dynamic
>>[
DefaultMaterialLocalizations
.
delegate
,
DefaultWidgetsLocalizations
.
delegate
],
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Navigator
(
key:
key
,
pages:
pages
,
onPopPage:
onPopPage
,
transitionDelegate:
transitionDelegate
??
const
DefaultTransitionDelegate
<
dynamic
>(),
),
),
),
);
}
testWidgets
(
'can initialize with pages list'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
NavigatorState
>
navigator
=
GlobalKey
<
NavigatorState
>();
final
List
<
TestPage
>
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'1'
),
name:
'initial'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'2'
),
name:
'second'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'3'
),
name:
'third'
),
];
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
));
expect
(
find
.
text
(
'third'
),
findsOneWidget
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'initial'
),
findsNothing
);
navigator
.
currentState
.
pop
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'third'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsOneWidget
);
expect
(
find
.
text
(
'initial'
),
findsNothing
);
navigator
.
currentState
.
pop
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'third'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'initial'
),
findsOneWidget
);
});
testWidgets
(
'can push and pop pages using page api'
,
(
WidgetTester
tester
)
async
{
Animation
<
double
>
secondaryAnimationOfRouteOne
;
Animation
<
double
>
primaryAnimationOfRouteOne
;
Animation
<
double
>
secondaryAnimationOfRouteTwo
;
Animation
<
double
>
primaryAnimationOfRouteTwo
;
Animation
<
double
>
secondaryAnimationOfRouteThree
;
Animation
<
double
>
primaryAnimationOfRouteThree
;
final
GlobalKey
<
NavigatorState
>
navigator
=
GlobalKey
<
NavigatorState
>();
List
<
Page
<
dynamic
>>
myPages
=
<
Page
<
dynamic
>>[
CustomBuilderPage
<
void
>(
key:
const
ValueKey
<
String
>(
'1'
),
name:
'initial'
,
routeBuilder:
(
BuildContext
context
,
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
_
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
secondaryAnimationOfRouteOne
=
secondaryAnimation
;
primaryAnimationOfRouteOne
=
animation
;
return
const
Text
(
'initial'
);
},
);
},
),
];
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
));
expect
(
find
.
text
(
'initial'
),
findsOneWidget
);
myPages
=
<
Page
<
dynamic
>>[
CustomBuilderPage
<
void
>(
key:
const
ValueKey
<
String
>(
'1'
),
name:
'initial'
,
routeBuilder:
(
BuildContext
context
,
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
_
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
secondaryAnimationOfRouteOne
=
secondaryAnimation
;
primaryAnimationOfRouteOne
=
animation
;
return
const
Text
(
'initial'
);
},
);
},
),
CustomBuilderPage
<
void
>(
key:
const
ValueKey
<
String
>(
'2'
),
name:
'second'
,
routeBuilder:
(
BuildContext
context
,
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
_
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
secondaryAnimationOfRouteTwo
=
secondaryAnimation
;
primaryAnimationOfRouteTwo
=
animation
;
return
const
Text
(
'second'
);
},
);
},
),
CustomBuilderPage
<
void
>(
key:
const
ValueKey
<
String
>(
'3'
),
name:
'third'
,
routeBuilder:
(
BuildContext
context
,
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
_
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
secondaryAnimationOfRouteThree
=
secondaryAnimation
;
primaryAnimationOfRouteThree
=
animation
;
return
const
Text
(
'third'
);
},
);
},
)
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
// The third page is transitioning, and the secondary animation of first
// page should chain with the third page. The animation of second page
// won't start until the third page finishes transition.
expect
(
secondaryAnimationOfRouteOne
.
value
,
primaryAnimationOfRouteThree
.
value
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
dismissed
);
expect
(
secondaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
forward
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
30
));
expect
(
secondaryAnimationOfRouteOne
.
value
,
primaryAnimationOfRouteThree
.
value
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
dismissed
);
expect
(
secondaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteThree
.
value
,
0.1
);
await
tester
.
pumpAndSettle
();
// After transition finishes, the routes' animations are correctly chained.
expect
(
secondaryAnimationOfRouteOne
.
value
,
primaryAnimationOfRouteTwo
.
value
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
value
,
primaryAnimationOfRouteThree
.
value
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
completed
);
expect
(
find
.
text
(
'third'
),
findsOneWidget
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'initial'
),
findsNothing
);
// Starts pops the pages using page api and verify the animations chain
// correctly.
myPages
=
<
Page
<
dynamic
>>[
CustomBuilderPage
<
void
>(
key:
const
ValueKey
<
String
>(
'1'
),
name:
'initial'
,
routeBuilder:
(
BuildContext
context
,
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
_
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
secondaryAnimationOfRouteOne
=
secondaryAnimation
;
primaryAnimationOfRouteOne
=
animation
;
return
const
Text
(
'initial'
);
},
);
},
),
CustomBuilderPage
<
void
>(
key:
const
ValueKey
<
String
>(
'2'
),
name:
'second'
,
routeBuilder:
(
BuildContext
context
,
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
_
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
secondaryAnimationOfRouteTwo
=
secondaryAnimation
;
primaryAnimationOfRouteTwo
=
animation
;
return
const
Text
(
'second'
);
},
);
},
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
30
));
expect
(
secondaryAnimationOfRouteOne
.
value
,
primaryAnimationOfRouteTwo
.
value
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
value
,
primaryAnimationOfRouteThree
.
value
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteThree
.
value
,
0.9
);
await
tester
.
pumpAndSettle
();
expect
(
secondaryAnimationOfRouteOne
.
value
,
primaryAnimationOfRouteTwo
.
value
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
value
,
primaryAnimationOfRouteThree
.
value
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
dismissed
);
});
testWidgets
(
'can modify routes history and secondary animation still works'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
NavigatorState
>
navigator
=
GlobalKey
<
NavigatorState
>();
Animation
<
double
>
secondaryAnimationOfRouteOne
;
Animation
<
double
>
primaryAnimationOfRouteOne
;
Animation
<
double
>
secondaryAnimationOfRouteTwo
;
Animation
<
double
>
primaryAnimationOfRouteTwo
;
Animation
<
double
>
secondaryAnimationOfRouteThree
;
Animation
<
double
>
primaryAnimationOfRouteThree
;
List
<
Page
<
dynamic
>>
myPages
=
<
CustomBuilderPage
<
void
>>[
CustomBuilderPage
<
void
>(
key:
const
ValueKey
<
String
>(
'1'
),
name:
'initial'
,
routeBuilder:
(
BuildContext
context
,
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
_
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
secondaryAnimationOfRouteOne
=
secondaryAnimation
;
primaryAnimationOfRouteOne
=
animation
;
return
const
Text
(
'initial'
);
},
);
},
),
CustomBuilderPage
<
void
>(
key:
const
ValueKey
<
String
>(
'2'
),
name:
'second'
,
routeBuilder:
(
BuildContext
context
,
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
_
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
secondaryAnimationOfRouteTwo
=
secondaryAnimation
;
primaryAnimationOfRouteTwo
=
animation
;
return
const
Text
(
'second'
);
},
);
},
),
CustomBuilderPage
<
void
>(
key:
const
ValueKey
<
String
>(
'3'
),
name:
'third'
,
routeBuilder:
(
BuildContext
context
,
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
_
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
secondaryAnimationOfRouteThree
=
secondaryAnimation
;
primaryAnimationOfRouteThree
=
animation
;
return
const
Text
(
'third'
);
},
);
},
),
];
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
));
expect
(
find
.
text
(
'third'
),
findsOneWidget
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'initial'
),
findsNothing
);
expect
(
secondaryAnimationOfRouteOne
.
value
,
primaryAnimationOfRouteTwo
.
value
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
value
,
primaryAnimationOfRouteThree
.
value
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
completed
);
myPages
=
myPages
.
reversed
.
toList
();
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
// Reversed routes are still chained up correctly.
expect
(
secondaryAnimationOfRouteThree
.
value
,
primaryAnimationOfRouteTwo
.
value
);
expect
(
primaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
value
,
primaryAnimationOfRouteOne
.
value
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
completed
);
navigator
.
currentState
.
pop
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
30
));
expect
(
secondaryAnimationOfRouteThree
.
value
,
primaryAnimationOfRouteTwo
.
value
);
expect
(
primaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
value
,
primaryAnimationOfRouteOne
.
value
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteOne
.
value
,
0.9
);
await
tester
.
pumpAndSettle
();
expect
(
secondaryAnimationOfRouteThree
.
value
,
primaryAnimationOfRouteTwo
.
value
);
expect
(
primaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
value
,
primaryAnimationOfRouteOne
.
value
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
dismissed
);
navigator
.
currentState
.
pop
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
30
));
expect
(
secondaryAnimationOfRouteThree
.
value
,
primaryAnimationOfRouteTwo
.
value
);
expect
(
primaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
value
,
primaryAnimationOfRouteOne
.
value
);
expect
(
primaryAnimationOfRouteTwo
.
value
,
0.9
);
expect
(
secondaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
dismissed
);
await
tester
.
pumpAndSettle
();
expect
(
secondaryAnimationOfRouteThree
.
value
,
primaryAnimationOfRouteTwo
.
value
);
expect
(
primaryAnimationOfRouteThree
.
status
,
AnimationStatus
.
completed
);
expect
(
secondaryAnimationOfRouteTwo
.
value
,
primaryAnimationOfRouteOne
.
value
);
expect
(
primaryAnimationOfRouteTwo
.
status
,
AnimationStatus
.
dismissed
);
expect
(
secondaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
dismissed
);
expect
(
primaryAnimationOfRouteOne
.
status
,
AnimationStatus
.
dismissed
);
});
testWidgets
(
'can work with pageless route'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
NavigatorState
>
navigator
=
GlobalKey
<
NavigatorState
>();
List
<
TestPage
>
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
));
expect
(
find
.
text
(
'second'
),
findsOneWidget
);
expect
(
find
.
text
(
'initial'
),
findsNothing
);
// Pushes two pageless routes to second page route
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'second-pageless1'
),
settings:
null
,
)
);
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'second-pageless2'
),
settings:
null
,
)
);
await
tester
.
pumpAndSettle
();
// Now the history should look like
// [initial, second, second-pageless1, second-pageless2].
expect
(
find
.
text
(
'initial'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless2'
),
findsOneWidget
);
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'1'
),
name:
'initial'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'2'
),
name:
'second'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'3'
),
name:
'third'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'initial'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless2'
),
findsNothing
);
expect
(
find
.
text
(
'third'
),
findsOneWidget
);
// Pushes one pageless routes to third page route
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'third-pageless1'
),
settings:
null
,
)
);
await
tester
.
pumpAndSettle
();
// Now the history should look like
// [initial, second, second-pageless1, second-pageless2, third, third-pageless1].
expect
(
find
.
text
(
'initial'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless2'
),
findsNothing
);
expect
(
find
.
text
(
'third'
),
findsNothing
);
expect
(
find
.
text
(
'third-pageless1'
),
findsOneWidget
);
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'1'
),
name:
'initial'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'3'
),
name:
'third'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'2'
),
name:
'second'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
// Swaps the order without any adding or removing should not trigger any
// transition. The routes should update without a pumpAndSettle
// Now the history should look like
// [initial, third, third-pageless1, second, second-pageless1, second-pageless2].
expect
(
find
.
text
(
'initial'
),
findsNothing
);
expect
(
find
.
text
(
'third'
),
findsNothing
);
expect
(
find
.
text
(
'third-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless2'
),
findsOneWidget
);
// Pops the route one by one to make sure the order is correct.
navigator
.
currentState
.
pop
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'initial'
),
findsNothing
);
expect
(
find
.
text
(
'third'
),
findsNothing
);
expect
(
find
.
text
(
'third-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless1'
),
findsOneWidget
);
expect
(
find
.
text
(
'second-pageless2'
),
findsNothing
);
expect
(
myPages
.
length
,
3
);
navigator
.
currentState
.
pop
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'initial'
),
findsNothing
);
expect
(
find
.
text
(
'third'
),
findsNothing
);
expect
(
find
.
text
(
'third-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsOneWidget
);
expect
(
find
.
text
(
'second-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless2'
),
findsNothing
);
expect
(
myPages
.
length
,
3
);
navigator
.
currentState
.
pop
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'initial'
),
findsNothing
);
expect
(
find
.
text
(
'third'
),
findsNothing
);
expect
(
find
.
text
(
'third-pageless1'
),
findsOneWidget
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless2'
),
findsNothing
);
expect
(
myPages
.
length
,
2
);
navigator
.
currentState
.
pop
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'initial'
),
findsNothing
);
expect
(
find
.
text
(
'third'
),
findsOneWidget
);
expect
(
find
.
text
(
'third-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless2'
),
findsNothing
);
expect
(
myPages
.
length
,
2
);
navigator
.
currentState
.
pop
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'initial'
),
findsOneWidget
);
expect
(
find
.
text
(
'third'
),
findsNothing
);
expect
(
find
.
text
(
'third-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless1'
),
findsNothing
);
expect
(
find
.
text
(
'second-pageless2'
),
findsNothing
);
expect
(
myPages
.
length
,
1
);
});
testWidgets
(
'complex case 1'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
NavigatorState
>
navigator
=
GlobalKey
<
NavigatorState
>();
List
<
TestPage
>
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'1'
),
name:
'initial'
),
];
bool
onPopPage
(
Route
<
dynamic
>
route
,
dynamic
result
)
{
myPages
.
removeWhere
((
Page
<
dynamic
>
page
)
=>
route
.
settings
==
page
);
return
route
.
didPop
(
result
);
}
// Add initial page route with one pageless route.
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
bool
initialPageless1Completed
=
false
;
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'initial-pageless1'
),
settings:
null
,
)
).
then
((
_
)
=>
initialPageless1Completed
=
true
);
await
tester
.
pumpAndSettle
();
// Pushes second page route with two pageless routes.
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
));
await
tester
.
pumpAndSettle
();
bool
secondPageless1Completed
=
false
;
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'second-pageless1'
),
settings:
null
,
)
).
then
((
_
)
=>
secondPageless1Completed
=
true
);
await
tester
.
pumpAndSettle
();
bool
secondPageless2Completed
=
false
;
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'second-pageless2'
),
settings:
null
,
)
).
then
((
_
)
=>
secondPageless2Completed
=
true
);
await
tester
.
pumpAndSettle
();
// Pushes third page route with one pageless route.
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'1'
),
name:
'initial'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'2'
),
name:
'second'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'3'
),
name:
'third'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
await
tester
.
pumpAndSettle
();
bool
thirdPageless1Completed
=
false
;
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'third-pageless1'
),
settings:
null
,
)
).
then
((
_
)
=>
thirdPageless1Completed
=
true
);
await
tester
.
pumpAndSettle
();
// Nothing has been popped.
expect
(
initialPageless1Completed
,
false
);
expect
(
secondPageless1Completed
,
false
);
expect
(
secondPageless2Completed
,
false
);
expect
(
thirdPageless1Completed
,
false
);
// Switches order and removes the initial page route.
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'3'
),
name:
'third'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'2'
),
name:
'second'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
// The pageless route of initial page route should be completed.
expect
(
initialPageless1Completed
,
true
);
expect
(
secondPageless1Completed
,
false
);
expect
(
secondPageless2Completed
,
false
);
expect
(
thirdPageless1Completed
,
false
);
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'3'
),
name:
'third'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
await
tester
.
pumpAndSettle
();
expect
(
secondPageless1Completed
,
true
);
expect
(
secondPageless2Completed
,
true
);
expect
(
thirdPageless1Completed
,
false
);
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'4'
),
name:
'forth'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
));
expect
(
thirdPageless1Completed
,
true
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'forth'
),
findsOneWidget
);
});
testWidgets
(
'complex case 1 - with always remove transition delegate'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
NavigatorState
>
navigator
=
GlobalKey
<
NavigatorState
>();
final
AlwaysRemoveTransitionDelegate
transitionDelegate
=
AlwaysRemoveTransitionDelegate
();
List
<
TestPage
>
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'1'
),
name:
'initial'
),
];
bool
onPopPage
(
Route
<
dynamic
>
route
,
dynamic
result
)
{
myPages
.
removeWhere
((
Page
<
dynamic
>
page
)
=>
route
.
settings
==
page
);
return
route
.
didPop
(
result
);
}
// Add initial page route with one pageless route.
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
,
transitionDelegate
));
bool
initialPageless1Completed
=
false
;
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'initial-pageless1'
),
settings:
null
,
)
).
then
((
_
)
=>
initialPageless1Completed
=
true
);
await
tester
.
pumpAndSettle
();
// Pushes second page route with two pageless routes.
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
,
transitionDelegate
));
bool
secondPageless1Completed
=
false
;
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'second-pageless1'
),
settings:
null
,
)
).
then
((
_
)
=>
secondPageless1Completed
=
true
);
await
tester
.
pumpAndSettle
();
bool
secondPageless2Completed
=
false
;
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'second-pageless2'
),
settings:
null
,
)
).
then
((
_
)
=>
secondPageless2Completed
=
true
);
await
tester
.
pumpAndSettle
();
// Pushes third page route with one pageless route.
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'1'
),
name:
'initial'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'2'
),
name:
'second'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'3'
),
name:
'third'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
,
transitionDelegate
));
bool
thirdPageless1Completed
=
false
;
navigator
.
currentState
.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
const
Text
(
'third-pageless1'
),
settings:
null
,
)
).
then
((
_
)
=>
thirdPageless1Completed
=
true
);
await
tester
.
pumpAndSettle
();
// Nothing has been popped.
expect
(
initialPageless1Completed
,
false
);
expect
(
secondPageless1Completed
,
false
);
expect
(
secondPageless2Completed
,
false
);
expect
(
thirdPageless1Completed
,
false
);
// Switches order and removes the initial page route.
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'3'
),
name:
'third'
),
const
TestPage
(
key:
ValueKey
<
String
>(
'2'
),
name:
'second'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
,
transitionDelegate
));
// The pageless route of initial page route should be removed without complete.
expect
(
initialPageless1Completed
,
false
);
expect
(
secondPageless1Completed
,
false
);
expect
(
secondPageless2Completed
,
false
);
expect
(
thirdPageless1Completed
,
false
);
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'3'
),
name:
'third'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
,
transitionDelegate
));
await
tester
.
pumpAndSettle
();
expect
(
initialPageless1Completed
,
false
);
expect
(
secondPageless1Completed
,
false
);
expect
(
secondPageless2Completed
,
false
);
expect
(
thirdPageless1Completed
,
false
);
myPages
=
<
TestPage
>[
const
TestPage
(
key:
ValueKey
<
String
>(
'4'
),
name:
'forth'
),
];
await
tester
.
pumpWidget
(
buildNavigator
(
myPages
,
onPopPage
,
navigator
,
transitionDelegate
));
await
tester
.
pump
();
expect
(
initialPageless1Completed
,
false
);
expect
(
secondPageless1Completed
,
false
);
expect
(
secondPageless2Completed
,
false
);
expect
(
thirdPageless1Completed
,
false
);
expect
(
find
.
text
(
'forth'
),
findsOneWidget
);
});
});
}
}
class
_TickingWidget
extends
StatefulWidget
{
class
_TickingWidget
extends
StatefulWidget
{
...
@@ -2402,62 +1742,6 @@ class _TickingWidgetState extends State<_TickingWidget> with SingleTickerProvide
...
@@ -2402,62 +1742,6 @@ class _TickingWidgetState extends State<_TickingWidget> with SingleTickerProvide
}
}
}
}
class
AlwaysRemoveTransitionDelegate
extends
TransitionDelegate
<
void
>
{
@override
Iterable
<
RouteTransitionRecord
>
resolve
({
List
<
RouteTransitionRecord
>
newPageRouteHistory
,
Map
<
RouteTransitionRecord
,
RouteTransitionRecord
>
locationToExitingPageRoute
,
Map
<
RouteTransitionRecord
,
List
<
RouteTransitionRecord
>>
pageRouteToPagelessRoutes
,
})
{
final
List
<
RouteTransitionRecord
>
results
=
<
RouteTransitionRecord
>[];
void
handleExitingRoute
(
RouteTransitionRecord
location
)
{
if
(!
locationToExitingPageRoute
.
containsKey
(
location
))
return
;
final
RouteTransitionRecord
exitingPageRoute
=
locationToExitingPageRoute
[
location
];
final
bool
hasPagelessRoute
=
pageRouteToPagelessRoutes
.
containsKey
(
exitingPageRoute
);
exitingPageRoute
.
markForRemove
();
results
.
add
(
exitingPageRoute
);
if
(
hasPagelessRoute
)
{
final
List
<
RouteTransitionRecord
>
pagelessRoutes
=
pageRouteToPagelessRoutes
[
exitingPageRoute
];
for
(
final
RouteTransitionRecord
pagelessRoute
in
pagelessRoutes
)
{
pagelessRoute
.
markForRemove
();
}
}
handleExitingRoute
(
exitingPageRoute
);
}
handleExitingRoute
(
null
);
for
(
final
RouteTransitionRecord
pageRoute
in
newPageRouteHistory
)
{
if
(
pageRoute
.
isEntering
)
{
pageRoute
.
markForAdd
();
}
results
.
add
(
pageRoute
);
handleExitingRoute
(
pageRoute
);
}
return
results
;
}
}
class
TestPage
extends
Page
<
void
>
{
const
TestPage
({
LocalKey
key
,
String
name
,
Object
arguments
,
})
:
super
(
key:
key
,
name:
name
,
arguments:
arguments
);
@override
Route
<
void
>
createRoute
(
BuildContext
context
)
{
return
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
Text
(
name
),
settings:
this
,
);
}
}
class
NoAnimationPageRoute
extends
PageRouteBuilder
<
void
>
{
class
NoAnimationPageRoute
extends
PageRouteBuilder
<
void
>
{
NoAnimationPageRoute
({
WidgetBuilder
pageBuilder
})
NoAnimationPageRoute
({
WidgetBuilder
pageBuilder
})
:
super
(
pageBuilder:
(
BuildContext
context
,
__
,
___
)
{
:
super
(
pageBuilder:
(
BuildContext
context
,
__
,
___
)
{
...
...
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