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
Expand all
Hide 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 {
/// 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.
/// Providing a restoration ID also enables the [Navigator]
or [Router] 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:
///
...
...
@@ -1518,6 +1518,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
if
(
_usesRouter
)
{
assert
(
_effectiveRouteInformationProvider
!=
null
);
routing
=
Router
<
Object
>(
restorationScopeId:
'router'
,
routeInformationProvider:
_effectiveRouteInformationProvider
,
routeInformationParser:
widget
.
routeInformationParser
,
routerDelegate:
widget
.
routerDelegate
!,
...
...
packages/flutter/lib/src/widgets/router.dart
View file @
b1b3c1a3
This diff is collapsed.
Click to expand it.
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() {
final
BuildContext
textContext
=
key
.
currentContext
!;
// This should not throw error.
Router
<
dynamic
>?
router
=
Router
.
maybeOf
(
textContext
);
final
Router
<
dynamic
>?
router
=
Router
.
maybeOf
(
textContext
);
expect
(
router
,
isNull
);
bool
hasFlutterError
=
false
;
try
{
router
=
Router
.
of
(
textContext
);
}
on
FlutterError
catch
(
e
)
{
expect
(
e
.
message
.
startsWith
(
'Router'
),
isTrue
);
hasFlutterError
=
true
;
}
expect
(
hasFlutterError
,
isTrue
);
expect
(
()
=>
Router
.
of
(
textContext
),
throwsA
(
isFlutterError
.
having
((
FlutterError
e
)
=>
e
.
message
,
'message'
,
startsWith
(
'Router'
)))
);
});
testWidgets
(
'Simple router can handle pop route'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -137,46 +133,48 @@ void main() {
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
();
provider
.
value
=
const
RouteInformation
(
location:
'initial'
,
);
try
{
Router
<
RouteInformation
>(
routeInformationProvider:
provider
,
routerDelegate:
SimpleRouterDelegate
(
builder:
(
BuildContext
context
,
RouteInformation
?
information
)
{
return
Text
(
information
!.
location
!);
},
),
);
}
on
AssertionError
catch
(
e
)
{
expect
(
e
.
message
,
'Both routeInformationProvider and routeInformationParser must be provided if this router '
'parses route information. Otherwise, they should both be null.'
,
);
}
expect
(
()
{
Router
<
RouteInformation
>(
routeInformationProvider:
provider
,
routerDelegate:
SimpleRouterDelegate
(
builder:
(
BuildContext
context
,
RouteInformation
?
information
)
{
return
Text
(
information
!.
location
!);
},
),
);
},
throwsA
(
isAssertionError
.
having
(
(
AssertionError
e
)
=>
e
.
message
,
'message'
,
'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.'
,
)),
);
});
testWidgets
(
'Router throw when passes only routeInformationParser'
,
(
WidgetTester
tester
)
async
{
try
{
Router
<
RouteInformation
>(
routeInformationParser:
SimpleRouteInformationParser
(),
routerDelegate:
SimpleRouterDelegate
(
builder:
(
BuildContext
context
,
RouteInformation
?
information
)
{
return
Text
(
information
!.
location
!);
},
),
);
}
on
AssertionError
catch
(
e
)
{
expect
(
e
.
message
,
'Both routeInformationProvider and routeInformationParser must be provided if this router '
'parses route information. Otherwise, they should both be null.'
,
);
}
testWidgets
(
'Router throw when passing restorationId without routeInformationParser'
,
(
WidgetTester
tester
)
async
{
expect
(
()
{
Router
<
RouteInformation
>(
restorationScopeId:
'foo'
,
routerDelegate:
SimpleRouterDelegate
(
builder:
(
BuildContext
context
,
RouteInformation
?
information
)
{
return
Text
(
information
!.
location
!);
},
),
);
},
throwsA
(
isAssertionError
.
having
(
(
AssertionError
e
)
=>
e
.
message
,
'message'
,
'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.'
,
)),
);
});
testWidgets
(
'PopNavigatorRouterDelegateMixin works'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -1091,6 +1089,35 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
await
tester
.
pump
();
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
)
{
...
...
@@ -1275,3 +1302,20 @@ class SimpleAsyncRouterDelegate extends RouterDelegate<RouteInformation> with Ch
@override
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