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
b1b3c1a3
Unverified
Commit
b1b3c1a3
authored
May 19, 2021
by
Michael Goderbauer
Committed by
GitHub
May 19, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
State Restoration for Router (#82727)
parent
2d283504
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
450 additions
and
143 deletions
+450
-143
app.dart
packages/flutter/lib/src/widgets/app.dart
+5
-4
router.dart
packages/flutter/lib/src/widgets/router.dart
+191
-96
router_restoration_test.dart
packages/flutter/test/widgets/router_restoration_test.dart
+167
-0
router_test.dart
packages/flutter/test/widgets/router_test.dart
+87
-43
No files found.
packages/flutter/lib/src/widgets/app.dart
View file @
b1b3c1a3
...
@@ -1099,10 +1099,10 @@ class WidgetsApp extends StatefulWidget {
...
@@ -1099,10 +1099,10 @@ class WidgetsApp extends StatefulWidget {
/// Providing a restoration ID inserts a [RootRestorationScope] into the
/// Providing a restoration ID inserts a [RootRestorationScope] into the
/// widget hierarchy, which enables state restoration for descendant widgets.
/// widget hierarchy, which enables state restoration for descendant widgets.
///
///
/// Providing a restoration ID also enables the [Navigator]
built by the
/// Providing a restoration ID also enables the [Navigator]
or [Router] built
///
[WidgetsApp] to restore its state (i.e. to restore the history stack of
///
by the [WidgetsApp] to restore its state (i.e. to restore the history
///
active [Route]s). See the documentation on [Navigator] for more details
///
stack of active [Route]s). See the documentation on [Navigator] for more
/// around state restoration of [Route]s.
///
details
around state restoration of [Route]s.
///
///
/// See also:
/// See also:
///
///
...
@@ -1518,6 +1518,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
...
@@ -1518,6 +1518,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
if
(
_usesRouter
)
{
if
(
_usesRouter
)
{
assert
(
_effectiveRouteInformationProvider
!=
null
);
assert
(
_effectiveRouteInformationProvider
!=
null
);
routing
=
Router
<
Object
>(
routing
=
Router
<
Object
>(
restorationScopeId:
'router'
,
routeInformationProvider:
_effectiveRouteInformationProvider
,
routeInformationProvider:
_effectiveRouteInformationProvider
,
routeInformationParser:
widget
.
routeInformationParser
,
routeInformationParser:
widget
.
routeInformationParser
,
routerDelegate:
widget
.
routerDelegate
!,
routerDelegate:
widget
.
routerDelegate
!,
...
...
packages/flutter/lib/src/widgets/router.dart
View file @
b1b3c1a3
...
@@ -13,6 +13,8 @@ import 'basic.dart';
...
@@ -13,6 +13,8 @@ import 'basic.dart';
import
'binding.dart'
;
import
'binding.dart'
;
import
'framework.dart'
;
import
'framework.dart'
;
import
'navigator.dart'
;
import
'navigator.dart'
;
import
'restoration.dart'
;
import
'restoration_properties.dart'
;
/// A piece of routing information.
/// A piece of routing information.
///
///
...
@@ -26,8 +28,16 @@ import 'navigator.dart';
...
@@ -26,8 +28,16 @@ import 'navigator.dart';
/// widget when a new [RouteInformation] is available. The [Router] widget takes
/// widget when a new [RouteInformation] is available. The [Router] widget takes
/// these information and navigates accordingly.
/// these information and navigates accordingly.
///
///
/// The latter case should only happen in a web application where the [Router]
/// The latter case happens in web application where the [Router] reports route
/// reports route changes back to web engine.
/// changes back to the web engine.
///
/// The current [RouteInformation] of an application is also used for state
/// restoration purposes. Before an application is killed, the [Router] converts
/// its current configurations into a [RouteInformation] object utilizing the
/// [RouteInformationProvider]. The [RouteInformation] object is then serialized
/// out and persisted. During state restoration, the object is deserialized and
/// passed back to the [RouteInformationProvider], which turns it into a
/// configuration for the [Router] again to restore its state from.
class
RouteInformation
{
class
RouteInformation
{
/// Creates a route information object.
/// Creates a route information object.
///
///
...
@@ -48,13 +58,17 @@ class RouteInformation {
...
@@ -48,13 +58,17 @@ class RouteInformation {
/// the text inside a [TextField] or the scroll position in a [ScrollView].
/// the text inside a [TextField] or the scroll position in a [ScrollView].
/// These widget states can be stored in the [state].
/// These widget states can be stored in the [state].
///
///
/// Currently, this information is only used by Flutter on the web:
/// On the web, this information is stored in the browser history when the
/// the data is stored in the browser history entry when the
/// [Router] reports this route information back to the web engine
/// [Router] reports this route information back to the web engine
/// through the [PlatformRouteInformationProvider]. The information
/// through the [PlatformRouteInformationProvider]. The information
/// is then passed back, along with the [location], when the user
/// is then passed back, along with the [location], when the user
/// clicks the back or forward buttons.
/// clicks the back or forward buttons.
///
///
/// This information is also serialized and persisted alongside the
/// [location] for state restoration purposes. During state restoration,
/// the information is made available again to the [Router] so it can restore
/// its configuration to the previous state.
///
/// The state must be serializable.
/// The state must be serializable.
final
Object
?
state
;
final
Object
?
state
;
}
}
...
@@ -226,6 +240,25 @@ class RouteInformation {
...
@@ -226,6 +240,25 @@ class RouteInformation {
/// [RouterDelegate.currentConfiguration] and
/// [RouterDelegate.currentConfiguration] and
/// [RouteInformationParser.restoreRouteInformation] APIs to provide an optimal
/// [RouteInformationParser.restoreRouteInformation] APIs to provide an optimal
/// user experience when running on the web platform.
/// user experience when running on the web platform.
///
/// ## State Restoration
///
/// The [Router] will restore the current configuration of the [routerDelegate]
/// during state restoration if it is configured with a [restorationScopeId] and
/// state restoration is enabled for the subtree. For that, the value of
/// [RouterDelegate.currentConfiguration] is serialized and persisted before the
/// app is killed by the operating system. After the app is restarted, the value
/// is deserialized and passed back to the [RouterDelegate] via a call to
/// [RouterDelegate.setRestoredRoutePath] (which by default just calls
/// [RouterDelegate.setNewRoutePath]). It is the responsibility of the
/// [RouterDelegate] to use the configuration information provided to restore
/// its internal state.
///
/// To serialize [RouterDelegate.currentConfiguration] and to deserialize it
/// again, the [Router] calls [RouteInformationParser.restoreRouteInformation]
/// and [RouteInformationParser.parseRouteInformation], respectively. Therefore,
/// if a [restorationScopeId] is provided, a [routeInformationParser] must be
/// configured as well.
class
Router
<
T
>
extends
StatefulWidget
{
class
Router
<
T
>
extends
StatefulWidget
{
/// Creates a router.
/// Creates a router.
///
///
...
@@ -233,8 +266,8 @@ class Router<T> extends StatefulWidget {
...
@@ -233,8 +266,8 @@ class Router<T> extends StatefulWidget {
/// router does not depend on route information. A common example is a sub router
/// router does not depend on route information. A common example is a sub router
/// that builds its content completely based on the app state.
/// that builds its content completely based on the app state.
///
///
/// If the [routeInformationProvider]
is not null, the [routeInformationParser] must
/// If the [routeInformationProvider]
or [restorationScopeId] is not null, then
/// also not be null.
///
[routeInformationParser] must
also not be null.
///
///
/// The [routerDelegate] must not be null.
/// The [routerDelegate] must not be null.
const
Router
({
const
Router
({
...
@@ -243,11 +276,10 @@ class Router<T> extends StatefulWidget {
...
@@ -243,11 +276,10 @@ class Router<T> extends StatefulWidget {
this
.
routeInformationParser
,
this
.
routeInformationParser
,
required
this
.
routerDelegate
,
required
this
.
routerDelegate
,
this
.
backButtonDispatcher
,
this
.
backButtonDispatcher
,
this
.
restorationScopeId
,
})
:
assert
(
})
:
assert
(
(
routeInformationProvider
==
null
)
==
(
routeInformationParser
==
null
),
(
routeInformationProvider
==
null
&&
restorationScopeId
==
null
)
||
routeInformationParser
!=
null
,
'Both routeInformationProvider and routeInformationParser must be provided '
'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.'
'if this router parses route information. Otherwise, they should both '
'be null.'
,
),
),
assert
(
routerDelegate
!=
null
),
assert
(
routerDelegate
!=
null
),
super
(
key:
key
);
super
(
key:
key
);
...
@@ -293,6 +325,27 @@ class Router<T> extends StatefulWidget {
...
@@ -293,6 +325,27 @@ class Router<T> extends StatefulWidget {
/// router, or the [ChildBackButtonDispatcher] for other routers.
/// router, or the [ChildBackButtonDispatcher] for other routers.
final
BackButtonDispatcher
?
backButtonDispatcher
;
final
BackButtonDispatcher
?
backButtonDispatcher
;
/// Restoration ID to save and restore the state of the [Router].
///
/// If non-null, the [Router] will persist the [RouterDelegate]'s current
/// configuration (i.e. [RouterDelegate.currentConfiguration]). During state
/// restoration, the [Router] informs the [RouterDelegate] of the previous
/// configuration by calling [RouterDelegate.setRestoredRoutePath] (which by
/// default just calls [RouterDelegate.setNewRoutePath]). It is the
/// responsibility of the [RouterDelegate] to restore its internal state based
/// on the provided configuration.
///
/// The router uses the [RouteInformationParser] to serialize and deserialize
/// [RouterDelegate.currentConfiguration]. Therefore, a
/// [routeInformationParser] must be provided when [restorationScopeId] is
/// non-null.
///
/// See also:
///
/// * [RestorationManager], which explains how state restoration works in
/// Flutter.
final
String
?
restorationScopeId
;
/// Retrieves the immediate [Router] ancestor from the given context.
/// Retrieves the immediate [Router] ancestor from the given context.
///
///
/// This method provides access to the delegates in the [Router]. For example,
/// This method provides access to the delegates in the [Router]. For example,
...
@@ -401,6 +454,7 @@ class Router<T> extends StatefulWidget {
...
@@ -401,6 +454,7 @@ class Router<T> extends StatefulWidget {
}
}
typedef
_AsyncPassthrough
<
Q
>
=
Future
<
Q
>
Function
(
Q
);
typedef
_AsyncPassthrough
<
Q
>
=
Future
<
Q
>
Function
(
Q
);
typedef
_DelegateRouteSetter
<
T
>
=
Future
<
void
>
Function
(
T
);
// Whether to report the route information in this build cycle.
// Whether to report the route information in this build cycle.
enum
_IntentionToReportRouteInformation
{
enum
_IntentionToReportRouteInformation
{
...
@@ -414,10 +468,14 @@ enum _IntentionToReportRouteInformation {
...
@@ -414,10 +468,14 @@ enum _IntentionToReportRouteInformation {
ignore
,
ignore
,
}
}
class
_RouterState
<
T
>
extends
State
<
Router
<
T
>>
{
class
_RouterState
<
T
>
extends
State
<
Router
<
T
>>
with
RestorationMixin
{
Object
?
_currentRouteInformationParserTransaction
;
Object
?
_currentRouteInformationParserTransaction
;
Object
?
_currentRouterDelegateTransaction
;
Object
?
_currentRouterDelegateTransaction
;
late
_IntentionToReportRouteInformation
_currentIntentionToReport
;
_IntentionToReportRouteInformation
_currentIntentionToReport
=
_IntentionToReportRouteInformation
.
none
;
final
_RestorableRouteInformation
_routeInformation
=
_RestorableRouteInformation
();
@override
String
?
get
restorationId
=>
widget
.
restorationScopeId
;
@override
@override
void
initState
()
{
void
initState
()
{
...
@@ -425,9 +483,15 @@ class _RouterState<T> extends State<Router<T>> {
...
@@ -425,9 +483,15 @@ class _RouterState<T> extends State<Router<T>> {
widget
.
routeInformationProvider
?.
addListener
(
_handleRouteInformationProviderNotification
);
widget
.
routeInformationProvider
?.
addListener
(
_handleRouteInformationProviderNotification
);
widget
.
backButtonDispatcher
?.
addCallback
(
_handleBackButtonDispatcherNotification
);
widget
.
backButtonDispatcher
?.
addCallback
(
_handleBackButtonDispatcherNotification
);
widget
.
routerDelegate
.
addListener
(
_handleRouterDelegateNotification
);
widget
.
routerDelegate
.
addListener
(
_handleRouterDelegateNotification
);
_currentIntentionToReport
=
_IntentionToReportRouteInformation
.
none
;
}
if
(
widget
.
routeInformationProvider
!=
null
)
{
_processInitialRoute
();
@override
void
restoreState
(
RestorationBucket
?
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
_routeInformation
,
'route'
);
if
(
_routeInformation
.
value
!=
null
)
{
_processRouteInformation
(
_routeInformation
.
value
!,
()
=>
widget
.
routerDelegate
.
setRestoredRoutePath
);
}
else
if
(
widget
.
routeInformationProvider
!=
null
)
{
_processRouteInformation
(
widget
.
routeInformationProvider
!.
value
!,
()
=>
widget
.
routerDelegate
.
setInitialRoutePath
);
}
}
}
}
...
@@ -436,7 +500,7 @@ class _RouterState<T> extends State<Router<T>> {
...
@@ -436,7 +500,7 @@ class _RouterState<T> extends State<Router<T>> {
String
?
_lastSeenLocation
;
String
?
_lastSeenLocation
;
void
_scheduleRouteInformationReportingTask
()
{
void
_scheduleRouteInformationReportingTask
()
{
if
(
_routeInformationReportingTaskScheduled
)
if
(
_routeInformationReportingTaskScheduled
||
widget
.
routeInformationProvider
==
null
)
return
;
return
;
assert
(
_currentIntentionToReport
!=
_IntentionToReportRouteInformation
.
none
);
assert
(
_currentIntentionToReport
!=
_IntentionToReportRouteInformation
.
none
);
_routeInformationReportingTaskScheduled
=
true
;
_routeInformationReportingTaskScheduled
=
true
;
...
@@ -447,62 +511,33 @@ class _RouterState<T> extends State<Router<T>> {
...
@@ -447,62 +511,33 @@ class _RouterState<T> extends State<Router<T>> {
assert
(
_routeInformationReportingTaskScheduled
);
assert
(
_routeInformationReportingTaskScheduled
);
_routeInformationReportingTaskScheduled
=
false
;
_routeInformationReportingTaskScheduled
=
false
;
if
(
_routeInformation
.
value
!=
null
)
{
final
RouteInformation
routeInformation
=
_routeInformation
.
value
!;
switch
(
_currentIntentionToReport
)
{
switch
(
_currentIntentionToReport
)
{
case
_IntentionToReportRouteInformation
.
none
:
case
_IntentionToReportRouteInformation
.
none
:
assert
(
false
);
assert
(
false
,
'_reportRouteInformation must not be called with _IntentionToReportRouteInformation.none'
);
return
;
return
;
case
_IntentionToReportRouteInformation
.
ignore
:
case
_IntentionToReportRouteInformation
.
ignore
:
// In the ignore case, we still want to update the _lastSeenLocation.
break
;
final
RouteInformation
?
routeInformation
=
_retrieveNewRouteInformation
();
if
(
routeInformation
!=
null
)
{
_lastSeenLocation
=
routeInformation
.
location
;
}
_currentIntentionToReport
=
_IntentionToReportRouteInformation
.
none
;
return
;
case
_IntentionToReportRouteInformation
.
maybe
:
case
_IntentionToReportRouteInformation
.
maybe
:
final
RouteInformation
?
routeInformation
=
_retrieveNewRouteInformation
();
if
(
routeInformation
!=
null
)
{
if
(
_lastSeenLocation
!=
routeInformation
.
location
)
{
if
(
_lastSeenLocation
!=
routeInformation
.
location
)
{
widget
.
routeInformationProvider
!.
routerReportsNewRouteInformation
(
routeInformation
);
widget
.
routeInformationProvider
!.
routerReportsNewRouteInformation
(
routeInformation
);
_lastSeenLocation
=
routeInformation
.
location
;
}
}
}
break
;
_currentIntentionToReport
=
_IntentionToReportRouteInformation
.
none
;
return
;
case
_IntentionToReportRouteInformation
.
must
:
case
_IntentionToReportRouteInformation
.
must
:
final
RouteInformation
?
routeInformation
=
_retrieveNewRouteInformation
();
if
(
routeInformation
!=
null
)
{
widget
.
routeInformationProvider
!.
routerReportsNewRouteInformation
(
routeInformation
);
widget
.
routeInformationProvider
!.
routerReportsNewRouteInformation
(
routeInformation
);
break
;
}
_lastSeenLocation
=
routeInformation
.
location
;
_lastSeenLocation
=
routeInformation
.
location
;
}
}
_currentIntentionToReport
=
_IntentionToReportRouteInformation
.
none
;
_currentIntentionToReport
=
_IntentionToReportRouteInformation
.
none
;
return
;
}
}
}
RouteInformation
?
_retrieveNewRouteInformation
()
{
RouteInformation
?
_retrieveNewRouteInformation
()
{
final
T
?
configuration
=
widget
.
routerDelegate
.
currentConfiguration
;
final
T
?
configuration
=
widget
.
routerDelegate
.
currentConfiguration
;
if
(
configuration
==
null
)
if
(
configuration
==
null
)
return
null
;
return
null
;
final
RouteInformation
?
routeInformation
=
widget
.
routeInformationParser
!.
restoreRouteInformation
(
configuration
);
return
widget
.
routeInformationParser
?.
restoreRouteInformation
(
configuration
);
assert
((){
if
(
routeInformation
==
null
)
{
FlutterError
.
reportError
(
const
FlutterErrorDetails
(
exception:
'Router.routeInformationParser returns a null RouteInformation. '
'If you opt for route information reporting, the '
'routeInformationParser must not report null for a given '
'configuration.'
,
),
);
}
return
true
;
}());
return
routeInformation
;
}
}
void
_setStateWithExplicitReportStatus
(
void
_setStateWithExplicitReportStatus
(
...
@@ -532,6 +567,7 @@ class _RouterState<T> extends State<Router<T>> {
...
@@ -532,6 +567,7 @@ class _RouterState<T> extends State<Router<T>> {
}
}
void
_maybeNeedToReportRouteInformation
()
{
void
_maybeNeedToReportRouteInformation
()
{
_routeInformation
.
value
=
_retrieveNewRouteInformation
();
_currentIntentionToReport
=
_currentIntentionToReport
!=
_IntentionToReportRouteInformation
.
none
_currentIntentionToReport
=
_currentIntentionToReport
!=
_IntentionToReportRouteInformation
.
none
?
_currentIntentionToReport
?
_currentIntentionToReport
:
_IntentionToReportRouteInformation
.
maybe
;
:
_IntentionToReportRouteInformation
.
maybe
;
...
@@ -582,28 +618,21 @@ class _RouterState<T> extends State<Router<T>> {
...
@@ -582,28 +618,21 @@ class _RouterState<T> extends State<Router<T>> {
super
.
dispose
();
super
.
dispose
();
}
}
void
_process
InitialRoute
(
)
{
void
_process
RouteInformation
(
RouteInformation
information
,
ValueGetter
<
_DelegateRouteSetter
<
T
>>
delegateRouteSetter
)
{
_currentRouteInformationParserTransaction
=
Object
();
_currentRouteInformationParserTransaction
=
Object
();
_currentRouterDelegateTransaction
=
Object
();
_currentRouterDelegateTransaction
=
Object
();
_lastSeenLocation
=
widget
.
routeInformationProvider
!.
value
!
.
location
;
_lastSeenLocation
=
information
.
location
;
widget
.
routeInformationParser
!
widget
.
routeInformationParser
!
.
parseRouteInformation
(
widget
.
routeInformationProvider
!.
value
!
)
.
parseRouteInformation
(
information
)
.
then
<
T
>(
_verifyRouteInformationParserStillCurrent
(
_currentRouteInformationParserTransaction
,
widget
))
.
then
<
T
>(
_verifyRouteInformationParserStillCurrent
(
_currentRouteInformationParserTransaction
,
widget
))
.
then
<
void
>(
widget
.
routerDelegate
.
setInitialRoutePath
)
.
then
<
void
>(
delegateRouteSetter
()
)
.
then
<
void
>(
_verifyRouterDelegatePushStillCurrent
(
_currentRouterDelegateTransaction
,
widget
))
.
then
<
void
>(
_verifyRouterDelegatePushStillCurrent
(
_currentRouterDelegateTransaction
,
widget
))
.
then
<
void
>(
_rebuild
);
.
then
<
void
>(
_rebuild
);
}
}
void
_handleRouteInformationProviderNotification
()
{
void
_handleRouteInformationProviderNotification
()
{
_currentRouteInformationParserTransaction
=
Object
();
assert
(
widget
.
routeInformationProvider
!.
value
!=
null
);
_currentRouterDelegateTransaction
=
Object
();
_processRouteInformation
(
widget
.
routeInformationProvider
!.
value
!,
()
=>
widget
.
routerDelegate
.
setNewRoutePath
);
_lastSeenLocation
=
widget
.
routeInformationProvider
!.
value
!.
location
;
widget
.
routeInformationParser
!
.
parseRouteInformation
(
widget
.
routeInformationProvider
!.
value
!)
.
then
<
T
>(
_verifyRouteInformationParserStillCurrent
(
_currentRouteInformationParserTransaction
,
widget
))
.
then
<
void
>(
widget
.
routerDelegate
.
setNewRoutePath
)
.
then
<
void
>(
_verifyRouterDelegatePushStillCurrent
(
_currentRouterDelegateTransaction
,
widget
))
.
then
<
void
>(
_rebuild
);
}
}
Future
<
bool
>
_handleBackButtonDispatcherNotification
()
{
Future
<
bool
>
_handleBackButtonDispatcherNotification
()
{
...
@@ -614,7 +643,6 @@ class _RouterState<T> extends State<Router<T>> {
...
@@ -614,7 +643,6 @@ class _RouterState<T> extends State<Router<T>> {
.
then
<
bool
>(
_verifyRouterDelegatePopStillCurrent
(
_currentRouterDelegateTransaction
,
widget
))
.
then
<
bool
>(
_verifyRouterDelegatePopStillCurrent
(
_currentRouterDelegateTransaction
,
widget
))
.
then
<
bool
>((
bool
data
)
{
.
then
<
bool
>((
bool
data
)
{
_rebuild
();
_rebuild
();
_maybeNeedToReportRouteInformation
();
return
SynchronousFuture
<
bool
>(
data
);
return
SynchronousFuture
<
bool
>(
data
);
});
});
}
}
...
@@ -663,6 +691,7 @@ class _RouterState<T> extends State<Router<T>> {
...
@@ -663,6 +691,7 @@ class _RouterState<T> extends State<Router<T>> {
Future
<
void
>
_rebuild
([
void
value
])
{
Future
<
void
>
_rebuild
([
void
value
])
{
setState
(()
{
/* routerDelegate is ready to rebuild */
});
setState
(()
{
/* routerDelegate is ready to rebuild */
});
_maybeNeedToReportRouteInformation
();
return
SynchronousFuture
<
void
>(
value
);
return
SynchronousFuture
<
void
>(
value
);
}
}
...
@@ -673,7 +702,9 @@ class _RouterState<T> extends State<Router<T>> {
...
@@ -673,7 +702,9 @@ class _RouterState<T> extends State<Router<T>> {
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
_RouterScope
(
return
UnmanagedRestorationScope
(
bucket:
bucket
,
child:
_RouterScope
(
routeInformationProvider:
widget
.
routeInformationProvider
,
routeInformationProvider:
widget
.
routeInformationProvider
,
backButtonDispatcher:
widget
.
backButtonDispatcher
,
backButtonDispatcher:
widget
.
backButtonDispatcher
,
routeInformationParser:
widget
.
routeInformationParser
,
routeInformationParser:
widget
.
routeInformationParser
,
...
@@ -684,6 +715,7 @@ class _RouterState<T> extends State<Router<T>> {
...
@@ -684,6 +715,7 @@ class _RouterState<T> extends State<Router<T>> {
// will have a BuildContext that contains the _RouterScope.
// will have a BuildContext that contains the _RouterScope.
builder:
widget
.
routerDelegate
.
build
,
builder:
widget
.
routerDelegate
.
build
,
),
),
),
);
);
}
}
}
}
...
@@ -1094,8 +1126,9 @@ abstract class RouteInformationParser<T> {
...
@@ -1094,8 +1126,9 @@ abstract class RouteInformationParser<T> {
/// Restore the route information from the given configuration.
/// Restore the route information from the given configuration.
///
///
/// This may return null, in which case the browser history will not be updated.
/// This may return null, in which case the browser history will not be
/// See [Router]'s documentation for details.
/// updated and state restoration is disabled. See [Router]'s documentation
/// for details.
///
///
/// The [parseRouteInformation] method must produce an equivalent
/// The [parseRouteInformation] method must produce an equivalent
/// configuration when passed this method's return value.
/// configuration when passed this method's return value.
...
@@ -1125,6 +1158,17 @@ abstract class RouteInformationParser<T> {
...
@@ -1125,6 +1158,17 @@ abstract class RouteInformationParser<T> {
///
///
/// All subclass must implement [setNewRoutePath], [popRoute], and [build].
/// All subclass must implement [setNewRoutePath], [popRoute], and [build].
///
///
/// ## State Restoration
///
/// If the [Router] owning this delegate is configured for state restoration, it
/// will persist and restore the configuration of this [RouterDelegate] using
/// the following mechanism: Before the app is killed by the operating system,
/// the value of [currentConfiguration] is serialized out and persisted. After
/// the app has restarted, the value is deserialized and passed back to the
/// [RouterDelegate] via a call to [setRestoredRoutePath] (which by default just
/// calls [setNewRoutePath]). It is the responsibility of the [RouterDelegate]
/// to use the configuration information provided to restore its internal state.
///
/// See also:
/// See also:
///
///
/// * [RouteInformationParser], which is responsible for parsing the route
/// * [RouteInformationParser], which is responsible for parsing the route
...
@@ -1143,10 +1187,29 @@ abstract class RouterDelegate<T> extends Listenable {
...
@@ -1143,10 +1187,29 @@ abstract class RouterDelegate<T> extends Listenable {
/// Consider using a [SynchronousFuture] if the result can be computed
/// Consider using a [SynchronousFuture] if the result can be computed
/// synchronously, so that the [Router] does not need to wait for the next
/// synchronously, so that the [Router] does not need to wait for the next
/// microtask to schedule a build.
/// microtask to schedule a build.
///
/// See also:
///
/// * [setRestoredRoutePath], which is called instead of this method during
/// state restoration.
Future
<
void
>
setInitialRoutePath
(
T
configuration
)
{
Future
<
void
>
setInitialRoutePath
(
T
configuration
)
{
return
setNewRoutePath
(
configuration
);
return
setNewRoutePath
(
configuration
);
}
}
/// Called by the [Router] during state restoration.
///
/// When the [Router] is configured for state restoration, it will persist
/// the value of [currentConfiguration] during state serialization. During
/// state restoration, the [Router] calls this method (instead of
/// [setInitialRoutePath]) to pass the previous configuration back to the
/// delegate. It is the responsibility of the delegate to restore its internal
/// state based on the provided configuration.
///
/// By default, this method forwards the `configuration` to [setNewRoutePath].
Future
<
void
>
setRestoredRoutePath
(
T
configuration
)
{
return
setNewRoutePath
(
configuration
);
}
/// Called by the [Router] when the [Router.routeInformationProvider] reports that a
/// Called by the [Router] when the [Router.routeInformationProvider] reports that a
/// new route has been pushed to the application by the operating system.
/// new route has been pushed to the application by the operating system.
///
///
...
@@ -1188,23 +1251,30 @@ abstract class RouterDelegate<T> extends Listenable {
...
@@ -1188,23 +1251,30 @@ abstract class RouterDelegate<T> extends Listenable {
/// At most one [Router] can opt in to route information reporting. Typically,
/// At most one [Router] can opt in to route information reporting. Typically,
/// only the top-most [Router] created by [WidgetsApp.router] should opt for
/// only the top-most [Router] created by [WidgetsApp.router] should opt for
/// route information reporting.
/// route information reporting.
///
/// ## State Restoration
///
/// This getter is also used by the [Router] to implement state restoration.
/// During state serialization, the [Router] will persist the current
/// configuration and during state restoration pass it back to the delegate
/// by calling [setRestoredRoutePath].
T
?
get
currentConfiguration
=>
null
;
T
?
get
currentConfiguration
=>
null
;
/// Called by the [Router] to obtain the widget tree that represents the
/// Called by the [Router] to obtain the widget tree that represents the
/// current state.
/// current state.
///
///
/// This is called whenever the [setInitialRoutePath] method's future
/// This is called whenever the [Future]s returned by [setInitialRoutePath],
/// completes, the [setNewRoutePath] method's future completes with the value
/// [setNewRoutePath], or [setRestoredRoutePath] complete as well as when this
/// true, the [popRoute] method's future completes with the value true, or
/// notifies its clients (see the [Listenable] interface, which this interface
/// this object notifies its clients (see the [Listenable] interface, which
/// includes). In addition, it may be called at other times. It is important,
/// this interface includes). In addition, it may be called at other times. It
/// therefore, that the methods above do not update the state that the [build]
/// is important, therefore, that the methods above do not update the state
/// method uses before they complete their respective futures.
/// that the [build] method uses before they complete their respective
/// futures.
///
///
/// Typically this method returns a suitably-configured [Navigator]. If you do
/// Typically this method returns a suitably-configured [Navigator]. If you do
/// plan to create a navigator, consider using the
/// plan to create a navigator, consider using the
/// [PopNavigatorRouterDelegateMixin].
/// [PopNavigatorRouterDelegateMixin]. If state restoration is enabled for the
/// [Router] using this delegate, consider providing a non-null
/// [Navigator.restorationScopeId] to the [Navigator] returned by this method.
///
///
/// This method must not return null.
/// This method must not return null.
///
///
...
@@ -1340,3 +1410,28 @@ mixin PopNavigatorRouterDelegateMixin<T> on RouterDelegate<T> {
...
@@ -1340,3 +1410,28 @@ mixin PopNavigatorRouterDelegateMixin<T> on RouterDelegate<T> {
return
navigator
.
maybePop
();
return
navigator
.
maybePop
();
}
}
}
}
class
_RestorableRouteInformation
extends
RestorableValue
<
RouteInformation
?>
{
@override
RouteInformation
?
createDefaultValue
()
=>
null
;
@override
void
didUpdateValue
(
RouteInformation
?
oldValue
)
{
notifyListeners
();
}
@override
RouteInformation
?
fromPrimitives
(
Object
?
data
)
{
if
(
data
==
null
)
{
return
null
;
}
assert
(
data
is
List
<
Object
?>
&&
data
.
length
==
2
);
final
List
<
Object
?>
castedData
=
data
as
List
<
Object
?>;
return
RouteInformation
(
location:
castedData
.
first
as
String
?,
state:
castedData
.
last
);
}
@override
Object
?
toPrimitives
()
{
return
value
==
null
?
null
:
<
Object
?>[
value
!.
location
,
value
!.
state
];
}
}
packages/flutter/test/widgets/router_restoration_test.dart
0 → 100644
View file @
b1b3c1a3
// 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.
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Router state restoration without RouteInfomrationProvider'
,
(
WidgetTester
tester
)
async
{
final
UniqueKey
router
=
UniqueKey
();
_TestRouterDelegate
delegate
()
=>
tester
.
widget
<
Router
<
Object
?>>(
find
.
byKey
(
router
)).
routerDelegate
as
_TestRouterDelegate
;
await
tester
.
pumpWidget
(
_TestWidget
(
routerKey:
router
));
expect
(
find
.
text
(
'Current config: null'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
isEmpty
);
expect
(
delegate
().
restoredRoutePaths
,
isEmpty
);
delegate
().
currentConfiguration
=
'/foo'
;
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Current config: /foo'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
isEmpty
);
expect
(
delegate
().
restoredRoutePaths
,
isEmpty
);
await
tester
.
restartAndRestore
();
expect
(
find
.
text
(
'Current config: /foo'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
isEmpty
);
expect
(
delegate
().
restoredRoutePaths
,
<
String
>[
'/foo'
]);
final
TestRestorationData
restorationData
=
await
tester
.
getRestorationData
();
delegate
().
currentConfiguration
=
'/bar'
;
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Current config: /bar'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
isEmpty
);
expect
(
delegate
().
restoredRoutePaths
,
<
String
>[
'/foo'
]);
await
tester
.
restoreFrom
(
restorationData
);
expect
(
find
.
text
(
'Current config: /foo'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
isEmpty
);
expect
(
delegate
().
restoredRoutePaths
,
<
String
>[
'/foo'
,
'/foo'
]);
});
testWidgets
(
'Router state restoration with RouteInfomrationProvider'
,
(
WidgetTester
tester
)
async
{
final
UniqueKey
router
=
UniqueKey
();
_TestRouterDelegate
delegate
()
=>
tester
.
widget
<
Router
<
Object
?>>(
find
.
byKey
(
router
)).
routerDelegate
as
_TestRouterDelegate
;
_TestRouteInformationProvider
provider
()
=>
tester
.
widget
<
Router
<
Object
?>>(
find
.
byKey
(
router
)).
routeInformationProvider
!
as
_TestRouteInformationProvider
;
await
tester
.
pumpWidget
(
_TestWidget
(
routerKey:
router
,
withInformationProvider:
true
));
expect
(
find
.
text
(
'Current config: /home'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
<
String
>[
'/home'
]);
expect
(
delegate
().
restoredRoutePaths
,
isEmpty
);
provider
().
value
=
const
RouteInformation
(
location:
'/foo'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Current config: /foo'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
<
String
>[
'/home'
,
'/foo'
]);
expect
(
delegate
().
restoredRoutePaths
,
isEmpty
);
await
tester
.
restartAndRestore
();
expect
(
find
.
text
(
'Current config: /foo'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
isEmpty
);
expect
(
delegate
().
restoredRoutePaths
,
<
String
>[
'/foo'
]);
final
TestRestorationData
restorationData
=
await
tester
.
getRestorationData
();
provider
().
value
=
const
RouteInformation
(
location:
'/bar'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Current config: /bar'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
<
String
>[
'/bar'
]);
expect
(
delegate
().
restoredRoutePaths
,
<
String
>[
'/foo'
]);
await
tester
.
restoreFrom
(
restorationData
);
expect
(
find
.
text
(
'Current config: /foo'
),
findsOneWidget
);
expect
(
delegate
().
newRoutePaths
,
<
String
>[
'/bar'
]);
expect
(
delegate
().
restoredRoutePaths
,
<
String
>[
'/foo'
,
'/foo'
]);
});
}
class
_TestRouteInformationParser
extends
RouteInformationParser
<
String
>
{
@override
Future
<
String
>
parseRouteInformation
(
RouteInformation
routeInformation
)
{
return
SynchronousFuture
<
String
>(
routeInformation
.
location
!);
}
@override
RouteInformation
?
restoreRouteInformation
(
String
configuration
)
{
return
RouteInformation
(
location:
configuration
);
}
}
class
_TestRouterDelegate
extends
RouterDelegate
<
String
>
with
ChangeNotifier
{
final
List
<
String
>
newRoutePaths
=
<
String
>[];
final
List
<
String
>
restoredRoutePaths
=
<
String
>[];
@override
String
?
get
currentConfiguration
=>
_currentConfiguration
;
String
?
_currentConfiguration
;
set
currentConfiguration
(
String
?
value
)
{
if
(
value
==
_currentConfiguration
)
{
return
;
}
_currentConfiguration
=
value
;
notifyListeners
();
}
@override
Future
<
void
>
setNewRoutePath
(
String
configuration
)
{
_currentConfiguration
=
configuration
;
newRoutePaths
.
add
(
configuration
);
return
SynchronousFuture
<
void
>(
null
);
}
@override
Future
<
void
>
setRestoredRoutePath
(
String
configuration
)
{
_currentConfiguration
=
configuration
;
restoredRoutePaths
.
add
(
configuration
);
return
SynchronousFuture
<
void
>(
null
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
Text
(
'Current config:
$currentConfiguration
'
,
textDirection:
TextDirection
.
ltr
);
}
@override
Future
<
bool
>
popRoute
()
async
=>
throw
UnimplementedError
();
}
class
_TestRouteInformationProvider
extends
RouteInformationProvider
with
ChangeNotifier
{
@override
RouteInformation
?
get
value
=>
_value
;
RouteInformation
?
_value
=
const
RouteInformation
(
location:
'/home'
);
set
value
(
RouteInformation
?
value
)
{
if
(
value
==
_value
)
{
return
;
}
_value
=
value
;
notifyListeners
();
}
}
class
_TestWidget
extends
StatefulWidget
{
const
_TestWidget
({
Key
?
key
,
this
.
withInformationProvider
=
false
,
this
.
routerKey
})
:
super
(
key:
key
);
final
bool
withInformationProvider
;
final
Key
?
routerKey
;
@override
State
<
_TestWidget
>
createState
()
=>
_TestWidgetState
();
}
class
_TestWidgetState
extends
State
<
_TestWidget
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
RootRestorationScope
(
restorationId:
'root'
,
child:
Router
<
String
>(
key:
widget
.
routerKey
,
restorationScopeId:
'router'
,
routerDelegate:
_TestRouterDelegate
(),
routeInformationParser:
_TestRouteInformationParser
(),
routeInformationProvider:
widget
.
withInformationProvider
?
_TestRouteInformationProvider
()
:
null
,
),
);
}
}
packages/flutter/test/widgets/router_test.dart
View file @
b1b3c1a3
...
@@ -85,17 +85,13 @@ void main() {
...
@@ -85,17 +85,13 @@ void main() {
final
BuildContext
textContext
=
key
.
currentContext
!;
final
BuildContext
textContext
=
key
.
currentContext
!;
// This should not throw error.
// This should not throw error.
Router
<
dynamic
>?
router
=
Router
.
maybeOf
(
textContext
);
final
Router
<
dynamic
>?
router
=
Router
.
maybeOf
(
textContext
);
expect
(
router
,
isNull
);
expect
(
router
,
isNull
);
bool
hasFlutterError
=
false
;
expect
(
try
{
()
=>
Router
.
of
(
textContext
),
router
=
Router
.
of
(
textContext
);
throwsA
(
isFlutterError
.
having
((
FlutterError
e
)
=>
e
.
message
,
'message'
,
startsWith
(
'Router'
)))
}
on
FlutterError
catch
(
e
)
{
);
expect
(
e
.
message
.
startsWith
(
'Router'
),
isTrue
);
hasFlutterError
=
true
;
}
expect
(
hasFlutterError
,
isTrue
);
});
});
testWidgets
(
'Simple router can handle pop route'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Simple router can handle pop route'
,
(
WidgetTester
tester
)
async
{
...
@@ -137,12 +133,13 @@ void main() {
...
@@ -137,12 +133,13 @@ void main() {
expect
(
find
.
text
(
'popped'
),
findsOneWidget
);
expect
(
find
.
text
(
'popped'
),
findsOneWidget
);
});
});
testWidgets
(
'Router throw when pass
es only routeInformationProvid
er'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Router throw when pass
ing routeInformationProvider without routeInformationPars
er'
,
(
WidgetTester
tester
)
async
{
final
SimpleRouteInformationProvider
provider
=
SimpleRouteInformationProvider
();
final
SimpleRouteInformationProvider
provider
=
SimpleRouteInformationProvider
();
provider
.
value
=
const
RouteInformation
(
provider
.
value
=
const
RouteInformation
(
location:
'initial'
,
location:
'initial'
,
);
);
try
{
expect
(
()
{
Router
<
RouteInformation
>(
Router
<
RouteInformation
>(
routeInformationProvider:
provider
,
routeInformationProvider:
provider
,
routerDelegate:
SimpleRouterDelegate
(
routerDelegate:
SimpleRouterDelegate
(
...
@@ -151,32 +148,33 @@ void main() {
...
@@ -151,32 +148,33 @@ void main() {
},
},
),
),
);
);
}
on
AssertionError
catch
(
e
)
{
},
expect
(
throwsA
(
isAssertionError
.
having
(
e
.
message
,
(
AssertionError
e
)
=>
e
.
message
,
'Both routeInformationProvider and routeInformationParser must be provided if this router '
'message'
,
'parses route information. Otherwise, they should both be null.'
,
'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.'
,
)),
);
);
}
});
});
testWidgets
(
'Router throw when passes only routeInformationParser'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Router throw when passing restorationId without routeInformationParser'
,
(
WidgetTester
tester
)
async
{
try
{
expect
(
()
{
Router
<
RouteInformation
>(
Router
<
RouteInformation
>(
routeInformationParser:
SimpleRouteInformationParser
()
,
restorationScopeId:
'foo'
,
routerDelegate:
SimpleRouterDelegate
(
routerDelegate:
SimpleRouterDelegate
(
builder:
(
BuildContext
context
,
RouteInformation
?
information
)
{
builder:
(
BuildContext
context
,
RouteInformation
?
information
)
{
return
Text
(
information
!.
location
!);
return
Text
(
information
!.
location
!);
},
},
),
),
);
);
}
on
AssertionError
catch
(
e
)
{
},
expect
(
throwsA
(
isAssertionError
.
having
(
e
.
message
,
(
AssertionError
e
)
=>
e
.
message
,
'Both routeInformationProvider and routeInformationParser must be provided if this router '
'message'
,
'parses route information. Otherwise, they should both be null.'
,
'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.'
,
)),
);
);
}
});
});
testWidgets
(
'PopNavigatorRouterDelegateMixin works'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'PopNavigatorRouterDelegateMixin works'
,
(
WidgetTester
tester
)
async
{
...
@@ -1091,6 +1089,35 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
...
@@ -1091,6 +1089,35 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
await
tester
.
pump
();
await
tester
.
pump
();
expect
(
find
.
text
(
'second callback'
),
findsOneWidget
);
expect
(
find
.
text
(
'second callback'
),
findsOneWidget
);
});
});
testWidgets
(
'Router reports location if it is different from location given by OS'
,
(
WidgetTester
tester
)
async
{
final
List
<
RouteInformation
>
reportedRouteInformation
=
<
RouteInformation
>[];
final
SimpleRouteInformationProvider
provider
=
SimpleRouteInformationProvider
(
onRouterReport:
reportedRouteInformation
.
add
,
)..
value
=
const
RouteInformation
(
location:
'/home'
);
await
tester
.
pumpWidget
(
buildBoilerPlate
(
Router
<
RouteInformation
>(
routeInformationProvider:
provider
,
routeInformationParser:
RedirectingInformationParser
(<
String
,
RouteInformation
>{
'/doesNotExist'
:
const
RouteInformation
(
location:
'/404'
),
}),
routerDelegate:
SimpleRouterDelegate
(
builder:
(
BuildContext
_
,
RouteInformation
?
info
)
=>
Text
(
'Current route:
${info?.location}
'
),
reportConfiguration:
true
,
),
),
));
expect
(
find
.
text
(
'Current route: /home'
),
findsOneWidget
);
expect
(
reportedRouteInformation
,
isEmpty
);
provider
.
value
=
const
RouteInformation
(
location:
'/doesNotExist'
);
await
tester
.
pump
();
expect
(
find
.
text
(
'Current route: /404'
),
findsOneWidget
);
expect
(
reportedRouteInformation
.
single
.
location
,
'/404'
);
});
}
}
Widget
buildBoilerPlate
(
Widget
child
)
{
Widget
buildBoilerPlate
(
Widget
child
)
{
...
@@ -1275,3 +1302,20 @@ class SimpleAsyncRouterDelegate extends RouterDelegate<RouteInformation> with Ch
...
@@ -1275,3 +1302,20 @@ class SimpleAsyncRouterDelegate extends RouterDelegate<RouteInformation> with Ch
@override
@override
Widget
build
(
BuildContext
context
)
=>
builder
(
context
,
routeInformation
);
Widget
build
(
BuildContext
context
)
=>
builder
(
context
,
routeInformation
);
}
}
class
RedirectingInformationParser
extends
RouteInformationParser
<
RouteInformation
>
{
RedirectingInformationParser
(
this
.
redirects
);
final
Map
<
String
,
RouteInformation
>
redirects
;
@override
Future
<
RouteInformation
>
parseRouteInformation
(
RouteInformation
information
)
{
return
SynchronousFuture
<
RouteInformation
>(
redirects
[
information
.
location
]
??
information
);
}
@override
RouteInformation
restoreRouteInformation
(
RouteInformation
configuration
)
{
return
configuration
;
}
}
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