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
fc85492d
Unverified
Commit
fc85492d
authored
Oct 02, 2020
by
Michael Goderbauer
Committed by
GitHub
Oct 02, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make Navigator restorable (inkl. WidgetsApp, MaterialApp, CupertinoApp) (#65658)
parent
5d6321b5
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
3171 additions
and
170 deletions
+3171
-170
app.dart
packages/flutter/lib/src/cupertino/app.dart
+7
-0
app.dart
packages/flutter/lib/src/material/app.dart
+7
-0
app.dart
packages/flutter/lib/src/widgets/app.dart
+37
-12
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+1712
-68
restoration.dart
packages/flutter/lib/src/widgets/restoration.dart
+9
-9
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+56
-45
tab_test.dart
packages/flutter/test/cupertino/tab_test.dart
+4
-0
text_field_restoration_test.dart
...s/flutter/test/cupertino/text_field_restoration_test.dart
+15
-17
app_test.dart
packages/flutter/test/material/app_test.dart
+4
-0
debug_test.dart
packages/flutter/test/material/debug_test.dart
+8
-0
text_field_restoration_test.dart
...es/flutter/test/material/text_field_restoration_test.dart
+15
-17
navigator_restoration_test.dart
...ages/flutter/test/widgets/navigator_restoration_test.dart
+1295
-0
navigator_test.dart
packages/flutter/test/widgets/navigator_test.dart
+2
-2
No files found.
packages/flutter/lib/src/cupertino/app.dart
View file @
fc85492d
...
...
@@ -94,6 +94,7 @@ class CupertinoApp extends StatefulWidget {
this
.
debugShowCheckedModeBanner
=
true
,
this
.
shortcuts
,
this
.
actions
,
this
.
restorationScopeId
,
})
:
assert
(
routes
!=
null
),
assert
(
navigatorObservers
!=
null
),
assert
(
title
!=
null
),
...
...
@@ -132,6 +133,7 @@ class CupertinoApp extends StatefulWidget {
this
.
debugShowCheckedModeBanner
=
true
,
this
.
shortcuts
,
this
.
actions
,
this
.
restorationScopeId
,
})
:
assert
(
title
!=
null
),
assert
(
showPerformanceOverlay
!=
null
),
assert
(
checkerboardRasterCacheImages
!=
null
),
...
...
@@ -315,6 +317,9 @@ class CupertinoApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
final
Map
<
Type
,
Action
<
Intent
>>?
actions
;
/// {@macro flutter.widgets.widgetsApp.restorationScopeId}
final
String
?
restorationScopeId
;
@override
_CupertinoAppState
createState
()
=>
_CupertinoAppState
();
...
...
@@ -400,6 +405,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
inspectorSelectButtonBuilder:
_inspectorSelectButtonBuilder
,
shortcuts:
widget
.
shortcuts
,
actions:
widget
.
actions
,
restorationScopeId:
widget
.
restorationScopeId
,
);
}
return
WidgetsApp
(
...
...
@@ -433,6 +439,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
inspectorSelectButtonBuilder:
_inspectorSelectButtonBuilder
,
shortcuts:
widget
.
shortcuts
,
actions:
widget
.
actions
,
restorationScopeId:
widget
.
restorationScopeId
,
);
}
...
...
packages/flutter/lib/src/material/app.dart
View file @
fc85492d
...
...
@@ -197,6 +197,7 @@ class MaterialApp extends StatefulWidget {
this
.
debugShowCheckedModeBanner
=
true
,
this
.
shortcuts
,
this
.
actions
,
this
.
restorationScopeId
,
})
:
assert
(
routes
!=
null
),
assert
(
navigatorObservers
!=
null
),
assert
(
title
!=
null
),
...
...
@@ -241,6 +242,7 @@ class MaterialApp extends StatefulWidget {
this
.
debugShowCheckedModeBanner
=
true
,
this
.
shortcuts
,
this
.
actions
,
this
.
restorationScopeId
,
})
:
assert
(
routeInformationParser
!=
null
),
assert
(
routerDelegate
!=
null
),
assert
(
title
!=
null
),
...
...
@@ -620,6 +622,9 @@ class MaterialApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
final
Map
<
Type
,
Action
<
Intent
>>
actions
;
/// {@macro flutter.widgets.widgetsApp.restorationScopeId}
final
String
restorationScopeId
;
/// Turns on a [GridPaper] overlay that paints a baseline grid
/// Material apps.
///
...
...
@@ -780,6 +785,7 @@ class _MaterialAppState extends State<MaterialApp> {
inspectorSelectButtonBuilder:
_inspectorSelectButtonBuilder
,
shortcuts:
widget
.
shortcuts
,
actions:
widget
.
actions
,
restorationScopeId:
widget
.
restorationScopeId
,
);
}
...
...
@@ -814,6 +820,7 @@ class _MaterialAppState extends State<MaterialApp> {
inspectorSelectButtonBuilder:
_inspectorSelectButtonBuilder
,
shortcuts:
widget
.
shortcuts
,
actions:
widget
.
actions
,
restorationScopeId:
widget
.
restorationScopeId
,
);
}
...
...
packages/flutter/lib/src/widgets/app.dart
View file @
fc85492d
...
...
@@ -19,6 +19,7 @@ import 'media_query.dart';
import
'navigator.dart'
;
import
'pages.dart'
;
import
'performance_overlay.dart'
;
import
'restoration.dart'
;
import
'router.dart'
;
import
'scrollable.dart'
;
import
'semantics_debugger.dart'
;
...
...
@@ -195,6 +196,7 @@ class WidgetsApp extends StatefulWidget {
this
.
inspectorSelectButtonBuilder
,
this
.
shortcuts
,
this
.
actions
,
this
.
restorationScopeId
,
})
:
assert
(
navigatorObservers
!=
null
),
assert
(
routes
!=
null
),
assert
(
...
...
@@ -290,6 +292,7 @@ class WidgetsApp extends StatefulWidget {
this
.
inspectorSelectButtonBuilder
,
this
.
shortcuts
,
this
.
actions
,
this
.
restorationScopeId
,
})
:
assert
(
routeInformationParser
!=
null
&&
routerDelegate
!=
null
,
...
...
@@ -945,6 +948,24 @@ class WidgetsApp extends StatefulWidget {
/// {@endtemplate}
final
Map
<
Type
,
Action
<
Intent
>>?
actions
;
/// {@template flutter.widgets.widgetsApp.restorationScopeId}
/// The identifier to use for state restoration of this app.
///
/// Providing a restoration ID inserts a [RootRestorationScope] into the
/// widget hierarchy, which enables state restoration for descendant widgets.
///
/// Providing a restoration ID also enables the [Navigator] built by the
/// [WidgetsApp] to restore its state (i.e. to restore the history stack of
/// active [Route]s). See the documentation on [Navigator] for more details
/// around state restoration of [Route]s.
///
/// See also:
///
/// * [RestorationManager], which explains how state restoration works in
/// Flutter.
/// {@endtemplate}
final
String
?
restorationScopeId
;
/// If true, forces the performance overlay to be visible in all instances.
///
/// Used by the `showPerformanceOverlay` observatory extension.
...
...
@@ -1467,6 +1488,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
}
else
{
assert
(
_navigator
!=
null
);
routing
=
Navigator
(
restorationScopeId:
'nav'
,
key:
_navigator
,
initialRoute:
_initialRouteName
,
onGenerateRoute:
_onGenerateRoute
,
...
...
@@ -1573,18 +1595,21 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
:
_locale
!;
assert
(
_debugCheckLocalizations
(
appLocale
));
return
Shortcuts
(
shortcuts:
widget
.
shortcuts
??
WidgetsApp
.
defaultShortcuts
,
debugLabel:
'<Default WidgetsApp Shortcuts>'
,
child:
Actions
(
actions:
widget
.
actions
??
WidgetsApp
.
defaultActions
,
child:
FocusTraversalGroup
(
policy:
ReadingOrderTraversalPolicy
(),
child:
_MediaQueryFromWindow
(
child:
Localizations
(
locale:
appLocale
,
delegates:
_localizationsDelegates
.
toList
(),
child:
title
,
return
RootRestorationScope
(
restorationId:
widget
.
restorationScopeId
,
child:
Shortcuts
(
shortcuts:
widget
.
shortcuts
??
WidgetsApp
.
defaultShortcuts
,
debugLabel:
'<Default WidgetsApp Shortcuts>'
,
child:
Actions
(
actions:
widget
.
actions
??
WidgetsApp
.
defaultActions
,
child:
FocusTraversalGroup
(
policy:
ReadingOrderTraversalPolicy
(),
child:
_MediaQueryFromWindow
(
child:
Localizations
(
locale:
appLocale
,
delegates:
_localizationsDelegates
.
toList
(),
child:
title
,
),
),
),
),
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
fc85492d
...
...
@@ -6,6 +6,7 @@ import 'dart:async';
import
'dart:collection'
;
import
'dart:convert'
;
import
'dart:developer'
as
developer
;
import
'dart:ui'
as
ui
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
...
...
@@ -19,6 +20,8 @@ import 'focus_scope.dart';
import
'framework.dart'
;
import
'heroes.dart'
;
import
'overlay.dart'
;
import
'restoration.dart'
;
import
'restoration_properties.dart'
;
import
'routes.dart'
;
import
'ticker_provider.dart'
;
...
...
@@ -42,6 +45,18 @@ typedef RouteFactory = Route<dynamic>? Function(RouteSettings settings);
/// Used by [Navigator.onGenerateInitialRoutes].
typedef
RouteListFactory
=
List
<
Route
<
dynamic
>>
Function
(
NavigatorState
navigator
,
String
initialRoute
);
/// Creates a [Route] that is to be added to a [Navigator].
///
/// The route can be configured with the provided `arguments`. The provided
/// `context` is the `BuildContext` of the [Navigator] to which the route is
/// added.
///
/// Used by the restorable methods of the [Navigator] that add anonymous routes
/// (e.g. [NavigatorState.restorablePush]). For this use case, the
/// [RestorableRouteBuilder] must be static function as the [Navigator] will
/// call it again during state restoration to re-create the route.
typedef
RestorableRouteBuilder
<
T
>
=
Route
<
T
>
Function
(
BuildContext
context
,
Object
?
arguments
);
/// Signature for the [Navigator.popUntil] predicate argument.
typedef
RoutePredicate
=
bool
Function
(
Route
<
dynamic
>
route
);
...
...
@@ -137,6 +152,21 @@ abstract class Route<T> {
RouteSettings
get
settings
=>
_settings
;
RouteSettings
_settings
;
/// The restoration scope ID to be used for the [RestorationScope] surrounding
/// this route.
///
/// The restoration scope ID is null if restoration is currently disabled
/// for this route.
///
/// If the restoration scope ID changes (e.g. because restoration is enabled
/// or disabled) during the life of the route, the [ValueListenable] notifies
/// its listeners. As an example, the ID changes to null while the route is
/// transitioning off screen, which triggers a notification on this field. At
/// that point, the route is considered as no longer present for restoration
/// purposes and its state will not be restored.
ValueListenable
<
String
?>
get
restorationScopeId
=>
_restorationScopeId
;
final
ValueNotifier
<
String
?>
_restorationScopeId
=
ValueNotifier
<
String
?>(
null
);
void
_updateSettings
(
RouteSettings
newSettings
)
{
assert
(
newSettings
!=
null
);
if
(
_settings
!=
newSettings
)
{
...
...
@@ -145,6 +175,10 @@ abstract class Route<T> {
}
}
void
_updateRestorationId
(
String
?
restorationId
)
{
_restorationScopeId
.
value
=
restorationId
;
}
/// The overlay entries of this route.
///
/// These are typically populated by [install]. The [Navigator] is in charge
...
...
@@ -510,6 +544,7 @@ abstract class Page<T> extends RouteSettings {
this
.
key
,
String
?
name
,
Object
?
arguments
,
this
.
restorationId
,
})
:
super
(
name:
name
,
arguments:
arguments
);
/// The key associated with this page.
...
...
@@ -517,6 +552,17 @@ abstract class Page<T> extends RouteSettings {
/// This key will be used for comparing pages in [canUpdate].
final
LocalKey
?
key
;
/// Restoration ID to save and restore the state of the [Route] configured by
/// this page.
///
/// If no restoration ID is provided, the [Route] will not restore its state.
///
/// See also:
///
/// * [RestorationManager], which explains how state restoration works in
/// Flutter.
final
String
?
restorationId
;
/// Whether this page can be updated with the [other] page.
///
/// Two pages are consider updatable if they have same the [runtimeType] and
...
...
@@ -1354,6 +1400,30 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
/// [Navigator], especially in large `build` methods where nested [Navigator]s
/// are created. The [Builder] widget can be used to access a [BuildContext] at
/// a desired location in the widget subtree.
///
/// ## State Restoration
///
/// If provided with a [restorationScopeId] and when surrounded by a valid
/// [RestorationScope] the [Navigator] will restore its state by recreating
/// the current history stack of [Route]s during state restoration and by
/// restoring the internal state of those [Route]s. However, not all [Route]s
/// on the stack can be restored:
///
/// * [Page]-based routes restore their state if [Page.restorationId] is
/// provided.
/// * [Route]s added with the classic imperative API ([push], [pushNamed], and
/// friends) can never restore their state.
/// * A [Route] added with the restorable imperative API ([restorablePush],
/// [restorablePushNamed], and all other imperative methods with "restorable"
/// in their name) restores its state if all routes below it up to and
/// including the first [Page]-based route below it are restored. If there
/// is no [Page]-based route below it, it only restores its state if all
/// routes below it restore theirs.
///
/// If a [Route] is deemed restorable, the [Navigator] will set its
/// [Route.restorationScopeId] to a non-null value. Routes can use that ID to
/// store and restore their own state. As an example, the [ModalRoute] will
/// use this ID to create a [RestorationScope] for its content widgets.
class
Navigator
extends
StatefulWidget
{
/// Creates a widget that maintains a stack-based history of child widgets.
///
...
...
@@ -1372,6 +1442,7 @@ class Navigator extends StatefulWidget {
this
.
transitionDelegate
=
const
DefaultTransitionDelegate
<
dynamic
>(),
this
.
reportsRouteUpdateToEngine
=
false
,
this
.
observers
=
const
<
NavigatorObserver
>[],
this
.
restorationScopeId
,
})
:
assert
(
pages
!=
null
),
assert
(
onGenerateInitialRoutes
!=
null
),
assert
(
transitionDelegate
!=
null
),
...
...
@@ -1454,6 +1525,26 @@ class Navigator extends StatefulWidget {
/// A list of observers for this navigator.
final
List
<
NavigatorObserver
>
observers
;
/// Restoration ID to save and restore the state of the navigator, including
/// its history.
///
/// If a restoration ID is provided, the navigator will persist its internal
/// state (including the route history as well as the restorable state of the
/// routes) and restore it during state restoration.
///
/// If no restoration ID is provided, the route history stack will not be
/// restored and state restoration is disabled for the individual routes as
/// well.
///
/// The state is persisted in a [RestorationBucket] claimed from
/// the surrounding [RestorationScope] using the provided restoration ID.
///
/// See also:
///
/// * [RestorationManager], which explains how state restoration works in
/// Flutter.
final
String
?
restorationScopeId
;
/// The name for the default route of the application.
///
/// See also:
...
...
@@ -1576,6 +1667,11 @@ class Navigator extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePushNamed], which pushes a route that can be restored
/// during state restoration.
@optionalTypeArgs
static
Future
<
T
>
pushNamed
<
T
extends
Object
?>(
BuildContext
context
,
...
...
@@ -1585,6 +1681,61 @@ class Navigator extends StatefulWidget {
return
Navigator
.
of
(
context
)!.
pushNamed
<
T
>(
routeName
,
arguments:
arguments
);
}
/// Push a named route onto the navigator that most tightly encloses the given
/// context.
///
/// {@template flutter.widgets.navigator.restorablePushNamed}
/// Unlike [Route]s pushed via [pushNamed], [Route]s pushed with this method
/// are restored during state restoration according to the rules outlined
/// in the "State Restoration" section of [Navigator].
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.pushNamed}
///
/// {@template flutter.widgets.navigator.restorablePushNamed.arguments}
/// The provided `arguments` are passed to the pushed route via
/// [RouteSettings.arguments]. Any object that is serializable via the
/// [StandardMessageCodec] can be passed as `arguments`. Often, a Map is used
/// to pass key-value pairs.
///
/// The arguments may be used in [Navigator.onGenerateRoute] or
/// [Navigator.onUnknownRoute] to construct the route.
/// {@endtemplate}
///
/// {@template flutter.widgets.navigator.restorablePushNamed.returnValue}
/// The method returns an opaque ID for the pushed route that can be used by
/// the [RestorableRouteFuture] to gain access to the actual [Route] object
/// added to the navigator and its return value. You can ignore the return
/// value of this method, if you do not care about the route object or the
/// route's return value.
/// {@endtemplate}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _showParisWeather() {
/// Navigator.restorablePushNamed(
/// context,
/// '/weather',
/// arguments: <String, String>{
/// 'city': 'Paris',
/// 'country': 'France',
/// },
/// );
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
static
String
restorablePushNamed
<
T
extends
Object
?>(
BuildContext
context
,
String
routeName
,
{
Object
?
arguments
,
})
{
return
Navigator
.
of
(
context
)!.
restorablePushNamed
<
T
>(
routeName
,
arguments:
arguments
);
}
/// Replace the current route of the navigator that most tightly encloses the
/// given context by pushing the route named [routeName] and then disposing
/// the previous route once the new route has finished animating in.
...
...
@@ -1633,6 +1784,11 @@ class Navigator extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePushReplacementNamed], which pushes a replacement route that
/// can be restored during state restoration.
@optionalTypeArgs
static
Future
<
T
>
pushReplacementNamed
<
T
extends
Object
?,
TO
extends
Object
?>(
BuildContext
context
,
...
...
@@ -1643,6 +1799,42 @@ class Navigator extends StatefulWidget {
return
Navigator
.
of
(
context
)!.
pushReplacementNamed
<
T
,
TO
>(
routeName
,
arguments:
arguments
,
result:
result
);
}
/// Replace the current route of the navigator that most tightly encloses the
/// given context by pushing the route named [routeName] and then disposing
/// the previous route once the new route has finished animating in.
///
/// {@template flutter.widgets.navigator.restorablePushReplacementNamed}
/// Unlike [Route]s pushed via [pushReplacementNamed], [Route]s pushed with
/// this method are restored during state restoration according to the rules
/// outlined in the "State Restoration" section of [Navigator].
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.pushReplacementNamed}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _switchToAudioVolume() {
/// Navigator.restorablePushReplacementNamed(context, '/settings/volume');
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
static
String
restorablePushReplacementNamed
<
T
extends
Object
?,
TO
extends
Object
?>(
BuildContext
context
,
String
routeName
,
{
TO
?
result
,
Object
?
arguments
,
})
{
return
Navigator
.
of
(
context
)!.
restorablePushReplacementNamed
<
T
,
TO
>(
routeName
,
arguments:
arguments
,
result:
result
);
}
/// Pop the current route off the navigator that most tightly encloses the
/// given context and push a named route in its place.
///
...
...
@@ -1686,6 +1878,11 @@ class Navigator extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePopAndPushNamed], which pushes a new route that can be
/// restored during state restoration.
@optionalTypeArgs
static
Future
<
T
>
popAndPushNamed
<
T
extends
Object
?,
TO
extends
Object
?>(
BuildContext
context
,
...
...
@@ -1696,6 +1893,41 @@ class Navigator extends StatefulWidget {
return
Navigator
.
of
(
context
)!.
popAndPushNamed
<
T
,
TO
>(
routeName
,
arguments:
arguments
,
result:
result
);
}
/// Pop the current route off the navigator that most tightly encloses the
/// given context and push a named route in its place.
///
/// {@template flutter.widgets.navigator.restorablePopAndPushNamed}
/// Unlike [Route]s pushed via [popAndPushNamed], [Route]s pushed with
/// this method are restored during state restoration according to the rules
/// outlined in the "State Restoration" section of [Navigator].
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.popAndPushNamed}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _selectNetwork() {
/// Navigator.restorablePopAndPushNamed(context, '/settings/network');
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
static
String
restorablePopAndPushNamed
<
T
extends
Object
?,
TO
extends
Object
?>(
BuildContext
context
,
String
routeName
,
{
TO
?
result
,
Object
?
arguments
,
})
{
return
Navigator
.
of
(
context
)!.
restorablePopAndPushNamed
<
T
,
TO
>(
routeName
,
arguments:
arguments
,
result:
result
);
}
/// Push the route with the given name onto the navigator that most tightly
/// encloses the given context, and then remove all the previous routes until
/// the `predicate` returns true.
...
...
@@ -1750,6 +1982,11 @@ class Navigator extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
/// be restored during state restoration.
@optionalTypeArgs
static
Future
<
T
>
pushNamedAndRemoveUntil
<
T
extends
Object
?>(
BuildContext
context
,
...
...
@@ -1760,6 +1997,42 @@ class Navigator extends StatefulWidget {
return
Navigator
.
of
(
context
)!.
pushNamedAndRemoveUntil
<
T
>(
newRouteName
,
predicate
,
arguments:
arguments
);
}
/// Push the route with the given name onto the navigator that most tightly
/// encloses the given context, and then remove all the previous routes until
/// the `predicate` returns true.
///
/// {@template flutter.widgets.navigator.restorablePushNamedAndRemoveUntil}
/// Unlike [Route]s pushed via [pushNamedAndRemoveUntil], [Route]s pushed with
/// this method are restored during state restoration according to the rules
/// outlined in the "State Restoration" section of [Navigator].
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _resetToOverview() {
/// Navigator.restorablePushNamedAndRemoveUntil(context, '/overview', ModalRoute.withName('/'));
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
static
String
restorablePushNamedAndRemoveUntil
<
T
extends
Object
?>(
BuildContext
context
,
String
newRouteName
,
RoutePredicate
predicate
,
{
Object
?
arguments
,
})
{
return
Navigator
.
of
(
context
)!.
restorablePushNamedAndRemoveUntil
<
T
>(
newRouteName
,
predicate
,
arguments:
arguments
);
}
/// Push the given route onto the navigator that most tightly encloses the
/// given context.
///
...
...
@@ -1788,11 +2061,70 @@ class Navigator extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePush], which pushes a route that can be restored during
/// state restoration.
@optionalTypeArgs
static
Future
<
T
>
push
<
T
extends
Object
?>(
BuildContext
context
,
Route
<
T
>
route
)
{
return
Navigator
.
of
(
context
)!.
push
(
route
);
}
/// Push a new route onto the navigator that most tightly encloses the
/// given context.
///
/// {@template flutter.widgets.navigator.restorablePush}
/// Unlike [Route]s pushed via [push], [Route]s pushed with this method are
/// restored during state restoration according to the rules outlined in the
/// "State Restoration" section of [Navigator].
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.push}
///
/// {@template flutter.widgets.navigator.restorablePush.arguments}
/// The method takes a _static_ [RestorableRouteBuilder] as argument, which
/// must instantiate and return a new [Route] object that will be added to
/// the navigator. The provided `arguments` object is passed to the
/// `routeBuilder`. The navigator calls the static `routeBuilder` function
/// again during state restoration to re-create the route object.
///
/// Any object that is serializable via the [StandardMessageCodec] can be
/// passed as `arguments`. Often, a Map is used to pass key-value pairs.
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool dartpad --template=stateful_widget_material}
///
/// Typical usage is as follows:
///
/// ```dart
/// static Route _myRouteBuilder(BuildContext context, Object arguments) {
/// return MaterialPageRoute(
/// builder: (BuildContext context) => MyStatefulWidget(),
/// );
/// }
///
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: const Text('Sample Code'),
/// ),
/// floatingActionButton: FloatingActionButton(
/// onPressed: () => Navigator.restorablePush(context, _myRouteBuilder),
/// tooltip: 'Increment Counter',
/// child: const Icon(Icons.add),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
static
String
restorablePush
<
T
extends
Object
?>(
BuildContext
context
,
RestorableRouteBuilder
<
T
>
routeBuilder
,
{
Object
?
arguments
})
{
return
Navigator
.
of
(
context
)!.
restorablePush
(
routeBuilder
,
arguments:
arguments
);
}
/// Replace the current route of the navigator that most tightly encloses the
/// given context by pushing the given route and then disposing the previous
/// route once the new route has finished animating in.
...
...
@@ -1832,11 +2164,62 @@ class Navigator extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePushReplacement], which pushes a replacement route that can
/// be restored during state restoration.
@optionalTypeArgs
static
Future
<
T
>
pushReplacement
<
T
extends
Object
?,
TO
extends
Object
?>(
BuildContext
context
,
Route
<
T
>
newRoute
,
{
TO
?
result
})
{
return
Navigator
.
of
(
context
)!.
pushReplacement
<
T
,
TO
>(
newRoute
,
result:
result
);
}
/// Replace the current route of the navigator that most tightly encloses the
/// given context by pushing a new route and then disposing the previous
/// route once the new route has finished animating in.
///
/// {@template flutter.widgets.navigator.restorablePushReplacement}
/// Unlike [Route]s pushed via [pushReplacement], [Route]s pushed with this
/// method are restored during state restoration according to the rules
/// outlined in the "State Restoration" section of [Navigator].
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.pushReplacement}
///
/// {@macro flutter.widgets.navigator.restorablePush.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool dartpad --template=stateful_widget_material}
///
/// Typical usage is as follows:
///
/// ```dart
/// static Route _myRouteBuilder(BuildContext context, Object arguments) {
/// return MaterialPageRoute(
/// builder: (BuildContext context) => MyStatefulWidget(),
/// );
/// }
///
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: const Text('Sample Code'),
/// ),
/// floatingActionButton: FloatingActionButton(
/// onPressed: () => Navigator.restorablePushReplacement(context, _myRouteBuilder),
/// tooltip: 'Increment Counter',
/// child: const Icon(Icons.add),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
static
String
restorablePushReplacement
<
T
extends
Object
?,
TO
extends
Object
?>(
BuildContext
context
,
RestorableRouteBuilder
<
T
>
routeBuilder
,
{
TO
?
result
,
Object
?
arguments
})
{
return
Navigator
.
of
(
context
)!.
restorablePushReplacement
<
T
,
TO
>(
routeBuilder
,
result:
result
,
arguments:
arguments
);
}
/// Push the given route onto the navigator that most tightly encloses the
/// given context, and then remove all the previous routes until the
/// `predicate` returns true.
...
...
@@ -1886,11 +2269,66 @@ class Navigator extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePushAndRemoveUntil], which pushes a route that can be
/// restored during state restoration.
@optionalTypeArgs
static
Future
<
T
>
pushAndRemoveUntil
<
T
extends
Object
?>(
BuildContext
context
,
Route
<
T
>
newRoute
,
RoutePredicate
predicate
)
{
return
Navigator
.
of
(
context
)!.
pushAndRemoveUntil
<
T
>(
newRoute
,
predicate
);
}
/// Push a new route onto the navigator that most tightly encloses the
/// given context, and then remove all the previous routes until the
/// `predicate` returns true.
///
/// {@template flutter.widgets.navigator.restorablePushAndRemoveUntil}
/// Unlike [Route]s pushed via [pushAndRemoveUntil], [Route]s pushed with this
/// method are restored during state restoration according to the rules
/// outlined in the "State Restoration" section of [Navigator].
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.pushAndRemoveUntil}
///
/// {@macro flutter.widgets.navigator.restorablePush.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool dartpad --template=stateful_widget_material}
///
/// Typical usage is as follows:
///
/// ```dart
/// static Route _myRouteBuilder(BuildContext context, Object arguments) {
/// return MaterialPageRoute(
/// builder: (BuildContext context) => MyStatefulWidget(),
/// );
/// }
///
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: const Text('Sample Code'),
/// ),
/// floatingActionButton: FloatingActionButton(
/// onPressed: () => Navigator.restorablePushAndRemoveUntil(
/// context,
/// _myRouteBuilder,
/// ModalRoute.withName('/'),
/// ),
/// tooltip: 'Increment Counter',
/// child: const Icon(Icons.add),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
static
String
restorablePushAndRemoveUntil
<
T
extends
Object
?>(
BuildContext
context
,
RestorableRouteBuilder
<
T
>
newRouteBuilder
,
RoutePredicate
predicate
,
{
Object
?
arguments
})
{
return
Navigator
.
of
(
context
)!.
restorablePushAndRemoveUntil
<
T
>(
newRouteBuilder
,
predicate
,
arguments:
arguments
);
}
/// Replaces a route on the navigator that most tightly encloses the given
/// context with a new route.
///
...
...
@@ -1922,11 +2360,32 @@ class Navigator extends StatefulWidget {
///
/// * [replaceRouteBelow], which is the same but identifies the route to be
/// removed by reference to the route above it, rather than directly.
/// * [restorableReplace], which adds a replacement route that can be
/// restored during state restoration.
@optionalTypeArgs
static
void
replace
<
T
extends
Object
?>(
BuildContext
context
,
{
required
Route
<
dynamic
>
oldRoute
,
required
Route
<
T
>
newRoute
})
{
return
Navigator
.
of
(
context
)!.
replace
<
T
>(
oldRoute:
oldRoute
,
newRoute:
newRoute
);
}
/// Replaces a route on the navigator that most tightly encloses the given
/// context with a new route.
///
/// {@template flutter.widgets.navigator.restorableReplace}
/// Unlike [Route]s added via [replace], [Route]s added with this method are
/// restored during state restoration according to the rules outlined in the
/// "State Restoration" section of [Navigator].
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.replace}
///
/// {@macro flutter.widgets.navigator.restorablePush.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
@optionalTypeArgs
static
String
restorableReplace
<
T
extends
Object
?>(
BuildContext
context
,
{
required
Route
<
dynamic
>
oldRoute
,
required
RestorableRouteBuilder
<
T
>
newRouteBuilder
,
Object
?
arguments
})
{
return
Navigator
.
of
(
context
)!.
restorableReplace
<
T
>(
oldRoute:
oldRoute
,
newRouteBuilder:
newRouteBuilder
,
arguments:
arguments
);
}
/// Replaces a route on the navigator that most tightly encloses the given
/// context with a new route. The route to be replaced is the one below the
/// given `anchorRoute`.
...
...
@@ -1956,11 +2415,33 @@ class Navigator extends StatefulWidget {
///
/// * [replace], which is the same but identifies the route to be removed
/// directly.
/// * [restorableReplaceRouteBelow], which adds a replacement route that can
/// be restored during state restoration.
@optionalTypeArgs
static
void
replaceRouteBelow
<
T
extends
Object
?>(
BuildContext
context
,
{
required
Route
<
dynamic
>
anchorRoute
,
required
Route
<
T
>
newRoute
})
{
return
Navigator
.
of
(
context
)!.
replaceRouteBelow
<
T
>(
anchorRoute:
anchorRoute
,
newRoute:
newRoute
);
}
/// Replaces a route on the navigator that most tightly encloses the given
/// context with a new route. The route to be replaced is the one below the
/// given `anchorRoute`.
///
/// {@template flutter.widgets.navigator.restorableReplaceRouteBelow}
/// Unlike [Route]s added via [restorableReplaceRouteBelow], [Route]s added
/// with this method are restored during state restoration according to the
/// rules outlined in the "State Restoration" section of [Navigator].
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.replaceRouteBelow}
///
/// {@macro flutter.widgets.navigator.restorablePush.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
@optionalTypeArgs
static
String
restorableReplaceRouteBelow
<
T
extends
Object
?>(
BuildContext
context
,
{
required
Route
<
dynamic
>
anchorRoute
,
required
RestorableRouteBuilder
<
T
>
newRouteBuilder
,
Object
?
arguments
})
{
return
Navigator
.
of
(
context
)!.
restorableReplaceRouteBelow
<
T
>(
anchorRoute:
anchorRoute
,
newRouteBuilder:
newRouteBuilder
,
arguments:
arguments
);
}
/// Whether the navigator that most tightly encloses the given context can be
/// popped.
///
...
...
@@ -2346,6 +2827,7 @@ class _RouteEntry extends RouteTransitionRecord {
_RouteEntry
(
this
.
route
,
{
required
_RouteLifecycle
initialState
,
this
.
restorationInformation
,
})
:
assert
(
route
!=
null
),
assert
(
initialState
!=
null
),
assert
(
...
...
@@ -2359,6 +2841,7 @@ class _RouteEntry extends RouteTransitionRecord {
@override
final
Route
<
dynamic
>
route
;
final
_RestorationInformation
?
restorationInformation
;
static
Route
<
dynamic
>
notAnnounced
=
_NotAnnounced
();
...
...
@@ -2367,6 +2850,21 @@ class _RouteEntry extends RouteTransitionRecord {
Route
<
dynamic
>
lastAnnouncedPoppedNextRoute
=
notAnnounced
;
// last argument to Route.didPopNext
Route
<
dynamic
>?
lastAnnouncedNextRoute
=
notAnnounced
;
// last argument to Route.didChangeNext
/// Restoration ID to be used for the encapsulating route when restoration is
/// enabled for it or null if restoration cannot be enabled for it.
String
?
get
restorationId
{
// User-provided restoration ids of Pages are prefixed with 'p+'. Generated
// ids for pageless routes are prefixed with 'r+' to avoid clashes.
if
(
hasPage
)
{
final
Page
<
Object
>
page
=
route
.
settings
as
Page
<
Object
>;
return
page
.
restorationId
!=
null
?
'p+
${page.restorationId}
'
:
null
;
}
if
(
restorationInformation
!=
null
)
{
return
'r+
${restorationInformation!.restorationScopeId}
'
;
}
return
null
;
}
bool
get
hasPage
=>
route
.
settings
is
Page
;
bool
canUpdateFrom
(
Page
<
dynamic
>
page
)
{
...
...
@@ -2537,6 +3035,8 @@ class _RouteEntry extends RouteTransitionRecord {
currentState
.
index
>=
_RouteLifecycle
.
add
.
index
;
}
bool
get
isPresentForRestoration
=>
currentState
.
index
<=
_RouteLifecycle
.
idle
.
index
;
bool
get
suitableForAnnouncement
{
return
currentState
.
index
<=
_RouteLifecycle
.
removing
.
index
&&
currentState
.
index
>=
_RouteLifecycle
.
push
.
index
;
...
...
@@ -2629,6 +3129,12 @@ class _RouteEntry extends RouteTransitionRecord {
remove
();
_isWaitingForExitingDecision
=
false
;
}
bool
get
restorationEnabled
=>
route
.
restorationScopeId
.
value
!=
null
;
set
restorationEnabled
(
bool
value
)
{
assert
(!
value
||
restorationId
!=
null
);
route
.
_updateRestorationId
(
value
?
restorationId
:
null
);
}
}
abstract
class
_NavigatorObservation
{
...
...
@@ -2693,9 +3199,10 @@ class _NavigatorReplaceObservation extends _NavigatorObservation {
/// The state for a [Navigator] widget.
///
/// A reference to this class can be obtained by calling [Navigator.of].
class
NavigatorState
extends
State
<
Navigator
>
with
TickerProviderStateMixin
{
final
GlobalKey
<
OverlayState
>
_overlayKey
=
GlobalKey
<
OverlayState
>()
;
class
NavigatorState
extends
State
<
Navigator
>
with
TickerProviderStateMixin
,
RestorationMixin
{
late
GlobalKey
<
OverlayState
>
_overlayKey
;
List
<
_RouteEntry
>
_history
=
<
_RouteEntry
>[];
final
_HistoryProperty
_serializableHistory
=
_HistoryProperty
();
final
Queue
<
_NavigatorObservation
>
_observedRouteAdditions
=
Queue
<
_NavigatorObservation
>();
final
Queue
<
_NavigatorObservation
>
_observedRouteDeletions
=
Queue
<
_NavigatorObservation
>();
...
...
@@ -2727,39 +3234,86 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
.
getElementForInheritedWidgetOfExactType
<
HeroControllerScope
>()
?.
widget
as
HeroControllerScope
?;
_updateHeroController
(
heroControllerScope
?.
controller
);
}
// Use [_nextPagelessRestorationScopeId] to get the next id.
final
RestorableNum
<
int
>
_rawNextPagelessRestorationScopeId
=
RestorableNum
<
int
>(
0
);
String
?
initialRoute
=
widget
.
initialRoute
;
if
(
widget
.
pages
.
isNotEmpty
)
{
_history
.
addAll
(
widget
.
pages
.
map
((
Page
<
dynamic
>
page
)
=>
_RouteEntry
(
page
.
createRoute
(
context
),
initialState:
_RouteLifecycle
.
add
,
))
int
get
_nextPagelessRestorationScopeId
=>
_rawNextPagelessRestorationScopeId
.
value
++;
@override
void
restoreState
(
RestorationBucket
?
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
_rawNextPagelessRestorationScopeId
,
'id'
);
registerForRestoration
(
_serializableHistory
,
'history'
);
// Delete everything in the old history and clear the overlay.
while
(
_history
.
isNotEmpty
)
{
_history
.
removeLast
().
dispose
();
}
assert
(
_history
.
isEmpty
);
_overlayKey
=
GlobalKey
<
OverlayState
>();
// Populate the new history from restoration data.
_history
.
addAll
(
_serializableHistory
.
restoreEntriesForPage
(
null
,
this
));
for
(
final
Page
<
dynamic
>
page
in
widget
.
pages
)
{
final
_RouteEntry
entry
=
_RouteEntry
(
page
.
createRoute
(
context
),
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
(
entry
.
route
.
settings
==
page
,
'The settings getter of a page-based Route must return a Page object. '
'Please set the settings to the Page in the Page.createRoute method.'
);
_history
.
add
(
entry
);
_history
.
addAll
(
_serializableHistory
.
restoreEntriesForPage
(
entry
,
this
));
}
// If there was nothing to restore, we need to process the initial route.
if
(!
_serializableHistory
.
hasData
)
{
String
?
initialRoute
=
widget
.
initialRoute
;
if
(
widget
.
pages
.
isEmpty
)
{
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
,
restorationInformation:
route
.
settings
.
name
!=
null
?
_RestorationInformation
.
named
(
name:
route
.
settings
.
name
!,
arguments:
null
,
restorationScopeId:
_nextPagelessRestorationScopeId
,
)
:
null
,
),
),
);
}
}
assert
(!
_debugLocked
);
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
_flushHistoryUpdates
();
assert
(()
{
_debugLocked
=
false
;
return
true
;
}());
}
@override
void
didToggleBucket
(
RestorationBucket
?
oldBucket
)
{
super
.
didToggleBucket
(
oldBucket
);
if
(
bucket
!=
null
)
{
_serializableHistory
.
update
(
_history
);
}
else
{
_serializableHistory
.
clear
();
}
}
@override
String
?
get
restorationId
=>
widget
.
restorationScopeId
;
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
...
...
@@ -2820,7 +3374,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
}
_updateEffectiveObservers
();
}
if
(
oldWidget
.
pages
!=
widget
.
pages
)
{
if
(
oldWidget
.
pages
!=
widget
.
pages
&&
!
restorePending
)
{
assert
(
widget
.
pages
.
isNotEmpty
,
'To use the Navigator.pages, there must be at least one page in the list.'
...
...
@@ -3282,8 +3836,12 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
overlayEntry
.
remove
();
entry
.
dispose
();
}
if
(
rearrangeOverlay
)
if
(
rearrangeOverlay
)
{
overlay
?.
rearrange
(
_allRouteOverlayEntries
);
}
if
(
bucket
!=
null
)
{
_serializableHistory
.
update
(
_history
);
}
}
void
_flushObserverNotifications
()
{
...
...
@@ -3416,6 +3974,11 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePushNamed], which pushes a route that can be restored
/// during state restoration.
@optionalTypeArgs
Future
<
T
>
pushNamed
<
T
extends
Object
?>(
String
routeName
,
{
...
...
@@ -3424,6 +3987,42 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
push
<
T
>(
_routeNamed
<
T
>(
routeName
,
arguments:
arguments
)!);
}
/// Push a named route onto the navigator.
///
/// {@macro flutter.widgets.navigator.restorablePushNamed}
///
/// {@macro flutter.widgets.navigator.pushNamed}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _openDetails() {
/// navigator.restorablePushNamed('/nyc/1776');
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
String
restorablePushNamed
<
T
extends
Object
?>(
String
routeName
,
{
Object
?
arguments
,
})
{
assert
(
routeName
!=
null
);
assert
(
debugIsSerializableForRestoration
(
arguments
),
'The arguments object must be serializable via the StandardMessageCodec.'
);
final
_RouteEntry
entry
=
_RestorationInformation
.
named
(
name:
routeName
,
arguments:
arguments
,
restorationScopeId:
_nextPagelessRestorationScopeId
,
).
toRouteEntry
(
this
,
initialState:
_RouteLifecycle
.
push
);
_pushEntry
(
entry
);
return
entry
.
restorationId
!;
}
/// Replace the current route of the navigator by pushing the route named
/// [routeName] and then disposing the previous route once the new route has
/// finished animating in.
...
...
@@ -3442,6 +4041,11 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePushReplacementNamed], which pushes a replacement route that
/// can be restored during state restoration.
@optionalTypeArgs
Future
<
T
>
pushReplacementNamed
<
T
extends
Object
?,
TO
extends
Object
?>(
String
routeName
,
{
...
...
@@ -3451,6 +4055,45 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
pushReplacement
<
T
,
TO
>(
_routeNamed
<
T
>(
routeName
,
arguments:
arguments
)!,
result:
result
);
}
/// Replace the current route of the navigator by pushing the route named
/// [routeName] and then disposing the previous route once the new route has
/// finished animating in.
///
/// {@macro flutter.widgets.navigator.restorablePushReplacementNamed}
///
/// {@macro flutter.widgets.navigator.pushReplacementNamed}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _startCar() {
/// navigator.restorablePushReplacementNamed('/jouett/1781');
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
String
restorablePushReplacementNamed
<
T
extends
Object
?,
TO
extends
Object
?>(
String
routeName
,
{
TO
?
result
,
Object
?
arguments
,
})
{
assert
(
routeName
!=
null
);
assert
(
debugIsSerializableForRestoration
(
arguments
),
'The arguments object must be serializable via the StandardMessageCodec.'
);
final
_RouteEntry
entry
=
_RestorationInformation
.
named
(
name:
routeName
,
arguments:
arguments
,
restorationScopeId:
_nextPagelessRestorationScopeId
,
).
toRouteEntry
(
this
,
initialState:
_RouteLifecycle
.
pushReplace
);
_pushReplacementEntry
(
entry
,
result
);
return
entry
.
restorationId
!;
}
/// Pop the current route off the navigator and push a named route in its
/// place.
///
...
...
@@ -3468,6 +4111,11 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePopAndPushNamed], which pushes a new route that can be
/// restored during state restoration.
@optionalTypeArgs
Future
<
T
>
popAndPushNamed
<
T
extends
Object
?,
TO
extends
Object
?>(
String
routeName
,
{
...
...
@@ -3478,6 +4126,37 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
pushNamed
<
T
>(
routeName
,
arguments:
arguments
);
}
/// Pop the current route off the navigator and push a named route in its
/// place.
///
/// {@macro flutter.widgets.navigator.restorablePopAndPushNamed}
///
/// {@macro flutter.widgets.navigator.popAndPushNamed}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _end() {
/// navigator.restorablePopAndPushNamed('/nyc/1776');
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
String
restorablePopAndPushNamed
<
T
extends
Object
?,
TO
extends
Object
?>(
String
routeName
,
{
TO
?
result
,
Object
?
arguments
,
})
{
pop
<
TO
>(
result
);
return
restorablePushNamed
(
routeName
,
arguments:
arguments
);
}
/// Push the route with the given name onto the navigator, and then remove all
/// the previous routes until the `predicate` returns true.
///
...
...
@@ -3495,6 +4174,11 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
/// be restored during state restoration.
@optionalTypeArgs
Future
<
T
>
pushNamedAndRemoveUntil
<
T
extends
Object
?>(
String
newRouteName
,
...
...
@@ -3504,6 +4188,44 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
pushAndRemoveUntil
<
T
>(
_routeNamed
<
T
>(
newRouteName
,
arguments:
arguments
)!,
predicate
);
}
/// Push the route with the given name onto the navigator, and then remove all
/// the previous routes until the `predicate` returns true.
///
/// {@macro flutter.widgets.navigator.restorablePushNamedAndRemoveUntil}
///
/// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _openCalendar() {
/// navigator.restorablePushNamedAndRemoveUntil('/calendar', ModalRoute.withName('/'));
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
String
restorablePushNamedAndRemoveUntil
<
T
extends
Object
?>(
String
newRouteName
,
RoutePredicate
predicate
,
{
Object
?
arguments
,
})
{
assert
(
newRouteName
!=
null
);
assert
(
debugIsSerializableForRestoration
(
arguments
),
'The arguments object must be serializable via the StandardMessageCodec.'
);
final
_RouteEntry
entry
=
_RestorationInformation
.
named
(
name:
newRouteName
,
arguments:
arguments
,
restorationScopeId:
_nextPagelessRestorationScopeId
,
).
toRouteEntry
(
this
,
initialState:
_RouteLifecycle
.
push
);
_pushEntryAndRemoveUntil
(
entry
,
predicate
);
return
entry
.
restorationId
!;
}
/// Push the given route onto the navigator.
///
/// {@macro flutter.widgets.navigator.push}
...
...
@@ -3518,26 +4240,95 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePush], which pushes a route that can be restored during
/// state restoration.
@optionalTypeArgs
Future
<
T
>
push
<
T
extends
Object
?>(
Route
<
T
>
route
)
{
_pushEntry
(
_RouteEntry
(
route
,
initialState:
_RouteLifecycle
.
push
));
return
route
.
popped
;
}
bool
_debugIsStaticCallback
(
Function
callback
)
{
bool
result
=
false
;
assert
(()
{
// TODO(goderbauer): remove the kIsWeb check when https://github.com/flutter/flutter/issues/33615 is resolved.
result
=
kIsWeb
||
ui
.
PluginUtilities
.
getCallbackHandle
(
callback
)
!=
null
;
return
true
;
}());
return
result
;
}
/// Push a new route onto the navigator.
///
/// {@macro flutter.widgets.navigator.restorablePush}
///
/// {@macro flutter.widgets.navigator.push}
///
/// {@macro flutter.widgets.navigator.restorablePush.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool dartpad --template=stateful_widget_material}
///
/// Typical usage is as follows:
///
/// ```dart
/// static Route _myRouteBuilder(BuildContext context, Object arguments) {
/// return MaterialPageRoute(
/// builder: (BuildContext context) => MyStatefulWidget(),
/// );
/// }
///
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: const Text('Sample Code'),
/// ),
/// floatingActionButton: FloatingActionButton(
/// onPressed: () => Navigator.of(context).restorablePush(_myRouteBuilder),
/// tooltip: 'Increment Counter',
/// child: const Icon(Icons.add),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
Future
<
T
>
push
<
T
>(
Route
<
T
>
route
)
{
String
restorablePush
<
T
extends
Object
?>(
RestorableRouteBuilder
<
T
>
routeBuilder
,
{
Object
?
arguments
})
{
assert
(
routeBuilder
!=
null
);
assert
(
_debugIsStaticCallback
(
routeBuilder
),
'The provided routeBuilder must be a static function.'
);
assert
(
debugIsSerializableForRestoration
(
arguments
),
'The arguments object must be serializable via the StandardMessageCodec.'
);
final
_RouteEntry
entry
=
_RestorationInformation
.
anonymous
(
routeBuilder:
routeBuilder
,
arguments:
arguments
,
restorationScopeId:
_nextPagelessRestorationScopeId
,
).
toRouteEntry
(
this
,
initialState:
_RouteLifecycle
.
push
);
_pushEntry
(
entry
);
return
entry
.
restorationId
!;
}
void
_pushEntry
(
_RouteEntry
entry
)
{
assert
(!
_debugLocked
);
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
assert
(
route
!=
null
);
assert
(
route
.
_navigator
==
null
);
_history
.
add
(
_RouteEntry
(
route
,
initialState:
_RouteLifecycle
.
push
));
assert
(
entry
.
route
!=
null
);
assert
(
entry
.
route
.
_navigator
==
null
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
push
);
_history
.
add
(
entry
);
_flushHistoryUpdates
();
assert
(()
{
_debugLocked
=
false
;
return
true
;
}());
_afterNavigation
(
route
);
return
route
.
popped
;
_afterNavigation
(
entry
.
route
);
}
void
_afterNavigation
<
T
>(
Route
<
T
>?
route
)
{
void
_afterNavigation
(
Route
<
dynamic
>?
route
)
{
if
(!
kReleaseMode
)
{
// Among other uses, performance tools use this event to ensure that perf
// stats reflect the time interval since the last navigation event
...
...
@@ -3548,8 +4339,8 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
routeJsonable
=
<
String
,
dynamic
>{};
String
description
;
if
(
route
is
TransitionRoute
<
T
>)
{
final
TransitionRoute
<
T
>
transitionRoute
=
route
;
if
(
route
is
TransitionRoute
<
dynamic
>)
{
final
TransitionRoute
<
dynamic
>
transitionRoute
=
route
;
description
=
transitionRoute
.
debugLabel
;
}
else
{
description
=
'
$route
'
;
...
...
@@ -3593,26 +4384,91 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [restorablePushReplacement], which pushes a replacement route that can
/// be restored during state restoration.
@optionalTypeArgs
Future
<
T
>
pushReplacement
<
T
extends
Object
?,
TO
extends
Object
?>(
Route
<
T
>
newRoute
,
{
TO
?
result
})
{
assert
(
newRoute
!=
null
);
assert
(
newRoute
.
_navigator
==
null
);
_pushReplacementEntry
(
_RouteEntry
(
newRoute
,
initialState:
_RouteLifecycle
.
pushReplace
),
result
);
return
newRoute
.
popped
;
}
/// Replace the current route of the navigator by pushing a new route and
/// then disposing the previous route once the new route has finished
/// animating in.
///
/// {@macro flutter.widgets.navigator.restorablePushReplacement}
///
/// {@macro flutter.widgets.navigator.pushReplacement}
///
/// {@macro flutter.widgets.navigator.restorablePush.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool dartpad --template=stateful_widget_material}
///
/// Typical usage is as follows:
///
/// ```dart
/// static Route _myRouteBuilder(BuildContext context, Object arguments) {
/// return MaterialPageRoute(
/// builder: (BuildContext context) => MyStatefulWidget(),
/// );
/// }
///
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: const Text('Sample Code'),
/// ),
/// floatingActionButton: FloatingActionButton(
/// onPressed: () => Navigator.of(context).restorablePushReplacement(
/// _myRouteBuilder,
/// ),
/// tooltip: 'Increment Counter',
/// child: const Icon(Icons.add),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
String
restorablePushReplacement
<
T
extends
Object
?,
TO
extends
Object
?>(
RestorableRouteBuilder
<
T
>
routeBuilder
,
{
TO
?
result
,
Object
?
arguments
})
{
assert
(
routeBuilder
!=
null
);
assert
(
_debugIsStaticCallback
(
routeBuilder
),
'The provided routeBuilder must be a static function.'
);
assert
(
debugIsSerializableForRestoration
(
arguments
),
'The arguments object must be serializable via the StandardMessageCodec.'
);
final
_RouteEntry
entry
=
_RestorationInformation
.
anonymous
(
routeBuilder:
routeBuilder
,
arguments:
arguments
,
restorationScopeId:
_nextPagelessRestorationScopeId
,
).
toRouteEntry
(
this
,
initialState:
_RouteLifecycle
.
pushReplace
);
_pushReplacementEntry
(
entry
,
result
);
return
entry
.
restorationId
!;
}
void
_pushReplacementEntry
<
TO
extends
Object
?>(
_RouteEntry
entry
,
TO
?
result
)
{
assert
(!
_debugLocked
);
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
assert
(
newR
oute
!=
null
);
assert
(
newR
oute
.
_navigator
==
null
);
assert
(
entry
.
r
oute
!=
null
);
assert
(
entry
.
r
oute
.
_navigator
==
null
);
assert
(
_history
.
isNotEmpty
);
assert
(
_history
.
any
(
_RouteEntry
.
isPresentPredicate
),
'Navigator has no active routes to replace.'
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
pushReplace
);
_history
.
lastWhere
(
_RouteEntry
.
isPresentPredicate
).
complete
(
result
,
isReplaced:
true
);
_history
.
add
(
_RouteEntry
(
newRoute
,
initialState:
_RouteLifecycle
.
pushReplace
)
);
_history
.
add
(
entry
);
_flushHistoryUpdates
();
assert
(()
{
_debugLocked
=
false
;
return
true
;
}());
_afterNavigation
(
newRoute
);
return
newRoute
.
popped
;
_afterNavigation
(
entry
.
route
);
}
/// Push the given route onto the navigator, and then remove all the previous
...
...
@@ -3633,19 +4489,87 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// }
/// ```
/// {@end-tool}
///
///
/// See also:
///
/// * [restorablePushAndRemoveUntil], which pushes a route that can be
/// restored during state restoration.
@optionalTypeArgs
Future
<
T
>
pushAndRemoveUntil
<
T
extends
Object
?>(
Route
<
T
>
newRoute
,
RoutePredicate
predicate
)
{
assert
(
newRoute
!=
null
);
assert
(
newRoute
.
_navigator
==
null
);
assert
(
newRoute
.
overlayEntries
.
isEmpty
);
_pushEntryAndRemoveUntil
(
_RouteEntry
(
newRoute
,
initialState:
_RouteLifecycle
.
push
),
predicate
);
return
newRoute
.
popped
;
}
/// Push a new route onto the navigator, and then remove all the previous
/// routes until the `predicate` returns true.
///
/// {@macro flutter.widgets.navigator.restorablePushAndRemoveUntil}
///
/// {@macro flutter.widgets.navigator.pushAndRemoveUntil}
///
/// {@macro flutter.widgets.navigator.restorablePush.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
///
/// {@tool dartpad --template=stateful_widget_material}
///
/// Typical usage is as follows:
///
/// ```dart
/// static Route _myRouteBuilder(BuildContext context, Object arguments) {
/// return MaterialPageRoute(
/// builder: (BuildContext context) => MyStatefulWidget(),
/// );
/// }
///
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: const Text('Sample Code'),
/// ),
/// floatingActionButton: FloatingActionButton(
/// onPressed: () => Navigator.of(context).restorablePushAndRemoveUntil(
/// _myRouteBuilder,
/// ModalRoute.withName('/'),
/// ),
/// tooltip: 'Increment Counter',
/// child: const Icon(Icons.add),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
String
restorablePushAndRemoveUntil
<
T
extends
Object
?>(
RestorableRouteBuilder
<
T
>
newRouteBuilder
,
RoutePredicate
predicate
,
{
Object
?
arguments
})
{
assert
(
newRouteBuilder
!=
null
);
assert
(
_debugIsStaticCallback
(
newRouteBuilder
),
'The provided routeBuilder must be a static function.'
);
assert
(
debugIsSerializableForRestoration
(
arguments
),
'The arguments object must be serializable via the StandardMessageCodec.'
);
final
_RouteEntry
entry
=
_RestorationInformation
.
anonymous
(
routeBuilder:
newRouteBuilder
,
arguments:
arguments
,
restorationScopeId:
_nextPagelessRestorationScopeId
,
).
toRouteEntry
(
this
,
initialState:
_RouteLifecycle
.
push
);
_pushEntryAndRemoveUntil
(
entry
,
predicate
);
return
entry
.
restorationId
!;
}
void
_pushEntryAndRemoveUntil
(
_RouteEntry
entry
,
RoutePredicate
predicate
)
{
assert
(!
_debugLocked
);
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
assert
(
newR
oute
!=
null
);
assert
(
newR
oute
.
_navigator
==
null
);
assert
(
newR
oute
.
overlayEntries
.
isEmpty
);
assert
(
entry
.
r
oute
!=
null
);
assert
(
entry
.
r
oute
.
_navigator
==
null
);
assert
(
entry
.
r
oute
.
overlayEntries
.
isEmpty
);
assert
(
predicate
!=
null
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
push
);
int
index
=
_history
.
length
-
1
;
_history
.
add
(
_RouteEntry
(
newRoute
,
initialState:
_RouteLifecycle
.
push
)
);
_history
.
add
(
entry
);
while
(
index
>=
0
&&
!
predicate
(
_history
[
index
].
route
))
{
if
(
_history
[
index
].
isPresent
)
_history
[
index
].
remove
();
...
...
@@ -3657,8 +4581,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
_debugLocked
=
false
;
return
true
;
}());
_afterNavigation
(
newRoute
);
return
newRoute
.
popped
;
_afterNavigation
(
entry
.
route
);
}
/// Replaces a route on the navigator with a new route.
...
...
@@ -3669,24 +4592,58 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
///
/// * [replaceRouteBelow], which is the same but identifies the route to be
/// removed by reference to the route above it, rather than directly.
/// * [restorableReplace], which adds a replacement route that can be
/// restored during state restoration.
@optionalTypeArgs
void
replace
<
T
extends
Object
?>({
required
Route
<
dynamic
>
oldRoute
,
required
Route
<
T
>
newRoute
})
{
assert
(!
_debugLocked
);
assert
(
oldRoute
!=
null
);
assert
(
oldRoute
.
_navigator
==
this
);
assert
(
newRoute
!=
null
);
if
(
oldRoute
==
newRoute
)
_replaceEntry
(
_RouteEntry
(
newRoute
,
initialState:
_RouteLifecycle
.
replace
),
oldRoute
);
}
/// Replaces a route on the navigator with a new route.
///
/// {@macro flutter.widgets.navigator.restorableReplace}
///
/// {@macro flutter.widgets.navigator.replace}
///
/// {@macro flutter.widgets.navigator.restorablePush.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
@optionalTypeArgs
String
restorableReplace
<
T
extends
Object
?>({
required
Route
<
dynamic
>
oldRoute
,
required
RestorableRouteBuilder
<
T
>
newRouteBuilder
,
Object
?
arguments
})
{
assert
(
oldRoute
!=
null
);
assert
(
oldRoute
.
_navigator
==
this
);
assert
(
newRouteBuilder
!=
null
);
assert
(
_debugIsStaticCallback
(
newRouteBuilder
),
'The provided routeBuilder must be a static function.'
);
assert
(
debugIsSerializableForRestoration
(
arguments
),
'The arguments object must be serializable via the StandardMessageCodec.'
);
assert
(
oldRoute
!=
null
);
final
_RouteEntry
entry
=
_RestorationInformation
.
anonymous
(
routeBuilder:
newRouteBuilder
,
arguments:
arguments
,
restorationScopeId:
_nextPagelessRestorationScopeId
,
).
toRouteEntry
(
this
,
initialState:
_RouteLifecycle
.
replace
);
_replaceEntry
(
entry
,
oldRoute
);
return
entry
.
restorationId
!;
}
void
_replaceEntry
(
_RouteEntry
entry
,
Route
<
dynamic
>
oldRoute
)
{
assert
(!
_debugLocked
);
if
(
oldRoute
==
entry
.
route
)
return
;
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
assert
(
oldRoute
.
_navigator
==
this
);
assert
(
newR
oute
.
_navigator
==
null
);
assert
(
entry
.
currentState
==
_RouteLifecycle
.
replace
);
assert
(
entry
.
r
oute
.
_navigator
==
null
);
final
int
index
=
_history
.
indexWhere
(
_RouteEntry
.
isRoutePredicate
(
oldRoute
));
assert
(
index
>=
0
,
'This Navigator does not contain the specified oldRoute.'
);
assert
(
_history
[
index
].
isPresent
,
'The specified oldRoute has already been removed from the Navigator.'
);
final
bool
wasCurrent
=
oldRoute
.
isCurrent
;
_history
.
insert
(
index
+
1
,
_RouteEntry
(
newRoute
,
initialState:
_RouteLifecycle
.
replace
)
);
_history
.
insert
(
index
+
1
,
entry
);
_history
[
index
].
remove
(
isReplaced:
true
);
_flushHistoryUpdates
();
assert
(()
{
...
...
@@ -3694,7 +4651,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
true
;
}());
if
(
wasCurrent
)
_afterNavigation
(
newR
oute
);
_afterNavigation
(
entry
.
r
oute
);
}
/// Replaces a route on the navigator with a new route. The route to be
...
...
@@ -3706,25 +4663,58 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
///
/// * [replace], which is the same but identifies the route to be removed
/// directly.
/// * [restorableReplaceRouteBelow], which adds a replacement route that can
/// be restored during state restoration.
@optionalTypeArgs
void
replaceRouteBelow
<
T
extends
Object
?>({
required
Route
<
dynamic
>
anchorRoute
,
required
Route
<
T
>
newRoute
})
{
assert
(!
_debugLocked
);
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
assert
(
anchorRoute
!=
null
);
assert
(
anchorRoute
.
_navigator
==
this
);
assert
(
newRoute
!=
null
);
assert
(
newRoute
.
_navigator
==
null
);
final
int
anchorIndex
=
_history
.
indexWhere
(
_RouteEntry
.
isRoutePredicate
(
anchorRoute
));
assert
(
anchorIndex
>=
0
,
'This Navigator does not contain the specified anchorRoute.'
);
assert
(
_history
[
anchorIndex
].
isPresent
,
'The specified anchorRoute has already been removed from the Navigator.'
);
int
index
=
anchorIndex
-
1
;
while
(
index
>=
0
)
{
assert
(
anchorRoute
!=
null
);
assert
(
anchorRoute
.
_navigator
==
this
);
_replaceEntryBelow
(
_RouteEntry
(
newRoute
,
initialState:
_RouteLifecycle
.
replace
),
anchorRoute
);
}
/// Replaces a route on the navigator with a new route. The route to be
/// replaced is the one below the given `anchorRoute`.
///
/// {@macro flutter.widgets.navigator.restorableReplaceRouteBelow}
///
/// {@macro flutter.widgets.navigator.replaceRouteBelow}
///
/// {@macro flutter.widgets.navigator.restorablePush.arguments}
///
/// {@macro flutter.widgets.navigator.restorablePushNamed.returnValue}
@optionalTypeArgs
String
restorableReplaceRouteBelow
<
T
extends
Object
?>({
required
Route
<
dynamic
>
anchorRoute
,
required
RestorableRouteBuilder
<
T
>
newRouteBuilder
,
Object
?
arguments
})
{
assert
(
anchorRoute
!=
null
);
assert
(
anchorRoute
.
_navigator
==
this
);
assert
(
newRouteBuilder
!=
null
);
assert
(
_debugIsStaticCallback
(
newRouteBuilder
),
'The provided routeBuilder must be a static function.'
);
assert
(
debugIsSerializableForRestoration
(
arguments
),
'The arguments object must be serializable via the StandardMessageCodec.'
);
assert
(
anchorRoute
!=
null
);
final
_RouteEntry
entry
=
_RestorationInformation
.
anonymous
(
routeBuilder:
newRouteBuilder
,
arguments:
arguments
,
restorationScopeId:
_nextPagelessRestorationScopeId
,
).
toRouteEntry
(
this
,
initialState:
_RouteLifecycle
.
replace
);
_replaceEntryBelow
(
entry
,
anchorRoute
);
return
entry
.
restorationId
!;
}
void
_replaceEntryBelow
(
_RouteEntry
entry
,
Route
<
dynamic
>
anchorRoute
)
{
assert
(!
_debugLocked
);
assert
(()
{
_debugLocked
=
true
;
return
true
;
}());
final
int
anchorIndex
=
_history
.
indexWhere
(
_RouteEntry
.
isRoutePredicate
(
anchorRoute
));
assert
(
anchorIndex
>=
0
,
'This Navigator does not contain the specified anchorRoute.'
);
assert
(
_history
[
anchorIndex
].
isPresent
,
'The specified anchorRoute has already been removed from the Navigator.'
);
int
index
=
anchorIndex
-
1
;
while
(
index
>=
0
)
{
if
(
_history
[
index
].
isPresent
)
break
;
index
-=
1
;
}
assert
(
index
>=
0
,
'There are no routes below the specified anchorRoute.'
);
_history
.
insert
(
index
+
1
,
_RouteEntry
(
newRoute
,
initialState:
_RouteLifecycle
.
replace
)
);
_history
.
insert
(
index
+
1
,
entry
);
_history
[
index
].
remove
(
isReplaced:
true
);
_flushHistoryUpdates
();
assert
(()
{
_debugLocked
=
false
;
return
true
;
}());
...
...
@@ -3839,7 +4829,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
_debugLocked
=
false
;
return
true
;
}());
_afterNavigation
<
dynamic
>
(
entry
.
route
);
_afterNavigation
(
entry
.
route
);
}
/// Calls [pop] repeatedly until the predicate returns true.
...
...
@@ -3893,7 +4883,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
true
;
}());
if
(
wasCurrent
)
_afterNavigation
<
dynamic
>
(
_afterNavigation
(
_history
.
cast
<
_RouteEntry
?>().
lastWhere
(
(
_RouteEntry
?
e
)
=>
e
!=
null
&&
_RouteEntry
.
isPresentPredicate
(
e
),
orElse:
()
=>
null
...
...
@@ -3962,6 +4952,15 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
assert
(()
{
_debugLocked
=
wasDebugLocked
!;
return
true
;
}());
}
@optionalTypeArgs
Route
<
T
>?
_getRouteById
<
T
>(
String
id
)
{
assert
(
id
!=
null
);
return
_history
.
cast
<
_RouteEntry
?>().
firstWhere
(
(
_RouteEntry
?
entry
)
=>
entry
!.
restorationId
==
id
,
orElse:
()
=>
null
,
)?.
route
as
Route
<
T
>;
}
int
get
_userGesturesInProgress
=>
_userGesturesInProgressCount
;
int
_userGesturesInProgressCount
=
0
;
set
_userGesturesInProgress
(
int
value
)
{
...
...
@@ -3981,7 +4980,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
/// Notifies its listeners if the value of [userGestureInProgress] changes.
final
ValueNotifier
<
bool
>
userGestureInProgressNotifier
=
ValueNotifier
<
bool
>(
false
);
/// The navigator is being controlled by a user gesture.
///
/// For example, called when the user beings an iOS back gesture.
...
...
@@ -4064,9 +5062,12 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
child:
FocusScope
(
node:
focusScopeNode
,
autofocus:
true
,
child:
Overlay
(
key:
_overlayKey
,
initialEntries:
overlay
==
null
?
_allRouteOverlayEntries
.
toList
(
growable:
false
)
:
const
<
OverlayEntry
>[],
child:
UnmanagedRestorationScope
(
bucket:
bucket
,
child:
Overlay
(
key:
_overlayKey
,
initialEntries:
overlay
==
null
?
_allRouteOverlayEntries
.
toList
(
growable:
false
)
:
const
<
OverlayEntry
>[],
),
),
),
),
...
...
@@ -4074,3 +5075,646 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
);
}
}
enum
_RouteRestorationType
{
named
,
anonymous
,
}
abstract
class
_RestorationInformation
{
_RestorationInformation
(
this
.
type
)
:
assert
(
type
!=
null
);
factory
_RestorationInformation
.
named
({
required
String
name
,
required
Object
?
arguments
,
required
int
restorationScopeId
,
})
=
_NamedRestorationInformation
;
factory
_RestorationInformation
.
anonymous
({
required
RestorableRouteBuilder
routeBuilder
,
required
Object
?
arguments
,
required
int
restorationScopeId
,
})
=
_AnonymousRestorationInformation
;
factory
_RestorationInformation
.
fromSerializableData
(
Object
data
)
{
assert
(
data
!=
null
);
final
List
<
Object
>
casted
=
data
as
List
<
Object
>;
assert
(
casted
.
isNotEmpty
);
final
_RouteRestorationType
type
=
_RouteRestorationType
.
values
[
casted
[
0
]
as
int
];
switch
(
type
)
{
case
_RouteRestorationType
.
named
:
return
_NamedRestorationInformation
.
fromSerializableData
(
casted
.
sublist
(
1
));
case
_RouteRestorationType
.
anonymous
:
return
_AnonymousRestorationInformation
.
fromSerializableData
(
casted
.
sublist
(
1
));
}
throw
StateError
(
'Invalid type:
$type
'
);
// ignore: dead_code
}
final
_RouteRestorationType
type
;
int
get
restorationScopeId
;
Object
?
_serializableData
;
bool
get
isRestorable
=>
true
;
Object
getSerializableData
()
{
_serializableData
??=
computeSerializableData
();
return
_serializableData
!;
}
@mustCallSuper
List
<
Object
>
computeSerializableData
()
{
return
<
Object
>[
type
.
index
];
}
@protected
Route
<
dynamic
>
createRoute
(
NavigatorState
navigator
);
_RouteEntry
toRouteEntry
(
NavigatorState
navigator
,
{
_RouteLifecycle
initialState
=
_RouteLifecycle
.
add
})
{
assert
(
navigator
!=
null
);
assert
(
initialState
!=
null
);
final
Route
<
Object
?>
route
=
createRoute
(
navigator
);
assert
(
route
!=
null
);
return
_RouteEntry
(
route
,
initialState:
initialState
,
restorationInformation:
this
,
);
}
}
class
_NamedRestorationInformation
extends
_RestorationInformation
{
_NamedRestorationInformation
({
required
this
.
name
,
required
this
.
arguments
,
required
this
.
restorationScopeId
,
})
:
assert
(
name
!=
null
),
super
(
_RouteRestorationType
.
named
);
factory
_NamedRestorationInformation
.
fromSerializableData
(
List
<
Object
>
data
)
{
assert
(
data
.
length
>=
2
);
return
_NamedRestorationInformation
(
restorationScopeId:
data
[
0
]
as
int
,
name:
data
[
1
]
as
String
,
arguments:
data
.
length
>
2
?
data
[
2
]
:
null
,
);
}
@override
List
<
Object
>
computeSerializableData
()
{
return
super
.
computeSerializableData
()..
addAll
(<
Object
>[
restorationScopeId
,
name
,
if
(
arguments
!=
null
)
arguments
!,
]);
}
@override
final
int
restorationScopeId
;
final
String
name
;
final
Object
?
arguments
;
@override
Route
<
dynamic
>
createRoute
(
NavigatorState
navigator
)
{
final
Route
<
dynamic
>
route
=
navigator
.
_routeNamed
<
dynamic
>(
name
,
arguments:
arguments
,
allowNull:
false
)!;
assert
(
route
!=
null
);
return
route
;
}
}
class
_AnonymousRestorationInformation
extends
_RestorationInformation
{
_AnonymousRestorationInformation
({
required
this
.
routeBuilder
,
required
this
.
arguments
,
required
this
.
restorationScopeId
,
})
:
assert
(
routeBuilder
!=
null
),
super
(
_RouteRestorationType
.
anonymous
);
factory
_AnonymousRestorationInformation
.
fromSerializableData
(
List
<
Object
>
data
)
{
assert
(
data
.
length
>
1
);
final
RestorableRouteBuilder
routeBuilder
=
ui
.
PluginUtilities
.
getCallbackFromHandle
(
ui
.
CallbackHandle
.
fromRawHandle
(
data
[
1
]
as
int
))
as
RestorableRouteBuilder
;
assert
(
routeBuilder
!=
null
);
return
_AnonymousRestorationInformation
(
restorationScopeId:
data
[
0
]
as
int
,
routeBuilder:
routeBuilder
,
arguments:
data
.
length
>
2
?
data
[
2
]
:
null
,
);
}
@override
// TODO(goderbauer): remove the kIsWeb check when https://github.com/flutter/flutter/issues/33615 is resolved.
bool
get
isRestorable
=>
!
kIsWeb
;
@override
List
<
Object
>
computeSerializableData
()
{
assert
(
isRestorable
);
final
ui
.
CallbackHandle
?
handle
=
ui
.
PluginUtilities
.
getCallbackHandle
(
routeBuilder
);
assert
(
handle
!=
null
);
return
super
.
computeSerializableData
()..
addAll
(<
Object
>[
restorationScopeId
,
handle
!.
toRawHandle
(),
if
(
arguments
!=
null
)
arguments
!,
]);
}
@override
final
int
restorationScopeId
;
final
RestorableRouteBuilder
routeBuilder
;
final
Object
?
arguments
;
@override
Route
<
dynamic
>
createRoute
(
NavigatorState
navigator
)
{
final
Route
<
dynamic
>
result
=
routeBuilder
(
navigator
.
context
,
arguments
);
assert
(
result
!=
null
);
return
result
;
}
}
class
_HistoryProperty
extends
RestorableProperty
<
Map
<
String
?,
List
<
Object
>>?>
{
// Routes not associated with a page are stored under key 'null'.
Map
<
String
?,
List
<
Object
>>?
_pageToPagelessRoutes
;
// Updating.
void
update
(
List
<
_RouteEntry
>
history
)
{
assert
(
isRegistered
);
final
bool
wasUninitialized
=
_pageToPagelessRoutes
==
null
;
bool
needsSerialization
=
wasUninitialized
;
_pageToPagelessRoutes
??=
<
String
,
List
<
Object
>>{};
_RouteEntry
?
currentPage
;
List
<
Object
>
newRoutesForCurrentPage
=
<
Object
>[];
List
<
Object
>
oldRoutesForCurrentPage
=
_pageToPagelessRoutes
![
null
]
??
const
<
Object
>[];
bool
restorationEnabled
=
true
;
final
Map
<
String
?,
List
<
Object
>>
newMap
=
<
String
?,
List
<
Object
>>{};
final
Set
<
String
?>
removedPages
=
_pageToPagelessRoutes
!.
keys
.
toSet
();
for
(
final
_RouteEntry
entry
in
history
)
{
if
(!
entry
.
isPresentForRestoration
)
{
entry
.
restorationEnabled
=
false
;
continue
;
}
assert
(
entry
.
isPresentForRestoration
);
if
(
entry
.
hasPage
)
{
needsSerialization
=
needsSerialization
||
newRoutesForCurrentPage
.
length
!=
oldRoutesForCurrentPage
.
length
;
_finalizePage
(
newRoutesForCurrentPage
,
currentPage
,
newMap
,
removedPages
);
currentPage
=
entry
;
restorationEnabled
=
entry
.
restorationId
!=
null
;
entry
.
restorationEnabled
=
restorationEnabled
;
if
(
restorationEnabled
)
{
assert
(
entry
.
restorationId
!=
null
);
newRoutesForCurrentPage
=
<
Object
>[];
oldRoutesForCurrentPage
=
_pageToPagelessRoutes
![
entry
.
restorationId
]
??
const
<
Object
>[];
}
else
{
newRoutesForCurrentPage
=
const
<
Object
>[];
oldRoutesForCurrentPage
=
const
<
Object
>[];
}
continue
;
}
assert
(!
entry
.
hasPage
);
restorationEnabled
=
restorationEnabled
&&
entry
.
restorationInformation
?.
isRestorable
==
true
;
entry
.
restorationEnabled
=
restorationEnabled
;
if
(
restorationEnabled
)
{
assert
(
entry
.
restorationId
!=
null
);
assert
(
currentPage
==
null
||
currentPage
.
restorationId
!=
null
);
assert
(
entry
.
restorationInformation
!=
null
);
final
Object
serializedData
=
entry
.
restorationInformation
!.
getSerializableData
();
needsSerialization
=
needsSerialization
||
oldRoutesForCurrentPage
.
length
<=
newRoutesForCurrentPage
.
length
||
oldRoutesForCurrentPage
[
newRoutesForCurrentPage
.
length
]
!=
serializedData
;
newRoutesForCurrentPage
.
add
(
serializedData
);
}
}
needsSerialization
=
needsSerialization
||
newRoutesForCurrentPage
.
length
!=
oldRoutesForCurrentPage
.
length
;
_finalizePage
(
newRoutesForCurrentPage
,
currentPage
,
newMap
,
removedPages
);
needsSerialization
=
needsSerialization
||
removedPages
.
isNotEmpty
;
assert
(
wasUninitialized
||
_debugMapsEqual
(
_pageToPagelessRoutes
!,
newMap
)
!=
needsSerialization
);
if
(
needsSerialization
)
{
_pageToPagelessRoutes
=
newMap
;
notifyListeners
();
}
}
void
_finalizePage
(
List
<
Object
>
routes
,
_RouteEntry
?
page
,
Map
<
String
?,
List
<
Object
>>
pageToRoutes
,
Set
<
String
?>
pagesToRemove
,
)
{
assert
(
page
==
null
||
page
.
hasPage
);
assert
(
pageToRoutes
!=
null
);
assert
(!
pageToRoutes
.
containsKey
(
page
?.
restorationId
));
if
(
routes
!=
null
&&
routes
.
isNotEmpty
)
{
assert
(
page
==
null
||
page
.
restorationId
!=
null
);
final
String
?
restorationId
=
page
?.
restorationId
;
pageToRoutes
[
restorationId
]
=
routes
;
pagesToRemove
.
remove
(
restorationId
);
}
}
bool
_debugMapsEqual
(
Map
<
String
?,
List
<
Object
>>
a
,
Map
<
String
?,
List
<
Object
>>
b
)
{
if
(!
setEquals
(
a
.
keys
.
toSet
(),
b
.
keys
.
toSet
()))
{
return
false
;
}
for
(
final
String
?
key
in
a
.
keys
)
{
if
(!
listEquals
(
a
[
key
],
b
[
key
]))
{
return
false
;
}
}
return
true
;
}
void
clear
()
{
assert
(
isRegistered
);
if
(
_pageToPagelessRoutes
==
null
)
{
return
;
}
_pageToPagelessRoutes
=
null
;
notifyListeners
();
}
// Restoration.
bool
get
hasData
=>
_pageToPagelessRoutes
!=
null
;
List
<
_RouteEntry
>
restoreEntriesForPage
(
_RouteEntry
?
page
,
NavigatorState
navigator
)
{
assert
(
isRegistered
);
assert
(
page
==
null
||
page
.
hasPage
);
final
List
<
_RouteEntry
>
result
=
<
_RouteEntry
>[];
if
(
_pageToPagelessRoutes
==
null
||
(
page
!=
null
&&
page
.
restorationId
==
null
))
{
return
result
;
}
final
List
<
Object
>?
serializedData
=
_pageToPagelessRoutes
![
page
?.
restorationId
];
if
(
serializedData
==
null
)
{
return
result
;
}
for
(
final
Object
data
in
serializedData
)
{
result
.
add
(
_RestorationInformation
.
fromSerializableData
(
data
).
toRouteEntry
(
navigator
));
}
return
result
;
}
// RestorableProperty overrides.
@override
Map
<
String
?,
List
<
Object
>>?
createDefaultValue
()
{
return
null
;
}
@override
Map
<
String
?,
List
<
Object
>>?
fromPrimitives
(
Object
data
)
{
final
Map
<
dynamic
,
dynamic
>
casted
=
data
as
Map
<
dynamic
,
dynamic
>;
return
casted
.
map
<
String
,
List
<
Object
>>((
dynamic
key
,
dynamic
value
)
=>
MapEntry
<
String
,
List
<
Object
>>(
key
as
String
,
List
<
Object
>.
from
(
value
as
List
<
dynamic
>,
growable:
true
),
));
}
@override
void
initWithValue
(
Map
<
String
?,
List
<
Object
>>?
value
)
{
_pageToPagelessRoutes
=
value
;
}
@override
Object
?
toPrimitives
()
{
return
_pageToPagelessRoutes
;
}
@override
bool
get
enabled
=>
hasData
;
}
/// A callback that given a [BuildContext] finds a [NavigatorState].
///
/// Used by [RestorableRouteFuture.navigatorFinder] to determine the navigator
/// to which a new route should be added.
typedef
NavigatorFinderCallback
=
NavigatorState
Function
(
BuildContext
context
);
/// A callback that given some `arguments` and a `navigator` adds a new
/// restorable route to that `navigator` and resturns the opaque ID of that
/// new route.
///
/// Usually, this callback calls one of the imperative methods on the Navigator
/// that have "restorable" in the name and returns their return value.
///
/// Used by [RestorableRouteFuture.onPresent].
typedef
RoutePresentationCallback
=
String
Function
(
NavigatorState
navigator
,
Object
?
arguments
);
/// A callback to handle the result of a completed [Route].
///
/// The return value of the route (which can be null for e.g. void routes) is
/// passed to the callback.
///
/// Used by [RestorableRouteFuture.onComplete].
typedef
RouteCompletionCallback
<
T
>
=
void
Function
(
T
result
);
/// Gives access to a [Route] object and its return value that was added to a
/// navigator via one of its "restorable" API methods.
///
/// When a [State] object wants access to the return value of a [Route] object
/// it has pushed onto the [Navigator], a [RestorableRouteFuture] ensures that
/// it will also have access to that value after state restoration.
///
/// To show a new route on the navigator defined by the [navigatorFinder], call
/// [present], which will invoke the [onPresent] callback. The [onPresent]
/// callback must add a new route to the navigator provided to it using one
/// of the "restorable" API methods. When the newly added route completes, the
/// [onComplete] callback executes. It is given the return value of the route,
/// which may be null.
///
/// While the route added via [present] is shown on the navigator, it can be
/// accessed via the [route] getter.
///
/// If the property is restored to a state in which [present] had been called on
/// it, but the route has not completed yet, the [RestorableRouteFuture] will
/// obtain the restored route object from the navigator again and call
/// [onComplete] once it completes.
///
/// The [RestorableRouteFuture] can only keep track of one active [route].
/// When [present] has been called to add a route, it may only be called again
/// after the previously added route has completed.
///
/// {@tool dartpad --template=freeform}
/// This example uses a [RestorableRouteFuture] in the `_MyHomeState` to push a
/// new `MyCounter` route and to retrieve its return value.
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart main
/// void main() => runApp(MyApp());
/// ```
///
/// ```dart preamble
/// class MyApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// restorationScopeId: 'app',
/// home: Scaffold(
/// appBar: AppBar(title: Text('RestorableRouteFuture Example')),
/// body: MyHome(),
/// ),
/// );
/// }
/// }
/// ```
///
/// ```dart
/// class MyHome extends StatefulWidget {
/// const MyHome({Key key}) : super(key: key);
///
/// @override
/// State<MyHome> createState() => _MyHomeState();
/// }
///
/// class _MyHomeState extends State<MyHome> with RestorationMixin {
/// final RestorableInt _lastCount = RestorableInt(0);
/// RestorableRouteFuture<int> _counterRoute;
///
/// @override
/// String get restorationId => 'home';
///
/// void initState() {
/// super.initState();
/// _counterRoute = RestorableRouteFuture<int>(
/// onPresent: (NavigatorState navigator, Object arguments) {
/// // Defines what route should be shown (and how it should be added
/// // to the navigator) when `RestorableRouteFuture.present` is called.
/// return navigator.restorablePush(
/// _counterRouteBuilder,
/// arguments: arguments,
/// );
/// },
/// onComplete: (int count) {
/// // Defines what should happen with the return value when the route
/// // completes.
/// setState(() {
/// _lastCount.value = count;
/// });
/// }
/// );
/// }
///
/// @override
/// void restoreState(RestorationBucket oldBucket, bool initialRestore) {
/// // Register the `RestorableRouteFuture` with the state restoration framework.
/// registerForRestoration(_counterRoute, 'route');
/// registerForRestoration(_lastCount, 'count');
/// }
///
/// @override
/// void dispose() {
/// super.dispose();
/// _lastCount.dispose();
/// _counterRoute?.dispose();
/// }
///
/// // A static `RestorableRouteBuilder` that can re-create the route during
/// // state restoration.
/// static Route<int> _counterRouteBuilder(BuildContext context, Object arguments) {
/// return MaterialPageRoute(
/// builder: (BuildContext context) => MyCounter(
/// title: arguments as String,
/// ),
/// );
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Center(
/// child: Column(
/// mainAxisSize: MainAxisSize.min,
/// children: <Widget>[
/// Text('Last count: ${_lastCount.value}'),
/// RaisedButton(
/// onPressed: () {
/// // Show the route defined by the `RestorableRouteFuture`.
/// _counterRoute.present('Awesome Counter');
/// },
/// child: Text('Open Counter'),
/// ),
/// ],
/// ),
/// );
/// }
/// }
///
/// // Widget for the route pushed by the `RestorableRouteFuture`.
/// class MyCounter extends StatefulWidget {
/// const MyCounter({Key key, this.title}) : super(key: key);
///
/// final String title;
///
/// @override
/// State<MyCounter> createState() => _MyCounterState();
/// }
///
/// class _MyCounterState extends State<MyCounter> with RestorationMixin {
/// final RestorableInt _count = RestorableInt(0);
///
/// @override
/// String get restorationId => 'counter';
///
/// @override
/// void restoreState(RestorationBucket oldBucket, bool initialRestore) {
/// registerForRestoration(_count, 'count');
/// }
///
/// @override
/// void dispose() {
/// super.dispose();
/// _count.dispose();
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: Text(widget.title),
/// leading: BackButton(
/// onPressed: () {
/// // Return the current count of the counter from this route.
/// Navigator.of(context).pop(_count.value);
/// },
/// ),
/// ),
/// body: Center(
/// child: Text('Count: ${_count.value}'),
/// ),
/// floatingActionButton: FloatingActionButton(
/// child: Icon(Icons.add),
/// onPressed: () {
/// setState(() {
/// _count.value++;
/// });
/// },
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
class
RestorableRouteFuture
<
T
>
extends
RestorableProperty
<
String
?>
{
/// Creates a [RestorableRouteFuture].
///
/// The [onPresent] and [navigatorFinder] arguments must not be null.
RestorableRouteFuture
({
this
.
navigatorFinder
=
_defaultNavigatorFinder
,
required
this
.
onPresent
,
this
.
onComplete
,
})
:
assert
(
onPresent
!=
null
),
assert
(
navigatorFinder
!=
null
);
/// A callback that given the [BuildContext] of the [State] object to which
/// this property is registered returns the [NavigatorState] of the navigator
/// to which the route instantiated in [onPresent] is added.
final
NavigatorFinderCallback
navigatorFinder
;
/// A callback that add a new [Route] to the provided navigator.
///
/// The callback must use one of the API methods on the [NavigatorState] that
/// have "restorable" in their name (e.g. [NavigatorState.restorablePush],
/// [NavigatorState.restorablePushNamed], etc.) and return the opaque ID
/// returned by those methods.
///
/// This callback is invoked when [present] is called with the `arguments`
/// Object that was passed to that method and the [NavigatorState] obtained
/// from [navigatorFinder].
final
RoutePresentationCallback
onPresent
;
/// A callback that is invoked when the [Route] added via [onPresent]
/// completes.
///
/// The return value of that route is passed to this method.
final
RouteCompletionCallback
<
T
>?
onComplete
;
/// Shows the route created by [onPresent] and invoke [onComplete] when it
/// completes.
///
/// The `arguments` object is passed to [onPresent] and can be used to
/// customize the route. It must be serializable via the
/// [StandardMessageCodec]. Often, a [Map] is used to pass key-value pairs.
void
present
([
Object
?
arguments
])
{
assert
(!
isPresent
);
assert
(
isRegistered
);
final
String
routeId
=
onPresent
(
_navigator
,
arguments
);
assert
(
routeId
!=
null
);
_hookOntoRouteFuture
(
routeId
);
notifyListeners
();
}
/// Whether the [Route] created by [present] is currently shown.
///
/// Returns true after [present] has been called until the [Route] compeltes.
bool
get
isPresent
=>
route
!=
null
;
/// The route that [present] added to the Navigator.
///
/// Returns null when currently no route is shown
Route
<
T
>?
get
route
=>
_route
;
Route
<
T
>?
_route
;
@override
String
?
createDefaultValue
()
=>
null
;
@override
void
initWithValue
(
String
?
value
)
{
if
(
value
!=
null
)
{
_hookOntoRouteFuture
(
value
);
}
}
@override
Object
?
toPrimitives
()
{
assert
(
route
!=
null
);
assert
(
enabled
);
return
route
?.
restorationScopeId
.
value
;
}
@override
String
fromPrimitives
(
Object
data
)
{
assert
(
data
!=
null
);
return
data
as
String
;
}
bool
_disposed
=
false
;
@override
void
dispose
()
{
super
.
dispose
();
_route
?.
restorationScopeId
.
removeListener
(
notifyListeners
);
_disposed
=
true
;
}
@override
bool
get
enabled
=>
route
?.
restorationScopeId
.
value
!=
null
;
NavigatorState
get
_navigator
{
final
NavigatorState
navigator
=
navigatorFinder
(
state
.
context
);
assert
(
navigator
!=
null
);
return
navigator
;
}
void
_hookOntoRouteFuture
(
String
id
)
{
assert
(
id
!=
null
);
_route
=
_navigator
.
_getRouteById
<
T
>(
id
);
assert
(
_route
!=
null
);
route
!.
restorationScopeId
.
addListener
(
notifyListeners
);
route
!.
popped
.
then
((
dynamic
result
)
{
if
(
_disposed
)
{
return
;
}
_route
?.
restorationScopeId
.
removeListener
(
notifyListeners
);
_route
=
null
;
notifyListeners
();
if
(
onComplete
!=
null
)
{
onComplete
!(
result
as
T
);
}
});
}
static
NavigatorState
_defaultNavigatorFinder
(
BuildContext
context
)
=>
Navigator
.
of
(
context
,
nullOk:
false
)!;
}
packages/flutter/lib/src/widgets/restoration.dart
View file @
fc85492d
...
...
@@ -499,10 +499,13 @@ abstract class RestorableProperty<T> extends ChangeNotifier {
}
/// The [State] object that this property is registered with.
///
/// Must only be called when [isRegistered] is true.
@protected
State
?
get
state
{
State
get
state
{
assert
(
isRegistered
);
assert
(
_debugAssertNotDisposed
());
return
_owner
;
return
_owner
!
;
}
/// Whether this property is currently registered with a [RestorationMixin].
...
...
@@ -609,13 +612,10 @@ abstract class RestorableProperty<T> extends ChangeNotifier {
/// class RestorationExampleApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// // The [RootRestorationScope] can be removed once it is part of [MaterialApp].
/// return RootRestorationScope(
/// restorationId: 'root',
/// child: MaterialApp(
/// title: 'Restorable Counter',
/// home: RestorableCounter(restorationId: 'counter'),
/// ),
/// return MaterialApp(
/// restorationScopeId: 'app',
/// title: 'Restorable Counter',
/// home: RestorableCounter(restorationId: 'counter'),
/// );
/// }
/// }
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
fc85492d
...
...
@@ -18,6 +18,7 @@ import 'modal_barrier.dart';
import
'navigator.dart'
;
import
'overlay.dart'
;
import
'page_storage.dart'
;
import
'restoration.dart'
;
import
'transitions.dart'
;
// Examples can assume:
...
...
@@ -773,54 +774,64 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
@override
Widget
build
(
BuildContext
context
)
{
return
_ModalScopeStatus
(
route:
widget
.
route
,
isCurrent:
widget
.
route
.
isCurrent
,
// _routeSetState is called if this updates
canPop:
widget
.
route
.
canPop
,
// _routeSetState is called if this updates
child:
Offstage
(
offstage:
widget
.
route
.
offstage
,
// _routeSetState is called if this updates
child:
PageStorage
(
bucket:
widget
.
route
.
_storageBucket
,
// immutable
child:
Actions
(
actions:
_actionMap
,
child:
FocusScope
(
node:
focusScopeNode
,
// immutable
child:
RepaintBoundary
(
child:
AnimatedBuilder
(
animation:
_listenable
,
// immutable
builder:
(
BuildContext
context
,
Widget
?
child
)
{
return
widget
.
route
.
buildTransitions
(
context
,
widget
.
route
.
animation
!,
widget
.
route
.
secondaryAnimation
!,
// This additional AnimatedBuilder is include because if the
// value of the userGestureInProgressNotifier changes, it's
// only necessary to rebuild the IgnorePointer widget and set
// the focus node's ability to focus.
AnimatedBuilder
(
animation:
widget
.
route
.
navigator
?.
userGestureInProgressNotifier
??
ValueNotifier
<
bool
>(
false
),
builder:
(
BuildContext
context
,
Widget
?
child
)
{
final
bool
ignoreEvents
=
_shouldIgnoreFocusRequest
;
focusScopeNode
.
canRequestFocus
=
!
ignoreEvents
;
return
IgnorePointer
(
ignoring:
ignoreEvents
,
child:
child
,
return
AnimatedBuilder
(
animation:
widget
.
route
.
restorationScopeId
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
assert
(
child
!=
null
);
return
RestorationScope
(
restorationId:
widget
.
route
.
restorationScopeId
.
value
,
child:
child
!,
);
},
child:
_ModalScopeStatus
(
route:
widget
.
route
,
isCurrent:
widget
.
route
.
isCurrent
,
// _routeSetState is called if this updates
canPop:
widget
.
route
.
canPop
,
// _routeSetState is called if this updates
child:
Offstage
(
offstage:
widget
.
route
.
offstage
,
// _routeSetState is called if this updates
child:
PageStorage
(
bucket:
widget
.
route
.
_storageBucket
,
// immutable
child:
Actions
(
actions:
_actionMap
,
child:
FocusScope
(
node:
focusScopeNode
,
// immutable
child:
RepaintBoundary
(
child:
AnimatedBuilder
(
animation:
_listenable
,
// immutable
builder:
(
BuildContext
context
,
Widget
?
child
)
{
return
widget
.
route
.
buildTransitions
(
context
,
widget
.
route
.
animation
!,
widget
.
route
.
secondaryAnimation
!,
// This additional AnimatedBuilder is include because if the
// value of the userGestureInProgressNotifier changes, it's
// only necessary to rebuild the IgnorePointer widget and set
// the focus node's ability to focus.
AnimatedBuilder
(
animation:
widget
.
route
.
navigator
?.
userGestureInProgressNotifier
??
ValueNotifier
<
bool
>(
false
),
builder:
(
BuildContext
context
,
Widget
?
child
)
{
final
bool
ignoreEvents
=
_shouldIgnoreFocusRequest
;
focusScopeNode
.
canRequestFocus
=
!
ignoreEvents
;
return
IgnorePointer
(
ignoring:
ignoreEvents
,
child:
child
,
);
},
child:
child
,
),
);
},
child:
_page
??=
RepaintBoundary
(
key:
widget
.
route
.
_subtreeKey
,
// immutable
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
widget
.
route
.
buildPage
(
context
,
widget
.
route
.
animation
!,
widget
.
route
.
secondaryAnimation
!,
);
},
child:
child
,
),
);
},
child:
_page
??=
RepaintBoundary
(
key:
widget
.
route
.
_subtreeKey
,
// immutable
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
widget
.
route
.
buildPage
(
context
,
widget
.
route
.
animation
!,
widget
.
route
.
secondaryAnimation
!,
);
},
),
),
),
...
...
packages/flutter/test/cupertino/tab_test.dart
View file @
fc85492d
...
...
@@ -97,6 +97,10 @@ void main() {
expect
(
tester
.
takeException
(),
isFlutterError
);
expect
(
unknownForRouteCalled
,
'/'
);
// Work-around for https://github.com/flutter/flutter/issues/65655.
await
tester
.
pumpWidget
(
Container
());
expect
(
tester
.
takeException
(),
isAssertionError
);
});
testWidgets
(
'Can use navigatorKey to navigate'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/cupertino/text_field_restoration_test.dart
View file @
fc85492d
...
...
@@ -16,9 +16,9 @@ const String alternativeText = 'Everything is awesome!!';
void
main
(
)
{
testWidgets
(
'CupertinoTextField restoration'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
child:
TestWidget
()
,
restorationId:
'root'
,
const
CupertinoApp
(
restorationScopeId:
'app'
,
home:
TestWidget
()
,
),
);
...
...
@@ -27,11 +27,11 @@ void main() {
testWidgets
(
'CupertinoTextField restoration with external controller'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
child:
TestWidget
(
const
CupertinoApp
(
restorationScopeId:
'app'
,
home:
TestWidget
(
useExternal:
true
,
),
restorationId:
'root'
,
),
);
...
...
@@ -102,17 +102,15 @@ class TestWidgetState extends State<TestWidget> with RestorationMixin {
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
home:
Material
(
child:
Align
(
alignment:
Alignment
.
center
,
child:
SizedBox
(
width:
50
,
child:
CupertinoTextField
(
restorationId:
'text'
,
maxLines:
3
,
controller:
widget
.
useExternal
?
controller
.
value
:
null
,
),
return
Material
(
child:
Align
(
alignment:
Alignment
.
center
,
child:
SizedBox
(
width:
50
,
child:
CupertinoTextField
(
restorationId:
'text'
,
maxLines:
3
,
controller:
widget
.
useExternal
?
controller
.
value
:
null
,
),
),
),
...
...
packages/flutter/test/material/app_test.dart
View file @
fc85492d
...
...
@@ -384,6 +384,10 @@ void main() {
);
expect
(
tester
.
takeException
(),
isFlutterError
);
expect
(
log
,
<
String
>[
'onGenerateRoute /'
,
'onUnknownRoute /'
]);
// Work-around for https://github.com/flutter/flutter/issues/65655.
await
tester
.
pumpWidget
(
Container
());
expect
(
tester
.
takeException
(),
isAssertionError
);
});
testWidgets
(
'MaterialApp with builder and no route information works.'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/material/debug_test.dart
View file @
fc85492d
...
...
@@ -142,6 +142,9 @@ void main() {
' PageStorage
\n
'
' Offstage
\n
'
' _ModalScopeStatus
\n
'
' UnmanagedRestorationScope
\n
'
' RestorationScope
\n
'
' AnimatedBuilder
\n
'
' _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#00000]
\n
'
' Semantics
\n
'
' _EffectiveTickerMode
\n
'
...
...
@@ -149,6 +152,7 @@ void main() {
' _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#00000]
\n
'
' _Theatre
\n
'
' Overlay-[LabeledGlobalKey<OverlayState>#00000]
\n
'
' UnmanagedRestorationScope
\n
'
' _FocusMarker
\n
'
' Semantics
\n
'
' FocusScope
\n
'
...
...
@@ -187,6 +191,10 @@ void main() {
' _FocusMarker
\n
'
' Focus
\n
'
' Shortcuts
\n
'
' UnmanagedRestorationScope
\n
'
' RestorationScope
\n
'
' UnmanagedRestorationScope
\n
'
' RootRestorationScope
\n
'
' WidgetsApp-[GlobalObjectKey _MaterialAppState#00000]
\n
'
' HeroControllerScope
\n
'
' ScrollConfiguration
\n
'
...
...
packages/flutter/test/material/text_field_restoration_test.dart
View file @
fc85492d
...
...
@@ -15,9 +15,9 @@ const String alternativeText = 'Everything is awesome!!';
void
main
(
)
{
testWidgets
(
'TextField restoration'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
child:
TestWidget
()
,
restorationId:
'root'
,
const
MaterialApp
(
restorationScopeId:
'app'
,
home:
TestWidget
()
,
),
);
...
...
@@ -26,11 +26,11 @@ void main() {
testWidgets
(
'TextField restoration with external controller'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
child:
TestWidget
(
const
MaterialApp
(
restorationScopeId:
'root'
,
home:
TestWidget
(
useExternal:
true
,
),
restorationId:
'root'
,
),
);
...
...
@@ -101,17 +101,15 @@ class TestWidgetState extends State<TestWidget> with RestorationMixin {
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
home:
Material
(
child:
Align
(
alignment:
Alignment
.
center
,
child:
SizedBox
(
width:
50
,
child:
TextField
(
restorationId:
'text'
,
maxLines:
3
,
controller:
widget
.
useExternal
?
controller
.
value
:
null
,
),
return
Material
(
child:
Align
(
alignment:
Alignment
.
center
,
child:
SizedBox
(
width:
50
,
child:
TextField
(
restorationId:
'text'
,
maxLines:
3
,
controller:
widget
.
useExternal
?
controller
.
value
:
null
,
),
),
),
...
...
packages/flutter/test/widgets/navigator_restoration_test.dart
0 → 100644
View file @
fc85492d
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import
'dart:ui'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
void
main
(
)
{
testWidgets
(
'Restoration Smoke Test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
2
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
3
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
,
count:
2
),
findsOneWidget
);
});
testWidgets
(
'restorablePushNamed'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
,
arguments:
3
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Foo'
,
count:
0
,
arguments:
3
),
findsOneWidget
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
2
,
arguments:
3
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
,
arguments:
4
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
,
arguments:
4
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
expect
(
findRoute
(
'Bar'
),
findsNothing
);
});
testWidgets
(
'restorablePushReplacementNamed'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushReplacementNamed
(
'Foo'
,
arguments:
3
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
0
,
arguments:
3
),
findsOneWidget
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
2
,
arguments:
3
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
,
arguments:
4
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
,
arguments:
4
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
expect
(
findRoute
(
'Bar'
),
findsNothing
);
});
testWidgets
(
'restorablePopAndPushNamed'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePopAndPushNamed
(
'Foo'
,
arguments:
3
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
0
,
arguments:
3
),
findsOneWidget
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
2
,
arguments:
3
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
,
arguments:
4
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
,
arguments:
4
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
expect
(
findRoute
(
'Bar'
),
findsNothing
);
});
testWidgets
(
'restorablePushNamedAndRemoveUntil'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamedAndRemoveUntil
(
'Foo'
,
(
Route
<
dynamic
>
_
)
=>
false
,
arguments:
3
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
0
,
arguments:
3
),
findsOneWidget
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
2
,
arguments:
3
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
,
arguments:
4
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
,
arguments:
4
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
,
arguments:
3
),
findsOneWidget
);
expect
(
findRoute
(
'Bar'
),
findsNothing
);
});
testWidgets
(
'restorablePush'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePush
(
_routeBuilder
,
arguments:
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Foo'
,
count:
0
),
findsOneWidget
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
2
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
expect
(
findRoute
(
'Bar'
),
findsNothing
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
testWidgets
(
'restorablePush adds route on all platforms'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePush
(
_routeBuilder
,
arguments:
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
});
testWidgets
(
'restorablePushReplacement'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushReplacement
(
_routeBuilder
,
arguments:
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
0
),
findsOneWidget
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
2
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
expect
(
findRoute
(
'Bar'
),
findsNothing
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
testWidgets
(
'restorablePushReplacement adds route on all platforms'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushReplacement
(
_routeBuilder
,
arguments:
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
});
testWidgets
(
'restorablePushAndRemoveUntil'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushAndRemoveUntil
(
_routeBuilder
,
(
Route
<
dynamic
>
_
)
=>
false
,
arguments:
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
0
),
findsOneWidget
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
2
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
expect
(
findRoute
(
'Bar'
),
findsNothing
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
testWidgets
(
'restorablePushAndRemoveUntil adds route on all platforms'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushAndRemoveUntil
(
_routeBuilder
,
(
Route
<
dynamic
>
_
)
=>
false
,
arguments:
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
});
testWidgets
(
'restorableReplace'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
final
Route
<
Object
>
oldRoute
=
ModalRoute
.
of
(
tester
.
element
(
find
.
text
(
'Route: home'
)));
expect
(
oldRoute
.
settings
.
name
,
'home'
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorableReplace
(
newRouteBuilder:
_routeBuilder
,
arguments:
'Foo'
,
oldRoute:
oldRoute
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
0
),
findsOneWidget
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
2
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
1
),
findsOneWidget
);
expect
(
findRoute
(
'Bar'
),
findsNothing
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
testWidgets
(
'restorableReplace adds route on all platforms'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
final
Route
<
Object
>
oldRoute
=
ModalRoute
.
of
(
tester
.
element
(
find
.
text
(
'Route: home'
)));
expect
(
oldRoute
.
settings
.
name
,
'home'
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorableReplace
(
newRouteBuilder:
_routeBuilder
,
arguments:
'Foo'
,
oldRoute:
oldRoute
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
});
testWidgets
(
'restorableReplaceRouteBelow'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Anchor'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'Anchor'
,
tester
);
expect
(
findRoute
(
'home'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
0
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Anchor'
,
count:
1
),
findsOneWidget
);
final
Route
<
Object
>
anchor
=
ModalRoute
.
of
(
tester
.
element
(
find
.
text
(
'Route: Anchor'
)));
expect
(
anchor
.
settings
.
name
,
'Anchor'
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorableReplaceRouteBelow
(
newRouteBuilder:
_routeBuilder
,
arguments:
'Foo'
,
anchorRoute:
anchor
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
0
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Anchor'
,
count:
1
),
findsOneWidget
);
await
tapRouteCounter
(
'Anchor'
,
tester
);
expect
(
findRoute
(
'Anchor'
,
count:
2
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
0
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Anchor'
,
count:
2
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Anchor'
,
tester
);
expect
(
findRoute
(
'Anchor'
,
count:
3
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
0
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Anchor'
,
count:
2
),
findsOneWidget
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
testWidgets
(
'restorableReplaceRouteBelow adds route on all platforms'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Anchor'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'Anchor'
,
tester
);
expect
(
findRoute
(
'home'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
0
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Anchor'
,
count:
1
),
findsOneWidget
);
final
Route
<
Object
>
anchor
=
ModalRoute
.
of
(
tester
.
element
(
find
.
text
(
'Route: Anchor'
)));
expect
(
anchor
.
settings
.
name
,
'Anchor'
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorableReplaceRouteBelow
(
newRouteBuilder:
_routeBuilder
,
arguments:
'Foo'
,
anchorRoute:
anchor
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Foo'
,
skipOffstage:
false
),
findsOneWidget
);
});
testWidgets
(
'restoring a popped route'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'Foo'
,
tester
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'home'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Foo'
,
count:
2
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
3
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Foo'
),
findsNothing
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'Foo'
,
count:
2
),
findsOneWidget
);
});
testWidgets
(
'popped routes are not restored'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Bar'
),
findsNothing
);
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'Bar'
),
findsNothing
);
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsOneWidget
);
});
testWidgets
(
'routes that are in the process of push are restored'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
);
await
tester
.
pump
();
await
tester
.
pump
();
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
// Push is in progress.
final
ModalRoute
<
Object
>
route1
=
ModalRoute
.
of
(
tester
.
element
(
find
.
text
(
'Route: Foo'
)));
final
String
route1id
=
route1
.
restorationScopeId
.
value
;
expect
(
route1id
,
isNotNull
);
expect
(
route1
.
settings
.
name
,
'Foo'
);
expect
(
route1
.
animation
.
isCompleted
,
isFalse
);
expect
(
route1
.
animation
.
isDismissed
,
isFalse
);
expect
(
route1
.
isActive
,
isTrue
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsOneWidget
);
final
ModalRoute
<
Object
>
route2
=
ModalRoute
.
of
(
tester
.
element
(
find
.
text
(
'Route: Foo'
)));
expect
(
route2
,
isNot
(
same
(
route1
)));
expect
(
route1
.
restorationScopeId
.
value
,
route1id
);
expect
(
route2
.
animation
.
isCompleted
,
isTrue
);
expect
(
route2
.
isActive
,
isTrue
);
});
testWidgets
(
'routes that are in the process of pop are not restored'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
);
await
tester
.
pumpAndSettle
();
final
ModalRoute
<
Object
>
route1
=
ModalRoute
.
of
(
tester
.
element
(
find
.
text
(
'Route: Foo'
)));
int
notifyCount
=
0
;
route1
.
restorationScopeId
.
addListener
(()
{
notifyCount
++;
});
expect
(
route1
.
isActive
,
isTrue
);
expect
(
route1
.
restorationScopeId
.
value
,
isNotNull
);
expect
(
route1
.
animation
.
isCompleted
,
isTrue
);
expect
(
notifyCount
,
0
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
expect
(
notifyCount
,
1
);
await
tester
.
pump
();
await
tester
.
pump
();
// Pop is in progress.
expect
(
route1
.
restorationScopeId
.
value
,
isNull
);
expect
(
route1
.
settings
.
name
,
'Foo'
);
expect
(
route1
.
animation
.
isCompleted
,
isFalse
);
expect
(
route1
.
animation
.
isDismissed
,
isFalse
);
expect
(
route1
.
isActive
,
isFalse
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'Foo'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
expect
(
notifyCount
,
1
);
});
testWidgets
(
'routes are restored in the right order'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route1'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route1'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route2'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route2'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route3'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route3'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route4'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route4'
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'route4'
),
findsOneWidget
);
expect
(
findRoute
(
'route3'
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'route2'
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'route1'
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route3'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route2'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route1'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
),
findsOneWidget
);
});
testWidgets
(
'all routes up to first unrestorable are restored'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route1'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route1'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route2'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route2'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pushNamed
(
'route3'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route3'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route4'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route4'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePush
(
_routeBuilder
,
arguments:
'route5'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route5'
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'route5'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'route4'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'route3'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'route2'
),
findsOneWidget
);
expect
(
findRoute
(
'route1'
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsOneWidget
);
});
testWidgets
(
'removing unrestorable routes restores all of them'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route1'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route1'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route2'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route2'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pushNamed
(
'route3'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route3'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route4'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route4'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'route5'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'route5'
),
findsOneWidget
);
final
Route
<
Object
>
route
=
ModalRoute
.
of
(
tester
.
element
(
find
.
text
(
'Route: route3'
,
skipOffstage:
false
)));
expect
(
route
.
settings
.
name
,
'route3'
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
removeRoute
(
route
);
await
tester
.
pumpAndSettle
();
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'route5'
),
findsOneWidget
);
expect
(
findRoute
(
'route4'
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'route3'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'route2'
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'route1'
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
skipOffstage:
false
),
findsOneWidget
);
});
testWidgets
(
'RestorableRouteFuture'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePush
(
_routeFutureBuilder
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Return value: null'
),
findsOneWidget
);
final
RestorableRouteFuture
<
int
>
routeFuture
=
tester
.
state
<
RouteFutureWidgetState
>(
find
.
byType
(
RouteFutureWidget
))
.
routeFuture
;
expect
(
routeFuture
.
route
,
isNull
);
expect
(
routeFuture
.
isPresent
,
isFalse
);
expect
(
routeFuture
.
enabled
,
isFalse
);
routeFuture
.
present
(
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Route: Foo'
),
findsOneWidget
);
expect
(
routeFuture
.
route
.
settings
.
name
,
'Foo'
);
expect
(
routeFuture
.
isPresent
,
isTrue
);
expect
(
routeFuture
.
enabled
,
isTrue
);
await
tester
.
restartAndRestore
();
expect
(
find
.
text
(
'Route: Foo'
),
findsOneWidget
);
final
RestorableRouteFuture
<
int
>
restoredRouteFuture
=
tester
.
state
<
RouteFutureWidgetState
>(
find
.
byType
(
RouteFutureWidget
,
skipOffstage:
false
))
.
routeFuture
;
expect
(
restoredRouteFuture
.
route
.
settings
.
name
,
'Foo'
);
expect
(
restoredRouteFuture
.
isPresent
,
isTrue
);
expect
(
restoredRouteFuture
.
enabled
,
isTrue
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
(
10
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Return value: 10'
),
findsOneWidget
);
expect
(
restoredRouteFuture
.
route
,
isNull
);
expect
(
restoredRouteFuture
.
isPresent
,
isFalse
);
expect
(
restoredRouteFuture
.
enabled
,
isFalse
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
testWidgets
(
'RestorableRouteFuture in unrestorable context'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
expect
(
findRoute
(
'home'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pushNamed
(
'unrestorable'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'unrestorable'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePush
(
_routeFutureBuilder
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Return value: null'
),
findsOneWidget
);
final
RestorableRouteFuture
<
int
>
routeFuture
=
tester
.
state
<
RouteFutureWidgetState
>(
find
.
byType
(
RouteFutureWidget
))
.
routeFuture
;
expect
(
routeFuture
.
route
,
isNull
);
expect
(
routeFuture
.
isPresent
,
isFalse
);
expect
(
routeFuture
.
enabled
,
isFalse
);
routeFuture
.
present
(
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Route: Foo'
),
findsOneWidget
);
expect
(
routeFuture
.
route
.
settings
.
name
,
'Foo'
);
expect
(
routeFuture
.
isPresent
,
isTrue
);
expect
(
routeFuture
.
enabled
,
isFalse
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
),
findsOneWidget
);
});
testWidgets
(
'Illegal arguments throw'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
TestWidget
());
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Bar'
);
await
tester
.
pumpAndSettle
();
final
Route
<
Object
>
oldRoute
=
ModalRoute
.
of
(
tester
.
element
(
find
.
text
(
'Route: Bar'
)));
expect
(
oldRoute
.
settings
.
name
,
'Bar'
);
final
Matcher
throwsArgumentsAssertionError
=
throwsA
(
isAssertionError
.
having
(
(
AssertionError
e
)
=>
e
.
message
,
'message'
,
'The arguments object must be serializable via the StandardMessageCodec.'
,
));
final
Matcher
throwsBuilderAssertionError
=
throwsA
(
isAssertionError
.
having
(
(
AssertionError
e
)
=>
e
.
message
,
'message'
,
'The provided routeBuilder must be a static function.'
,
));
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
,
arguments:
Object
()),
throwsArgumentsAssertionError
,
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushReplacementNamed
(
'Foo'
,
arguments:
Object
()),
throwsArgumentsAssertionError
,
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePopAndPushNamed
(
'Foo'
,
arguments:
Object
()),
throwsArgumentsAssertionError
,
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamedAndRemoveUntil
(
'Foo'
,
(
Route
<
Object
>
_
)
=>
false
,
arguments:
Object
()),
throwsArgumentsAssertionError
,
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePush
(
_routeBuilder
,
arguments:
Object
()),
throwsArgumentsAssertionError
,
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushReplacement
(
_routeBuilder
,
arguments:
Object
()),
throwsArgumentsAssertionError
,
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushAndRemoveUntil
(
_routeBuilder
,
(
Route
<
Object
>
_
)
=>
false
,
arguments:
Object
()),
throwsArgumentsAssertionError
,
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorableReplace
(
newRouteBuilder:
_routeBuilder
,
oldRoute:
oldRoute
,
arguments:
Object
()),
throwsArgumentsAssertionError
,
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorableReplaceRouteBelow
(
newRouteBuilder:
_routeBuilder
,
anchorRoute:
oldRoute
,
arguments:
Object
()),
throwsArgumentsAssertionError
,
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePush
((
BuildContext
_
,
Object
__
)
=>
null
),
throwsBuilderAssertionError
,
skip:
isBrowser
,
// https://github.com/flutter/flutter/issues/33615
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushReplacement
((
BuildContext
_
,
Object
__
)
=>
null
),
throwsBuilderAssertionError
,
skip:
isBrowser
,
// https://github.com/flutter/flutter/issues/33615
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushAndRemoveUntil
((
BuildContext
_
,
Object
__
)
=>
null
,
(
Route
<
Object
>
_
)
=>
false
),
throwsBuilderAssertionError
,
skip:
isBrowser
,
// https://github.com/flutter/flutter/issues/33615
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorableReplace
(
newRouteBuilder:
(
BuildContext
_
,
Object
__
)
=>
null
,
oldRoute:
oldRoute
),
throwsBuilderAssertionError
,
skip:
isBrowser
,
// https://github.com/flutter/flutter/issues/33615
);
expect
(
()
=>
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorableReplaceRouteBelow
(
newRouteBuilder:
(
BuildContext
_
,
Object
__
)
=>
null
,
anchorRoute:
oldRoute
),
throwsBuilderAssertionError
,
skip:
isBrowser
,
// https://github.com/flutter/flutter/issues/33615
);
});
testWidgets
(
'Moving scopes'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
restorationId:
'root'
,
child:
TestWidget
(
restorationId:
null
,
),
));
await
tapRouteCounter
(
'home'
,
tester
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
// Nothing is restored.
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'Foo'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
await
tapRouteCounter
(
'home'
,
tester
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
);
await
tester
.
pumpAndSettle
();
// Move navigator into restoration scope.
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
restorationId:
'root'
,
child:
TestWidget
(
restorationId:
'app'
,
),
));
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
// Everything is restored.
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
// Move navigator out of restoration scope.
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
restorationId:
'root'
,
child:
TestWidget
(
restorationId:
null
,
),
));
expect
(
findRoute
(
'Foo'
),
findsOneWidget
);
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
// Nothing is restored.
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'Foo'
),
findsNothing
);
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
});
testWidgets
(
'Restoring pages'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
PagedTestWidget
());
expect
(
findRoute
(
'home'
,
count:
0
),
findsOneWidget
);
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'Foo'
,
tester
);
await
tapRouteCounter
(
'Foo'
,
tester
);
expect
(
findRoute
(
'Foo'
,
count:
2
),
findsOneWidget
);
final
TestRestorationData
data
=
await
tester
.
getRestorationData
();
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'Foo'
,
count:
2
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
PagedTestNavigatorState
>(
find
.
byType
(
PagedTestNavigator
)).
addPage
(
'bar'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'bar'
,
tester
);
expect
(
findRoute
(
'bar'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'Foo'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'Foo'
,
count:
0
),
findsOneWidget
);
await
tester
.
restoreFrom
(
data
);
expect
(
findRoute
(
'bar'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'Foo'
,
count:
2
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
PagedTestNavigatorState
>(
find
.
byType
(
PagedTestNavigator
)).
addPage
(
'bar'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'bar'
,
count:
0
),
findsOneWidget
);
});
testWidgets
(
'Unrestorable pages'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
PagedTestWidget
());
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
PagedTestNavigatorState
>(
find
.
byType
(
PagedTestNavigator
)).
addPage
(
'p1'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'p1'
,
tester
);
expect
(
findRoute
(
'p1'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'r1'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'r1'
,
tester
);
expect
(
findRoute
(
'r1'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
PagedTestNavigatorState
>(
find
.
byType
(
PagedTestNavigator
)).
addPage
(
'p2'
,
restoreState:
false
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'p2'
,
tester
);
expect
(
findRoute
(
'p2'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'r2'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'r2'
,
tester
);
expect
(
findRoute
(
'r2'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
PagedTestNavigatorState
>(
find
.
byType
(
PagedTestNavigator
)).
addPage
(
'p3'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'p3'
,
tester
);
expect
(
findRoute
(
'p3'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'r3'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'r3'
,
tester
);
expect
(
findRoute
(
'r3'
,
count:
1
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'r2'
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'r3'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'p3'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'p2'
,
count:
0
),
findsOneWidget
);
// Page did not restore its state!
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'r1'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'p1'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pop
();
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
});
testWidgets
(
'removed page is not restored'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
PagedTestWidget
());
await
tapRouteCounter
(
'home'
,
tester
);
expect
(
findRoute
(
'home'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
PagedTestNavigatorState
>(
find
.
byType
(
PagedTestNavigator
)).
addPage
(
'p1'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'p1'
,
tester
);
expect
(
findRoute
(
'p1'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
restorablePushNamed
(
'r1'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'r1'
,
tester
);
expect
(
findRoute
(
'r1'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
PagedTestNavigatorState
>(
find
.
byType
(
PagedTestNavigator
)).
addPage
(
'p2'
);
await
tester
.
pumpAndSettle
();
await
tapRouteCounter
(
'p2'
,
tester
);
expect
(
findRoute
(
'p2'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
PagedTestNavigatorState
>(
find
.
byType
(
PagedTestNavigator
)).
removePage
(
'p1'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'p1'
,
count:
1
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'r1'
,
count:
1
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'p2'
,
count:
1
),
findsOneWidget
);
await
tester
.
restartAndRestore
();
expect
(
findRoute
(
'home'
,
count:
1
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
findRoute
(
'p1'
,
count:
1
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'r1'
,
count:
1
,
skipOffstage:
false
),
findsNothing
);
expect
(
findRoute
(
'p2'
,
count:
1
),
findsOneWidget
);
tester
.
state
<
PagedTestNavigatorState
>(
find
.
byType
(
PagedTestNavigator
)).
addPage
(
'p1'
);
await
tester
.
pumpAndSettle
();
expect
(
findRoute
(
'p1'
,
count:
0
),
findsOneWidget
);
});
}
Route
<
void
>
_routeBuilder
(
BuildContext
context
,
Object
arguments
)
{
return
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
{
return
RouteWidget
(
name:
arguments
as
String
,
);
},
);
}
Route
<
void
>
_routeFutureBuilder
(
BuildContext
context
,
Object
arguments
)
{
return
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
{
return
RouteFutureWidget
();
},
);
}
class
PagedTestWidget
extends
StatelessWidget
{
const
PagedTestWidget
({
this
.
restorationId
=
'app'
});
final
String
restorationId
;
@override
Widget
build
(
BuildContext
context
)
{
return
RootRestorationScope
(
restorationId:
restorationId
,
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
PagedTestNavigator
(),
),
);
}
}
class
PagedTestNavigator
extends
StatefulWidget
{
@override
State
<
PagedTestNavigator
>
createState
()
=>
PagedTestNavigatorState
();
}
class
PagedTestNavigatorState
extends
State
<
PagedTestNavigator
>
with
RestorationMixin
{
final
RestorableString
_routes
=
RestorableString
(
'r-home'
);
void
addPage
(
String
name
,
{
bool
restoreState
=
true
,
int
index
})
{
assert
(!
name
.
contains
(
','
));
assert
(!
name
.
startsWith
(
'r-'
));
final
List
<
String
>
routes
=
_routes
.
value
.
split
(
','
);
name
=
restoreState
?
'r-
$name
'
:
name
;
if
(
index
!=
null
)
{
routes
.
insert
(
index
,
name
);
}
else
{
routes
.
add
(
name
);
}
setState
(()
{
_routes
.
value
=
routes
.
join
(
','
);
});
}
bool
removePage
(
String
name
)
{
final
List
<
String
>
routes
=
_routes
.
value
.
split
(
','
);
if
(
routes
.
remove
(
name
)
||
routes
.
remove
(
'r-
$name
'
))
{
setState
(()
{
_routes
.
value
=
routes
.
join
(
','
);
});
return
true
;
}
return
false
;
}
@override
Widget
build
(
BuildContext
context
)
{
return
Navigator
(
restorationScopeId:
'nav'
,
onPopPage:
(
Route
<
dynamic
>
route
,
dynamic
result
)
{
if
(
route
.
didPop
(
result
))
{
removePage
(
route
.
settings
.
name
);
return
true
;
}
return
false
;
},
pages:
_routes
.
value
.
isEmpty
?
const
<
Page
<
Object
>>[]
:
_routes
.
value
.
split
(
','
).
map
((
String
name
)
{
if
(
name
.
startsWith
(
'r-'
))
{
name
=
name
.
substring
(
2
);
return
TestPage
(
name:
name
,
restorationId:
name
,
key:
ValueKey
<
String
>(
name
),
);
}
return
TestPage
(
name:
name
,
key:
ValueKey
<
String
>(
name
),
);
}).
toList
(),
onGenerateRoute:
(
RouteSettings
settings
)
{
return
MaterialPageRoute
<
int
>(
settings:
settings
,
builder:
(
BuildContext
context
)
{
return
RouteWidget
(
name:
settings
.
name
,
arguments:
settings
.
arguments
,
);
},
);
},
);
}
@override
String
get
restorationId
=>
'router'
;
@override
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
_routes
,
'routes'
);
}
@override
void
dispose
()
{
super
.
dispose
();
_routes
.
dispose
();
}
}
class
TestPage
extends
Page
<
void
>
{
const
TestPage
({
LocalKey
key
,
String
name
,
String
restorationId
})
:
super
(
name:
name
,
key:
key
,
restorationId:
restorationId
);
@override
Route
<
void
>
createRoute
(
BuildContext
context
)
{
return
MaterialPageRoute
<
void
>(
settings:
this
,
builder:
(
BuildContext
context
)
{
return
RouteWidget
(
name:
name
,
);
}
);
}
}
class
TestWidget
extends
StatelessWidget
{
const
TestWidget
({
this
.
restorationId
=
'app'
});
final
String
restorationId
;
@override
Widget
build
(
BuildContext
context
)
{
return
RootRestorationScope
(
restorationId:
restorationId
,
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Navigator
(
initialRoute:
'home'
,
restorationScopeId:
'app'
,
onGenerateRoute:
(
RouteSettings
settings
)
{
return
MaterialPageRoute
<
int
>(
settings:
settings
,
builder:
(
BuildContext
context
)
{
return
RouteWidget
(
name:
settings
.
name
,
arguments:
settings
.
arguments
,
);
},
);
},
),
),
);
}
}
class
RouteWidget
extends
StatefulWidget
{
const
RouteWidget
({
Key
key
,
this
.
name
,
this
.
arguments
})
:
super
(
key:
key
);
final
String
name
;
final
Object
arguments
;
@override
State
<
RouteWidget
>
createState
()
=>
RouteWidgetState
();
}
class
RouteWidgetState
extends
State
<
RouteWidget
>
with
RestorationMixin
{
final
RestorableInt
counter
=
RestorableInt
(
0
);
@override
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
counter
,
'counter'
);
}
@override
String
get
restorationId
=>
'stateful'
;
@override
void
dispose
()
{
super
.
dispose
();
counter
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Center
(
child:
Column
(
children:
<
Widget
>[
GestureDetector
(
child:
Text
(
'Route:
${widget.name}
'
),
onTap:
()
{
setState
(()
{
counter
.
value
++;
});
},
),
if
(
widget
.
arguments
!=
null
)
Text
(
'Arguments(home):
${widget.arguments}
'
),
Text
(
'Counter(
${widget.name}
):
${counter.value}
'
),
],
),
);
}
}
class
RouteFutureWidget
extends
StatefulWidget
{
@override
State
<
RouteFutureWidget
>
createState
()
=>
RouteFutureWidgetState
();
}
class
RouteFutureWidgetState
extends
State
<
RouteFutureWidget
>
with
RestorationMixin
{
RestorableRouteFuture
<
int
>
routeFuture
;
int
value
;
@override
void
initState
()
{
super
.
initState
();
routeFuture
=
RestorableRouteFuture
<
int
>(
onPresent:
(
NavigatorState
navigatorState
,
Object
arguments
)
{
return
navigatorState
.
restorablePushNamed
(
arguments
as
String
);
},
onComplete:
(
int
i
)
{
setState
(()
{
value
=
i
;
});
}
);
}
@override
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
routeFuture
,
'routeFuture'
);
}
@override
String
get
restorationId
=>
'routefuturewidget'
;
@override
void
dispose
()
{
super
.
dispose
();
routeFuture
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Center
(
child:
Text
(
'Return value:
$value
'
),
);
}
}
Finder
findRoute
(
String
name
,
{
Object
arguments
,
int
count
,
bool
skipOffstage
=
true
})
=>
_RouteFinder
(
name
,
arguments:
arguments
,
count:
count
,
skipOffstage:
skipOffstage
);
Future
<
void
>
tapRouteCounter
(
String
name
,
WidgetTester
tester
)
async
{
await
tester
.
tap
(
find
.
text
(
'Route:
$name
'
));
await
tester
.
pump
();
}
class
_RouteFinder
extends
MatchFinder
{
_RouteFinder
(
this
.
name
,
{
this
.
arguments
,
this
.
count
,
bool
skipOffstage
=
true
})
:
super
(
skipOffstage:
skipOffstage
);
final
String
name
;
final
Object
arguments
;
final
int
count
;
@override
String
get
description
{
String
result
=
'Route(name:
$name
'
;
if
(
arguments
!=
null
)
{
result
+=
', arguments:
$arguments
'
;
}
if
(
count
!=
null
)
{
result
+=
', count:
$count
'
;
}
return
result
;
}
@override
bool
matches
(
Element
candidate
)
{
final
Widget
widget
=
candidate
.
widget
;
if
(
widget
is
RouteWidget
)
{
if
(
widget
.
name
!=
name
)
{
return
false
;
}
if
(
arguments
!=
null
&&
widget
.
arguments
!=
arguments
)
{
return
false
;
}
final
RouteWidgetState
state
=
(
candidate
as
StatefulElement
).
state
as
RouteWidgetState
;
if
(
count
!=
null
&&
state
.
counter
.
value
!=
count
)
{
return
false
;
}
return
true
;
}
return
false
;
}
}
packages/flutter/test/widgets/navigator_test.dart
View file @
fc85492d
...
...
@@ -1664,7 +1664,7 @@ void main() {
' The onGenerateRoute callback must never return null, unless an
\n
'
' onUnknownRoute callback is provided as well.
\n
'
' The Navigator was:
\n
'
' NavigatorState#
4d6bf(lifecycle state: created)
\n
'
,
' NavigatorState#
00000(lifecycle state: initialized)
\n
'
),
);
});
...
...
@@ -1690,7 +1690,7 @@ void main() {
' route "/".
\n
'
' The onUnknownRoute callback must never return null.
\n
'
' The Navigator was:
\n
'
' NavigatorState#
38036(lifecycle state: creat
ed)
\n
'
,
' NavigatorState#
00000(lifecycle state: initializ
ed)
\n
'
,
),
);
});
...
...
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