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
39a46bed
Unverified
Commit
39a46bed
authored
Aug 13, 2020
by
Michael Goderbauer
Committed by
GitHub
Aug 13, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove decommission from RestorationBuckets (#63687)
parent
06c3de32
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
459 additions
and
308 deletions
+459
-308
restoration.dart
packages/flutter/lib/src/services/restoration.dart
+40
-82
restoration.dart
packages/flutter/lib/src/widgets/restoration.dart
+95
-74
scrollable.dart
packages/flutter/lib/src/widgets/scrollable.dart
+2
-2
restoration.dart
packages/flutter/test/services/restoration.dart
+7
-0
restoration_bucket_test.dart
packages/flutter/test/services/restoration_bucket_test.dart
+0
-53
restoration_test.dart
packages/flutter/test/services/restoration_test.dart
+64
-9
restorable_property_test.dart
packages/flutter/test/widgets/restorable_property_test.dart
+1
-1
restoration_mixin_test.dart
packages/flutter/test/widgets/restoration_mixin_test.dart
+1
-45
restoration_scope_test.dart
packages/flutter/test/widgets/restoration_scope_test.dart
+0
-39
restoration_scopes_moving_test.dart
.../flutter/test/widgets/restoration_scopes_moving_test.dart
+238
-0
root_restoration_scope_test.dart
...ges/flutter/test/widgets/root_restoration_scope_test.dart
+2
-2
restoration.dart
packages/flutter_test/lib/src/restoration.dart
+8
-0
restoration_test.dart
packages/flutter_test/test/restoration_test.dart
+1
-1
No files found.
packages/flutter/lib/src/services/restoration.dart
View file @
39a46bed
...
@@ -55,13 +55,10 @@ typedef _BucketVisitor = void Function(RestorationBucket bucket);
...
@@ -55,13 +55,10 @@ typedef _BucketVisitor = void Function(RestorationBucket bucket);
/// In addition to providing restoration data when the app is launched,
/// In addition to providing restoration data when the app is launched,
/// restoration data may also be provided to a running app to restore it to a
/// restoration data may also be provided to a running app to restore it to a
/// previous state (e.g. when the user hits the back/forward button in the web
/// previous state (e.g. when the user hits the back/forward button in the web
/// browser). When this happens, the current bucket hierarchy is decommissioned
/// browser). When this happens, the [RestorationManager] notifies its listeners
/// and replaced with the hierarchy deserialized from the newly provided
/// (added via [addListener]) that a new [rootBucket] is available. In response
/// restoration data. Buckets in the old hierarchy notify their listeners when
/// to the notification, listeners must stop using the old bucket and restore
/// they get decommissioned. In response to the notification, listeners must
/// their state from the information in the new [rootBucket].
/// stop using the old buckets. Owners of those buckets must dispose of them and
/// claim a new child as a replacement from a parent in the new bucket hierarchy
/// (that parent may be the updated [rootBucket]).
///
///
/// Same platforms restrict the size of the restoration data. Therefore, the
/// Same platforms restrict the size of the restoration data. Therefore, the
/// data stored in the buckets should be as small as possible while still
/// data stored in the buckets should be as small as possible while still
...
@@ -147,6 +144,17 @@ class RestorationManager extends ChangeNotifier {
...
@@ -147,6 +144,17 @@ class RestorationManager extends ChangeNotifier {
Completer
<
RestorationBucket
>?
_pendingRootBucket
;
Completer
<
RestorationBucket
>?
_pendingRootBucket
;
bool
_rootBucketIsValid
=
false
;
bool
_rootBucketIsValid
=
false
;
/// Returns true for the frame after [rootBucket] has been replaced with a
/// new non-null bucket.
///
/// When true, entities should forget their current state and restore
/// their state according to the information in the new [rootBucket].
///
/// The [RestorationManager] informs its listeners (added via [addListener])
/// when this flag changes from false to true.
bool
get
isReplacing
=>
_isReplacing
;
bool
_isReplacing
=
false
;
Future
<
void
>
_getRootBucketFromEngine
()
async
{
Future
<
void
>
_getRootBucketFromEngine
()
async
{
final
Map
<
dynamic
,
dynamic
>?
config
=
await
SystemChannels
.
restoration
.
invokeMethod
<
Map
<
dynamic
,
dynamic
>>(
'get'
);
final
Map
<
dynamic
,
dynamic
>?
config
=
await
SystemChannels
.
restoration
.
invokeMethod
<
Map
<
dynamic
,
dynamic
>>(
'get'
);
if
(
_pendingRootBucket
==
null
)
{
if
(
_pendingRootBucket
==
null
)
{
...
@@ -172,11 +180,9 @@ class RestorationManager extends ChangeNotifier {
...
@@ -172,11 +180,9 @@ class RestorationManager extends ChangeNotifier {
/// The `enabled` parameter indicates whether the engine wants to receive
/// The `enabled` parameter indicates whether the engine wants to receive
/// restoration data. When `enabled` is false, state restoration is turned
/// restoration data. When `enabled` is false, state restoration is turned
/// off and the [rootBucket] is set to null. When `enabled` is true, the
/// off and the [rootBucket] is set to null. When `enabled` is true, the
/// provided restoration `data` will be parsed into
the
[rootBucket]. If
/// provided restoration `data` will be parsed into
a new
[rootBucket]. If
/// `data` is null, an empty [rootBucket] will be instantiated.
/// `data` is null, an empty [rootBucket] will be instantiated.
///
///
/// When this method is called, the old [rootBucket] is decommissioned.
///
/// Subclasses in test frameworks may call this method at any time to inject
/// Subclasses in test frameworks may call this method at any time to inject
/// restoration data (obtained e.g. by overriding [sendToEngine]) into the
/// restoration data (obtained e.g. by overriding [sendToEngine]) into the
/// [RestorationManager]. When the method is called before the [rootBucket] is
/// [RestorationManager]. When the method is called before the [rootBucket] is
...
@@ -185,9 +191,16 @@ class RestorationManager extends ChangeNotifier {
...
@@ -185,9 +191,16 @@ class RestorationManager extends ChangeNotifier {
@protected
@protected
void
handleRestorationUpdateFromEngine
({
required
bool
enabled
,
required
Uint8List
?
data
})
{
void
handleRestorationUpdateFromEngine
({
required
bool
enabled
,
required
Uint8List
?
data
})
{
assert
(
enabled
!=
null
);
assert
(
enabled
!=
null
);
assert
(
enabled
||
data
==
null
);
final
RestorationBucket
?
oldRoot
=
_rootBucket
;
_isReplacing
=
_rootBucketIsValid
&&
enabled
;
if
(
_isReplacing
)
{
SchedulerBinding
.
instance
!.
addPostFrameCallback
((
Duration
_
)
{
_isReplacing
=
false
;
});
}
final
RestorationBucket
?
oldRoot
=
_rootBucket
;
_rootBucket
=
enabled
_rootBucket
=
enabled
?
RestorationBucket
.
root
(
manager:
this
,
rawData:
_decodeRestorationData
(
data
))
?
RestorationBucket
.
root
(
manager:
this
,
rawData:
_decodeRestorationData
(
data
))
:
null
;
:
null
;
...
@@ -198,11 +211,7 @@ class RestorationManager extends ChangeNotifier {
...
@@ -198,11 +211,7 @@ class RestorationManager extends ChangeNotifier {
if
(
_rootBucket
!=
oldRoot
)
{
if
(
_rootBucket
!=
oldRoot
)
{
notifyListeners
();
notifyListeners
();
}
oldRoot
?.
dispose
();
if
(
oldRoot
!=
null
)
{
oldRoot
..
decommission
()
..
dispose
();
}
}
}
}
...
@@ -404,27 +413,13 @@ class RestorationManager extends ChangeNotifier {
...
@@ -404,27 +413,13 @@ class RestorationManager extends ChangeNotifier {
/// stored in the bucket. If the bucket is empty, it may initialize itself to
/// stored in the bucket. If the bucket is empty, it may initialize itself to
/// default values.
/// default values.
///
///
/// During the lifetime of a bucket, it may notify its listeners that the bucket
/// has been [decommission]ed. This happens when new restoration data has been
/// provided to, for example, the [RestorationManager] to restore the
/// application to a different state (e.g. when the user hits the back/forward
/// button in the web browser). In response to the notification, owners must
/// dispose their current bucket and replace it with a new bucket claimed from a
/// new parent (which will have been initialized with the new restoration data).
/// For example, if the owner previously claimed its bucket from
/// [RestorationManager.rootBucket], it must claim its new bucket from there
/// again. The root bucket will have been replaced with the new root bucket just
/// before the bucket listeners are informed about the decommission. Once the
/// new bucket is obtained, owners should restore their internal state according
/// to the information in the new bucket.
///
/// When the data stored in a bucket is no longer needed to restore the
/// When the data stored in a bucket is no longer needed to restore the
/// application to its current state (e.g. because the owner of the bucket is no
/// application to its current state (e.g. because the owner of the bucket is no
/// longer shown on screen), the bucket must be [dispose]d. This will remove all
/// longer shown on screen), the bucket must be [dispose]d. This will remove all
/// information stored in the bucket from the app's restoration data and that
/// information stored in the bucket from the app's restoration data and that
/// information will not be available again when the application is restored to
/// information will not be available again when the application is restored to
/// this state in the future.
/// this state in the future.
class
RestorationBucket
extends
ChangeNotifier
{
class
RestorationBucket
{
/// Creates an empty [RestorationBucket] to be provided to [adoptChild] to add
/// Creates an empty [RestorationBucket] to be provided to [adoptChild] to add
/// it to the bucket hierarchy.
/// it to the bucket hierarchy.
///
///
...
@@ -529,6 +524,16 @@ class RestorationBucket extends ChangeNotifier {
...
@@ -529,6 +524,16 @@ class RestorationBucket extends ChangeNotifier {
RestorationManager
?
_manager
;
RestorationManager
?
_manager
;
RestorationBucket
?
_parent
;
RestorationBucket
?
_parent
;
/// Returns true when entities processing this bucket should restore their
/// state from the information in the bucket (e.g. via [read] and
/// [claimChild]) instead of copying their current state information into the
/// bucket (e.g. via [write] and [adoptChild].
///
/// This flag is true for the frame after the [RestorationManager] has been
/// instructed to restore the application from newly provided restoration
/// data.
bool
get
isReplacing
=>
_manager
?.
isReplacing
??
false
;
/// The restoration ID under which the bucket is currently stored in the
/// The restoration ID under which the bucket is currently stored in the
/// parent of this bucket (or wants to be stored if it is currently
/// parent of this bucket (or wants to be stored if it is currently
/// parent-less).
/// parent-less).
...
@@ -545,49 +550,6 @@ class RestorationBucket extends ChangeNotifier {
...
@@ -545,49 +550,6 @@ class RestorationBucket extends ChangeNotifier {
// Maps a restoration ID to a value that is stored in this bucket.
// Maps a restoration ID to a value that is stored in this bucket.
Map
<
dynamic
,
dynamic
>
get
_rawValues
=>
_rawData
.
putIfAbsent
(
_valuesMapKey
,
()
=>
<
dynamic
,
dynamic
>{})
as
Map
<
dynamic
,
dynamic
>;
Map
<
dynamic
,
dynamic
>
get
_rawValues
=>
_rawData
.
putIfAbsent
(
_valuesMapKey
,
()
=>
<
dynamic
,
dynamic
>{})
as
Map
<
dynamic
,
dynamic
>;
/// Called to signal that this bucket and all its descendants are no longer
/// part of the current restoration data and must not be used anymore.
///
/// Calling this method will drop this bucket from its parent and notify all
/// its listeners as well as all listeners of its descendants. Once a bucket
/// has notified its listeners, it must not be used anymore. During the next
/// frame following the notification, the bucket must be disposed and replaced
/// with a new bucket.
///
/// As an example, the [RestorationManager] calls this method on its root
/// bucket when it has been asked to restore a running application to a
/// different state. At that point, the data stored in the current bucket
/// hierarchy is invalid and will be replaced with a new hierarchy generated
/// from the restoration data describing the new state. To replace the current
/// bucket hierarchy, [decommission] is called on the root bucket to signal to
/// all owners of buckets in the hierarchy that their bucket has become
/// invalid. In response to the notification, bucket owners must [dispose]
/// their buckets and claim a new bucket from the newly created hierarchy. For
/// example, the owner of a bucket that was originally claimed from the
/// [RestorationManager.rootBucket] must dispose that bucket and claim a new
/// bucket from the new [RestorationManager.rootBucket]. Once the new bucket
/// is claimed, owners should restore their state according to the data stored
/// in the new bucket.
void
decommission
()
{
assert
(
_debugAssertNotDisposed
());
if
(
_parent
!=
null
)
{
_parent
!.
_dropChild
(
this
);
_parent
=
null
;
}
_performDecommission
();
}
bool
_decommissioned
=
false
;
void
_performDecommission
()
{
_decommissioned
=
true
;
_updateManager
(
null
);
notifyListeners
();
_visitChildren
((
RestorationBucket
bucket
)
{
bucket
.
_performDecommission
();
});
}
// Get and store values.
// Get and store values.
/// Returns the value of type `P` that is currently stored in the bucket under
/// Returns the value of type `P` that is currently stored in the bucket under
...
@@ -782,7 +744,6 @@ class RestorationBucket extends ChangeNotifier {
...
@@ -782,7 +744,6 @@ class RestorationBucket extends ChangeNotifier {
bool
_needsSerialization
=
false
;
bool
_needsSerialization
=
false
;
void
_markNeedsSerialization
()
{
void
_markNeedsSerialization
()
{
assert
(
_manager
!=
null
||
_decommissioned
);
if
(!
_needsSerialization
)
{
if
(!
_needsSerialization
)
{
_needsSerialization
=
true
;
_needsSerialization
=
true
;
_manager
?.
scheduleSerializationFor
(
this
);
_manager
?.
scheduleSerializationFor
(
this
);
...
@@ -903,8 +864,8 @@ class RestorationBucket extends ChangeNotifier {
...
@@ -903,8 +864,8 @@ class RestorationBucket extends ChangeNotifier {
// Bucket management
// Bucket management
/// Changes the restoration ID under which the bucket is
stored in its parent
/// Changes the restoration ID under which the bucket is
(or will be) stored
/// to `newRestorationId`.
///
in its parent
to `newRestorationId`.
///
///
/// No-op if the bucket is already stored under the provided id.
/// No-op if the bucket is already stored under the provided id.
///
///
...
@@ -915,13 +876,12 @@ class RestorationBucket extends ChangeNotifier {
...
@@ -915,13 +876,12 @@ class RestorationBucket extends ChangeNotifier {
void
rename
(
String
newRestorationId
)
{
void
rename
(
String
newRestorationId
)
{
assert
(
_debugAssertNotDisposed
());
assert
(
_debugAssertNotDisposed
());
assert
(
newRestorationId
!=
null
);
assert
(
newRestorationId
!=
null
);
assert
(
_parent
!=
null
);
if
(
newRestorationId
==
restorationId
)
{
if
(
newRestorationId
==
restorationId
)
{
return
;
return
;
}
}
_parent
!
.
_removeChildData
(
this
);
_parent
?
.
_removeChildData
(
this
);
_restorationId
=
newRestorationId
;
_restorationId
=
newRestorationId
;
_parent
!
.
_addChildData
(
this
);
_parent
?
.
_addChildData
(
this
);
}
}
/// Deletes the bucket and all the data stored in it from the bucket
/// Deletes the bucket and all the data stored in it from the bucket
...
@@ -936,7 +896,6 @@ class RestorationBucket extends ChangeNotifier {
...
@@ -936,7 +896,6 @@ class RestorationBucket extends ChangeNotifier {
/// as well.
/// as well.
///
///
/// This method must only be called by the object's owner.
/// This method must only be called by the object's owner.
@override
void
dispose
()
{
void
dispose
()
{
assert
(
_debugAssertNotDisposed
());
assert
(
_debugAssertNotDisposed
());
_visitChildren
(
_dropChild
,
concurrentModification:
true
);
_visitChildren
(
_dropChild
,
concurrentModification:
true
);
...
@@ -945,7 +904,6 @@ class RestorationBucket extends ChangeNotifier {
...
@@ -945,7 +904,6 @@ class RestorationBucket extends ChangeNotifier {
_parent
?.
_removeChildData
(
this
);
_parent
?.
_removeChildData
(
this
);
_parent
=
null
;
_parent
=
null
;
_updateManager
(
null
);
_updateManager
(
null
);
super
.
dispose
();
_debugDisposed
=
true
;
_debugDisposed
=
true
;
}
}
...
...
packages/flutter/lib/src/widgets/restoration.dart
View file @
39a46bed
...
@@ -104,7 +104,7 @@ class _RestorationScopeState extends State<RestorationScope> with RestorationMix
...
@@ -104,7 +104,7 @@ class _RestorationScopeState extends State<RestorationScope> with RestorationMix
String
get
restorationId
=>
widget
.
restorationId
;
String
get
restorationId
=>
widget
.
restorationId
;
@override
@override
void
restoreState
(
RestorationBucket
oldBucket
)
{
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
// Nothing to do.
// Nothing to do.
// The bucket gets injected into the widget tree in the build method.
// The bucket gets injected into the widget tree in the build method.
}
}
...
@@ -648,7 +648,7 @@ abstract class RestorableProperty<T> extends ChangeNotifier {
...
@@ -648,7 +648,7 @@ abstract class RestorableProperty<T> extends ChangeNotifier {
/// String get restorationId => widget.restorationId;
/// String get restorationId => widget.restorationId;
///
///
/// @override
/// @override
/// void restoreState(RestorationBucket oldBucket) {
/// void restoreState(RestorationBucket oldBucket
, bool initialRestore
) {
/// // All restorable properties must be registered with the mixin. After
/// // All restorable properties must be registered with the mixin. After
/// // registration, the counter either has its old value restored or is
/// // registration, the counter either has its old value restored or is
/// // initialized to its default value.
/// // initialized to its default value.
...
@@ -783,7 +783,7 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
...
@@ -783,7 +783,7 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
/// [bucket].
/// [bucket].
@mustCallSuper
@mustCallSuper
@protected
@protected
void
restoreState
(
RestorationBucket
oldBucket
);
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
);
/// Called when [bucket] switches between null and non-null values.
/// Called when [bucket] switches between null and non-null values.
///
///
...
@@ -804,8 +804,8 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
...
@@ -804,8 +804,8 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
@mustCallSuper
@mustCallSuper
@protected
@protected
void
didToggleBucket
(
RestorationBucket
oldBucket
)
{
void
didToggleBucket
(
RestorationBucket
oldBucket
)
{
// When
restore is pending, restoreState must be
called instead.
// When
a bucket is replaced, must `restoreState` is
called instead.
assert
(
!
restorePending
);
assert
(
_bucket
?.
isReplacing
!=
true
);
}
}
// Maps properties to their listeners.
// Maps properties to their listeners.
...
@@ -900,8 +900,22 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
...
@@ -900,8 +900,22 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
/// [restorationId] was caused by an updated widget.
/// [restorationId] was caused by an updated widget.
@protected
@protected
void
didUpdateRestorationId
()
{
void
didUpdateRestorationId
()
{
if
(
_bucket
?.
restorationId
!=
restorationId
&&
!
restorePending
)
{
// There's nothing to do if:
_updateBucketIfNecessary
();
// - We don't have a parent to claim a bucket from.
// - Our current bucket already uses the provided restoration ID.
// - There's a restore pending, which means that didUpdateDependencies
// will be called and we handle the rename there.
if
(
_currentParent
==
null
||
_bucket
?.
restorationId
==
restorationId
||
restorePending
)
{
return
;
}
final
RestorationBucket
oldBucket
=
_bucket
;
assert
(!
restorePending
);
final
bool
didReplaceBucket
=
_updateBucketIfNecessary
(
parent:
_currentParent
,
restorePending:
false
);
if
(
didReplaceBucket
)
{
assert
(
oldBucket
!=
_bucket
);
assert
(
_bucket
==
null
||
oldBucket
==
null
);
oldBucket
?.
dispose
();
}
}
}
}
...
@@ -923,32 +937,50 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
...
@@ -923,32 +937,50 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
/// While this is true, [bucket] will also still return the old bucket with
/// While this is true, [bucket] will also still return the old bucket with
/// the old restoration data. It will update to the new bucket with the new
/// the old restoration data. It will update to the new bucket with the new
/// data just before [restoreState] is invoked.
/// data just before [restoreState] is invoked.
bool
get
restorePending
=>
_restorePending
;
bool
get
restorePending
{
bool
_restorePending
=
true
;
if
(
_firstRestorePending
)
{
return
true
;
}
if
(
restorationId
==
null
)
{
return
false
;
}
final
RestorationBucket
potentialNewParent
=
RestorationScope
.
of
(
context
);
return
potentialNewParent
!=
_currentParent
&&
potentialNewParent
?.
isReplacing
==
true
;
}
List
<
RestorableProperty
<
Object
>>
_debugPropertiesWaitingForReregistration
;
List
<
RestorableProperty
<
Object
>>
_debugPropertiesWaitingForReregistration
;
bool
get
_debugDoingRestore
=>
_debugPropertiesWaitingForReregistration
!=
null
;
bool
get
_debugDoingRestore
=>
_debugPropertiesWaitingForReregistration
!=
null
;
bool
_firstRestorePending
=
true
;
RestorationBucket
_currentParent
;
@override
@override
void
didChangeDependencies
()
{
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
super
.
didChangeDependencies
();
RestorationBucket
oldBucket
;
if
(
_restorePending
)
{
final
RestorationBucket
oldBucket
=
_bucket
;
oldBucket
=
_bucket
;
final
bool
needsRestore
=
restorePending
;
// Throw away the old bucket so [_updateBucketIfNecessary] will claim a
_currentParent
=
RestorationScope
.
of
(
context
);
// new one with the new restoration data.
_bucket
=
null
;
final
bool
didReplaceBucket
=
_updateBucketIfNecessary
(
parent:
_currentParent
,
restorePending:
needsRestore
);
if
(
needsRestore
)
{
_doRestore
(
oldBucket
);
}
if
(
didReplaceBucket
)
{
assert
(
oldBucket
!=
_bucket
);
oldBucket
?.
dispose
();
}
}
}
_updateBucketIfNecessary
();
if
(
_restorePending
)
{
_restorePending
=
false
;
void
_doRestore
(
RestorationBucket
oldBucket
)
{
assert
(()
{
assert
(()
{
_debugPropertiesWaitingForReregistration
=
_properties
.
keys
.
toList
();
_debugPropertiesWaitingForReregistration
=
_properties
.
keys
.
toList
();
return
true
;
return
true
;
}());
}());
restoreState
(
oldBucket
);
restoreState
(
oldBucket
,
_firstRestorePending
);
_firstRestorePending
=
false
;
assert
(()
{
assert
(()
{
if
(
_debugPropertiesWaitingForReregistration
.
isNotEmpty
)
{
if
(
_debugPropertiesWaitingForReregistration
.
isNotEmpty
)
{
...
@@ -968,53 +1000,42 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
...
@@ -968,53 +1000,42 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
_debugPropertiesWaitingForReregistration
=
null
;
_debugPropertiesWaitingForReregistration
=
null
;
return
true
;
return
true
;
}());
}());
oldBucket
?.
dispose
();
}
}
}
void
_markNeedsRestore
()
{
// Returns true if `bucket` has been replaced with a new bucket. It's the
_restorePending
=
true
;
// responsibility of the caller to dispose the old bucket when this returns true.
// [didChangeDependencies] will be called next because our bucket can only
bool
_updateBucketIfNecessary
({
// become invalid if our parent bucket ([RestorationScope.of]) is replaced
@required
RestorationBucket
parent
,
// with a new one.
@required
bool
restorePending
,
}
})
{
if
(
restorationId
==
null
||
parent
==
null
)
{
void
_updateBucketIfNecessary
()
{
final
bool
didReplace
=
_setNewBucketIfNecessary
(
newBucket:
null
,
restorePending:
restorePending
);
if
(
restorationId
==
null
)
{
_setNewBucketIfNecessary
(
newBucket:
null
);
assert
(
_bucket
==
null
);
return
;
}
final
RestorationBucket
newParent
=
RestorationScope
.
of
(
context
);
if
(
newParent
==
null
)
{
_setNewBucketIfNecessary
(
newBucket:
null
);
assert
(
_bucket
==
null
);
assert
(
_bucket
==
null
);
return
;
return
didReplace
;
}
}
if
(
_bucket
==
null
)
{
assert
(
newParent
!=
null
);
assert
(
restorationId
!=
null
);
assert
(
restorationId
!=
null
);
final
RestorationBucket
newBucket
=
newParent
.
claimChild
(
restorationId
,
debugOwner:
this
)
assert
(
parent
!=
null
);
..
addListener
(
_markNeedsRestore
);
if
(
restorePending
||
_bucket
==
null
)
{
final
RestorationBucket
newBucket
=
parent
.
claimChild
(
restorationId
,
debugOwner:
this
);
assert
(
newBucket
!=
null
);
assert
(
newBucket
!=
null
);
_setNewBucketIfNecessary
(
newBucket:
newBucket
);
final
bool
didReplace
=
_setNewBucketIfNecessary
(
newBucket:
newBucket
,
restorePending:
restorePending
);
assert
(
_bucket
==
newBucket
);
assert
(
_bucket
==
newBucket
);
return
;
return
didReplace
;
}
}
// We have an existing bucket, make sure it has the right parent and id.
// We have an existing bucket, make sure it has the right parent and id.
assert
(
_bucket
!=
null
);
assert
(
_bucket
!=
null
);
assert
(
newParent
!=
null
);
assert
(!
restorePending
);
assert
(
restorationId
!=
null
);
_bucket
.
rename
(
restorationId
);
_bucket
.
rename
(
restorationId
);
newParent
.
adoptChild
(
_bucket
);
parent
.
adoptChild
(
_bucket
);
return
false
;
}
}
void
_setNewBucketIfNecessary
({
@required
RestorationBucket
newBucket
})
{
// Returns true if `bucket` has been replaced with a new bucket. It's the
// responsibility of the caller to dispose the old bucket when this returns true.
bool
_setNewBucketIfNecessary
({
@required
RestorationBucket
newBucket
,
@required
bool
restorePending
})
{
if
(
newBucket
==
_bucket
)
{
if
(
newBucket
==
_bucket
)
{
return
;
return
false
;
}
}
assert
(
newBucket
==
null
||
_bucket
==
null
);
final
RestorationBucket
oldBucket
=
_bucket
;
final
RestorationBucket
oldBucket
=
_bucket
;
_bucket
=
newBucket
;
_bucket
=
newBucket
;
if
(!
restorePending
)
{
if
(!
restorePending
)
{
...
@@ -1024,7 +1045,7 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
...
@@ -1024,7 +1045,7 @@ mixin RestorationMixin<S extends StatefulWidget> on State<S> {
}
}
didToggleBucket
(
oldBucket
);
didToggleBucket
(
oldBucket
);
}
}
oldBucket
?.
dispose
()
;
return
true
;
}
}
void
_updateProperty
(
RestorableProperty
<
Object
>
property
)
{
void
_updateProperty
(
RestorableProperty
<
Object
>
property
)
{
...
...
packages/flutter/lib/src/widgets/scrollable.dart
View file @
39a46bed
...
@@ -402,11 +402,11 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
...
@@ -402,11 +402,11 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
}
}
@override
@override
void
restoreState
(
RestorationBucket
oldBucket
)
{
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
_persistedScrollOffset
,
'offset'
);
registerForRestoration
(
_persistedScrollOffset
,
'offset'
);
assert
(
position
!=
null
);
assert
(
position
!=
null
);
if
(
_persistedScrollOffset
.
value
!=
null
)
{
if
(
_persistedScrollOffset
.
value
!=
null
)
{
position
.
restoreOffset
(
_persistedScrollOffset
.
value
,
initialRestore:
oldBucket
==
null
);
position
.
restoreOffset
(
_persistedScrollOffset
.
value
,
initialRestore:
initialRestore
);
}
}
}
}
...
...
packages/flutter/test/services/restoration.dart
View file @
39a46bed
...
@@ -50,9 +50,16 @@ class MockRestorationManager extends TestRestorationManager {
...
@@ -50,9 +50,16 @@ class MockRestorationManager extends TestRestorationManager {
Future
<
RestorationBucket
>
_rootBucket
;
Future
<
RestorationBucket
>
_rootBucket
;
set
rootBucket
(
Future
<
RestorationBucket
>
value
)
{
set
rootBucket
(
Future
<
RestorationBucket
>
value
)
{
_rootBucket
=
value
;
_rootBucket
=
value
;
_isRestoring
=
true
;
ServicesBinding
.
instance
.
addPostFrameCallback
((
Duration
_
)
{
_isRestoring
=
false
;
});
notifyListeners
();
notifyListeners
();
}
}
@override
bool
get
isReplacing
=>
_isRestoring
;
bool
_isRestoring
;
@override
@override
Future
<
void
>
sendToEngine
(
Uint8List
encodedData
)
{
Future
<
void
>
sendToEngine
(
Uint8List
encodedData
)
{
...
...
packages/flutter/test/services/restoration_bucket_test.dart
View file @
39a46bed
...
@@ -541,60 +541,12 @@ void main() {
...
@@ -541,60 +541,12 @@ void main() {
expect
(
rawData
[
childrenMapKey
][
'child1'
][
childrenMapKey
][
'child2'
][
valuesMapKey
][
'hello'
],
'world'
);
expect
(
rawData
[
childrenMapKey
][
'child1'
][
childrenMapKey
][
'child2'
][
valuesMapKey
][
'hello'
],
'world'
);
});
});
test
(
'decommission drops itself from parent and notifies all listeners'
,
()
{
final
MockRestorationManager
manager
=
MockRestorationManager
();
final
Map
<
String
,
dynamic
>
rawData
=
_createRawDataSet
();
final
RestorationBucket
root
=
RestorationBucket
.
root
(
manager:
manager
,
rawData:
rawData
);
final
RestorationBucket
child1
=
root
.
claimChild
(
'child1'
,
debugOwner:
'owner1'
);
final
RestorationBucket
child2
=
root
.
claimChild
(
'child2'
,
debugOwner:
'owner1'
);
final
RestorationBucket
childOfChild1
=
child1
.
claimChild
(
'child1.1'
,
debugOwner:
'owner1'
);
final
RestorationBucket
childOfChildOfChild1
=
childOfChild1
.
claimChild
(
'child1.1.1'
,
debugOwner:
'owner1'
);
expect
(
manager
.
updateScheduled
,
isTrue
);
manager
.
doSerialization
();
expect
(
manager
.
updateScheduled
,
isFalse
);
bool
rootDecommissioned
=
false
;
root
.
addListener
(()
{
rootDecommissioned
=
true
;
});
bool
child1Decommissioned
=
false
;
child1
.
addListener
(()
{
child1Decommissioned
=
true
;
});
bool
child2Decommissioned
=
false
;
child2
.
addListener
(()
{
child2Decommissioned
=
true
;
});
bool
childOfChild1Decommissioned
=
false
;
childOfChild1
.
addListener
(()
{
childOfChild1Decommissioned
=
true
;
});
bool
childOfChildOfChild1Decommissioned
=
false
;
childOfChildOfChild1
.
addListener
(()
{
childOfChildOfChild1Decommissioned
=
true
;
});
expect
(
rawData
[
childrenMapKey
].
containsKey
(
'child1'
),
isTrue
);
child1
.
decommission
();
expect
(
rootDecommissioned
,
isFalse
);
expect
(
child2Decommissioned
,
isFalse
);
expect
(
child1Decommissioned
,
isTrue
);
expect
(
childOfChild1Decommissioned
,
isTrue
);
expect
(
childOfChildOfChild1Decommissioned
,
isTrue
);
expect
(
rawData
[
childrenMapKey
].
containsKey
(
'child1'
),
isFalse
);
});
test
(
'throws when used after dispose'
,
()
{
test
(
'throws when used after dispose'
,
()
{
final
RestorationBucket
bucket
=
RestorationBucket
.
empty
(
restorationId:
'foo'
,
debugOwner:
null
);
final
RestorationBucket
bucket
=
RestorationBucket
.
empty
(
restorationId:
'foo'
,
debugOwner:
null
);
bucket
.
dispose
();
bucket
.
dispose
();
expect
(()
=>
bucket
.
debugOwner
,
throwsFlutterError
);
expect
(()
=>
bucket
.
debugOwner
,
throwsFlutterError
);
expect
(()
=>
bucket
.
restorationId
,
throwsFlutterError
);
expect
(()
=>
bucket
.
restorationId
,
throwsFlutterError
);
expect
(()
=>
bucket
.
decommission
(),
throwsFlutterError
);
expect
(()
=>
bucket
.
read
<
int
>(
'foo'
),
throwsFlutterError
);
expect
(()
=>
bucket
.
read
<
int
>(
'foo'
),
throwsFlutterError
);
expect
(()
=>
bucket
.
write
(
'foo'
,
10
),
throwsFlutterError
);
expect
(()
=>
bucket
.
write
(
'foo'
,
10
),
throwsFlutterError
);
expect
(()
=>
bucket
.
remove
<
int
>(
'foo'
),
throwsFlutterError
);
expect
(()
=>
bucket
.
remove
<
int
>(
'foo'
),
throwsFlutterError
);
...
@@ -605,11 +557,6 @@ void main() {
...
@@ -605,11 +557,6 @@ void main() {
expect
(()
=>
bucket
.
rename
(
'bar'
),
throwsFlutterError
);
expect
(()
=>
bucket
.
rename
(
'bar'
),
throwsFlutterError
);
expect
(()
=>
bucket
.
dispose
(),
throwsFlutterError
);
expect
(()
=>
bucket
.
dispose
(),
throwsFlutterError
);
});
});
test
(
'cannot serialize without manager'
,
()
{
final
RestorationBucket
bucket
=
RestorationBucket
.
empty
(
restorationId:
'foo'
,
debugOwner:
null
);
expect
(()
=>
bucket
.
write
(
'foo'
,
10
),
throwsAssertionError
);
});
}
}
Map
<
String
,
dynamic
>
_createRawDataSet
()
{
Map
<
String
,
dynamic
>
_createRawDataSet
()
{
...
...
packages/flutter/test/services/restoration_test.dart
View file @
39a46bed
...
@@ -126,26 +126,21 @@ void main() {
...
@@ -126,26 +126,21 @@ void main() {
final
RestorationBucket
child
=
rootBucket
.
claimChild
(
'child1'
,
debugOwner:
null
);
final
RestorationBucket
child
=
rootBucket
.
claimChild
(
'child1'
,
debugOwner:
null
);
expect
(
child
.
read
<
int
>(
'another value'
),
22
);
expect
(
child
.
read
<
int
>(
'another value'
),
22
);
bool
rootDecommissioned
=
false
;
bool
rootReplaced
=
false
;
bool
childDecommissioned
=
false
;
RestorationBucket
newRoot
;
RestorationBucket
newRoot
;
rootBucket
.
addListener
(()
{
manager
.
addListener
(()
{
root
Decommission
ed
=
true
;
root
Replac
ed
=
true
;
manager
.
rootBucket
.
then
((
RestorationBucket
bucket
)
{
manager
.
rootBucket
.
then
((
RestorationBucket
bucket
)
{
newRoot
=
bucket
;
newRoot
=
bucket
;
});
});
// The new bucket is available synchronously.
// The new bucket is available synchronously.
expect
(
newRoot
,
isNotNull
);
expect
(
newRoot
,
isNotNull
);
});
});
child
.
addListener
(()
{
childDecommissioned
=
true
;
});
// Send new Data.
// Send new Data.
await
_pushDataFromEngine
(
_createEncodedRestorationData2
());
await
_pushDataFromEngine
(
_createEncodedRestorationData2
());
expect
(
rootDecommissioned
,
isTrue
);
expect
(
rootReplaced
,
isTrue
);
expect
(
childDecommissioned
,
isTrue
);
expect
(
newRoot
,
isNot
(
same
(
rootBucket
)));
expect
(
newRoot
,
isNot
(
same
(
rootBucket
)));
child
.
dispose
();
child
.
dispose
();
...
@@ -234,6 +229,60 @@ void main() {
...
@@ -234,6 +229,60 @@ void main() {
manager
.
flushData
();
manager
.
flushData
();
expect
(
callsToEngine
,
hasLength
(
1
));
expect
(
callsToEngine
,
hasLength
(
1
));
});
});
testWidgets
(
'isReplacing'
,
(
WidgetTester
tester
)
async
{
final
Completer
<
Map
<
dynamic
,
dynamic
>>
result
=
Completer
<
Map
<
dynamic
,
dynamic
>>();
SystemChannels
.
restoration
.
setMockMethodCallHandler
((
MethodCall
call
)
{
return
result
.
future
;
});
final
TestRestorationManager
manager
=
TestRestorationManager
();
expect
(
manager
.
isReplacing
,
isFalse
);
RestorationBucket
rootBucket
;
manager
.
rootBucket
.
then
((
RestorationBucket
bucket
)
{
rootBucket
=
bucket
;
});
result
.
complete
(
_createEncodedRestorationData1
());
await
tester
.
idle
();
expect
(
rootBucket
,
isNotNull
);
expect
(
rootBucket
.
isReplacing
,
isFalse
);
expect
(
manager
.
isReplacing
,
isFalse
);
tester
.
binding
.
scheduleFrame
();
await
tester
.
pump
();
expect
(
manager
.
isReplacing
,
isFalse
);
expect
(
rootBucket
.
isReplacing
,
isFalse
);
manager
.
receiveDataFromEngine
(
enabled:
true
,
data:
null
);
RestorationBucket
rootBucket2
;
manager
.
rootBucket
.
then
((
RestorationBucket
bucket
)
{
rootBucket2
=
bucket
;
});
expect
(
rootBucket2
,
isNotNull
);
expect
(
rootBucket2
,
isNot
(
same
(
rootBucket
)));
expect
(
manager
.
isReplacing
,
isTrue
);
expect
(
rootBucket2
.
isReplacing
,
isTrue
);
await
tester
.
idle
();
expect
(
manager
.
isReplacing
,
isTrue
);
expect
(
rootBucket2
.
isReplacing
,
isTrue
);
tester
.
binding
.
scheduleFrame
();
await
tester
.
pump
();
expect
(
manager
.
isReplacing
,
isFalse
);
expect
(
rootBucket2
.
isReplacing
,
isFalse
);
manager
.
receiveDataFromEngine
(
enabled:
false
,
data:
null
);
RestorationBucket
rootBucket3
;
manager
.
rootBucket
.
then
((
RestorationBucket
bucket
)
{
rootBucket3
=
bucket
;
});
expect
(
rootBucket3
,
isNull
);
expect
(
manager
.
isReplacing
,
isFalse
);
await
tester
.
idle
();
expect
(
manager
.
isReplacing
,
isFalse
);
tester
.
binding
.
scheduleFrame
();
await
tester
.
pump
();
expect
(
manager
.
isReplacing
,
isFalse
);
});
});
});
test
(
'debugIsSerializableForRestoration'
,
()
{
test
(
'debugIsSerializableForRestoration'
,
()
{
...
@@ -305,3 +354,9 @@ Map<dynamic, dynamic> _packageRestorationData({bool enabled = true, Map<dynamic,
...
@@ -305,3 +354,9 @@ Map<dynamic, dynamic> _packageRestorationData({bool enabled = true, Map<dynamic,
'data'
:
encoded
==
null
?
null
:
encoded
.
buffer
.
asUint8List
(
encoded
.
offsetInBytes
,
encoded
.
lengthInBytes
)
'data'
:
encoded
==
null
?
null
:
encoded
.
buffer
.
asUint8List
(
encoded
.
offsetInBytes
,
encoded
.
lengthInBytes
)
};
};
}
}
class
TestRestorationManager
extends
RestorationManager
{
void
receiveDataFromEngine
({
@required
bool
enabled
,
@required
Uint8List
data
})
{
handleRestorationUpdateFromEngine
(
enabled:
enabled
,
data:
data
);
}
}
packages/flutter/test/widgets/restorable_property_test.dart
View file @
39a46bed
...
@@ -353,7 +353,7 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
...
@@ -353,7 +353,7 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
final
_TestRestorableValue
objectValue
=
_TestRestorableValue
();
final
_TestRestorableValue
objectValue
=
_TestRestorableValue
();
@override
@override
void
restoreState
(
RestorationBucket
oldBucket
)
{
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
numValue
,
'num'
);
registerForRestoration
(
numValue
,
'num'
);
registerForRestoration
(
doubleValue
,
'double'
);
registerForRestoration
(
doubleValue
,
'double'
);
registerForRestoration
(
intValue
,
'int'
);
registerForRestoration
(
intValue
,
'int'
);
...
...
packages/flutter/test/widgets/restoration_mixin_test.dart
View file @
39a46bed
...
@@ -356,50 +356,6 @@ void main() {
...
@@ -356,50 +356,6 @@ void main() {
expect
(
rawData
[
childrenMapKey
].
containsKey
(
'moving-child'
),
isTrue
);
expect
(
rawData
[
childrenMapKey
].
containsKey
(
'moving-child'
),
isTrue
);
});
});
testWidgets
(
'decommission claims new bucket with data'
,
(
WidgetTester
tester
)
async
{
final
MockRestorationManager
manager
=
MockRestorationManager
();
RestorationBucket
root
=
RestorationBucket
.
root
(
manager:
manager
,
rawData:
<
String
,
dynamic
>{});
await
tester
.
pumpWidget
(
UnmanagedRestorationScope
(
bucket:
root
,
child:
const
_TestRestorableWidget
(
restorationId:
'child1'
,
),
),
);
manager
.
doSerialization
();
final
_TestRestorableWidgetState
state
=
tester
.
state
(
find
.
byType
(
_TestRestorableWidget
));
expect
(
state
.
bucket
.
restorationId
,
'child1'
);
expect
(
state
.
property
.
value
,
10
);
// Initialized to default.
expect
(
state
.
bucket
.
read
<
int
>(
'foo'
),
10
);
final
RestorationBucket
bucket
=
state
.
bucket
;
state
.
property
.
log
.
clear
();
state
.
restoreStateLog
.
clear
();
// Replace root bucket.
root
..
decommission
()..
dispose
();
root
=
RestorationBucket
.
root
(
manager:
manager
,
rawData:
_createRawDataSet
());
await
tester
.
pumpWidget
(
UnmanagedRestorationScope
(
bucket:
root
,
child:
const
_TestRestorableWidget
(
restorationId:
'child1'
,
),
),
);
// Bucket has been replaced.
expect
(
state
.
bucket
,
isNot
(
same
(
bucket
)));
expect
(
state
.
bucket
.
restorationId
,
'child1'
);
expect
(
state
.
property
.
value
,
22
);
// Restored value.
expect
(
state
.
bucket
.
read
<
int
>(
'foo'
),
22
);
expect
(
state
.
restoreStateLog
.
single
,
bucket
);
expect
(
state
.
toogleBucketLog
,
isEmpty
);
expect
(
state
.
property
.
log
,
<
String
>[
'fromPrimitives'
,
'initWithValue'
]);
});
testWidgets
(
'restartAndRestore'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'restartAndRestore'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
const
RootRestorationScope
(
...
@@ -711,7 +667,7 @@ class _TestRestorableWidgetState extends State<_TestRestorableWidget> with Resto
...
@@ -711,7 +667,7 @@ class _TestRestorableWidgetState extends State<_TestRestorableWidget> with Resto
@override
@override
void
restoreState
(
RestorationBucket
oldBucket
)
{
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
restoreStateLog
.
add
(
oldBucket
);
restoreStateLog
.
add
(
oldBucket
);
registerForRestoration
(
property
,
'foo'
);
registerForRestoration
(
property
,
'foo'
);
if
(
_rerigisterAdditionalProperty
&&
additionalProperty
!=
null
)
{
if
(
_rerigisterAdditionalProperty
&&
additionalProperty
!=
null
)
{
...
...
packages/flutter/test/widgets/restoration_scope_test.dart
View file @
39a46bed
...
@@ -323,45 +323,6 @@ void main() {
...
@@ -323,45 +323,6 @@ void main() {
expect
(
rawData
[
childrenMapKey
][
'fixed'
],
isEmpty
);
expect
(
rawData
[
childrenMapKey
][
'fixed'
],
isEmpty
);
expect
(
rawData
[
childrenMapKey
].
containsKey
(
'moving-child'
),
isTrue
);
expect
(
rawData
[
childrenMapKey
].
containsKey
(
'moving-child'
),
isTrue
);
});
});
testWidgets
(
'decommission claims new bucket with data'
,
(
WidgetTester
tester
)
async
{
final
MockRestorationManager
manager
=
MockRestorationManager
();
RestorationBucket
root
=
RestorationBucket
.
root
(
manager:
manager
,
rawData:
<
String
,
dynamic
>{});
await
tester
.
pumpWidget
(
UnmanagedRestorationScope
(
bucket:
root
,
child:
const
RestorationScope
(
restorationId:
'child1'
,
child:
BucketSpy
(),
),
),
);
manager
.
doSerialization
();
final
BucketSpyState
state
=
tester
.
state
(
find
.
byType
(
BucketSpy
));
expect
(
state
.
bucket
.
restorationId
,
'child1'
);
expect
(
state
.
bucket
.
read
<
int
>(
'foo'
),
isNull
);
// Does not exist.
final
RestorationBucket
bucket
=
state
.
bucket
;
// Replace root bucket.
root
..
decommission
()..
dispose
();
root
=
RestorationBucket
.
root
(
manager:
manager
,
rawData:
_createRawDataSet
());
await
tester
.
pumpWidget
(
UnmanagedRestorationScope
(
bucket:
root
,
child:
const
RestorationScope
(
restorationId:
'child1'
,
child:
BucketSpy
(),
),
),
);
// Bucket has been replaced.
expect
(
state
.
bucket
,
isNot
(
same
(
bucket
)));
expect
(
state
.
bucket
.
restorationId
,
'child1'
);
expect
(
state
.
bucket
.
read
<
int
>(
'foo'
),
22
);
});
});
});
}
}
...
...
packages/flutter/test/widgets/restoration_scopes_moving_test.dart
0 → 100644
View file @
39a46bed
// 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
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'widget moves scopes during restore'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
RootRestorationScope
(
restorationId:
'root'
,
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
TestWidgetWithCounterChild
(),
),
));
expect
(
tester
.
state
<
TestWidgetWithCounterChildState
>(
find
.
byType
(
TestWidgetWithCounterChild
)).
restoreChild
,
true
);
expect
(
find
.
text
(
'Counter: 0'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'Counter: 0'
));
await
tester
.
pump
();
expect
(
find
.
text
(
'Counter: 1'
),
findsOneWidget
);
final
TestRestorationData
dataWithChild
=
await
tester
.
getRestorationData
();
tester
.
state
<
TestWidgetWithCounterChildState
>(
find
.
byType
(
TestWidgetWithCounterChild
)).
restoreChild
=
false
;
await
tester
.
pump
();
expect
(
tester
.
state
<
TestWidgetWithCounterChildState
>(
find
.
byType
(
TestWidgetWithCounterChild
)).
restoreChild
,
false
);
await
tester
.
tap
(
find
.
text
(
'Counter: 1'
));
await
tester
.
pump
();
expect
(
find
.
text
(
'Counter: 2'
),
findsOneWidget
);
final
TestRestorationData
dataWithoutChild
=
await
tester
.
getRestorationData
();
// Child moves from outside to inside scope.
await
tester
.
restoreFrom
(
dataWithChild
);
expect
(
find
.
text
(
'Counter: 1'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'Counter: 1'
));
await
tester
.
pump
();
expect
(
find
.
text
(
'Counter: 2'
),
findsOneWidget
);
// Child stays inside scope.
await
tester
.
restoreFrom
(
dataWithChild
);
expect
(
find
.
text
(
'Counter: 1'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'Counter: 1'
));
await
tester
.
tap
(
find
.
text
(
'Counter: 1'
));
await
tester
.
tap
(
find
.
text
(
'Counter: 1'
));
await
tester
.
tap
(
find
.
text
(
'Counter: 1'
));
await
tester
.
tap
(
find
.
text
(
'Counter: 1'
));
await
tester
.
pump
();
expect
(
find
.
text
(
'Counter: 6'
),
findsOneWidget
);
// Child moves from inside to outside scope.
await
tester
.
restoreFrom
(
dataWithoutChild
);
expect
(
find
.
text
(
'Counter: 6'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'Counter: 6'
));
await
tester
.
pump
();
expect
(
find
.
text
(
'Counter: 7'
),
findsOneWidget
);
// Child stays outside scope.
await
tester
.
restoreFrom
(
dataWithoutChild
);
expect
(
find
.
text
(
'Counter: 7'
),
findsOneWidget
);
expect
(
tester
.
state
<
TestWidgetWithCounterChildState
>(
find
.
byType
(
TestWidgetWithCounterChild
)).
toggleCount
,
0
);
});
testWidgets
(
'restoration is turned on later'
,
(
WidgetTester
tester
)
async
{
tester
.
binding
.
restorationManager
.
disableRestoration
();
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
restorationId:
'root-child'
,
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
TestWidget
(
restorationId:
'foo'
,
),
),
));
final
TestWidgetState
state
=
tester
.
state
<
TestWidgetState
>(
find
.
byType
(
TestWidget
));
expect
(
find
.
text
(
'hello'
),
findsOneWidget
);
expect
(
state
.
buckets
.
single
,
isNull
);
expect
(
state
.
flags
.
single
,
isTrue
);
expect
(
state
.
bucket
,
isNull
);
state
.
buckets
.
clear
();
state
.
flags
.
clear
();
await
tester
.
restoreFrom
(
TestRestorationData
.
empty
);
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
restorationId:
'root-child'
,
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
TestWidget
(
restorationId:
'foo'
,
),
),
));
expect
(
find
.
text
(
'hello'
),
findsOneWidget
);
expect
(
state
.
buckets
.
single
,
isNull
);
expect
(
state
.
flags
.
single
,
isFalse
);
expect
(
state
.
bucket
,
isNotNull
);
expect
(
state
.
toggleCount
,
0
);
});
}
class
TestWidgetWithCounterChild
extends
StatefulWidget
{
@override
State
<
TestWidgetWithCounterChild
>
createState
()
=>
TestWidgetWithCounterChildState
();
}
class
TestWidgetWithCounterChildState
extends
State
<
TestWidgetWithCounterChild
>
with
RestorationMixin
{
final
RestorableBool
childRestorationEnabled
=
RestorableBool
(
true
);
int
toggleCount
=
0
;
@override
void
didToggleBucket
(
RestorationBucket
oldBucket
)
{
super
.
didToggleBucket
(
oldBucket
);
toggleCount
++;
}
@override
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
childRestorationEnabled
,
'childRestorationEnabled'
);
}
bool
get
restoreChild
=>
childRestorationEnabled
.
value
;
set
restoreChild
(
bool
value
)
{
if
(
value
==
childRestorationEnabled
.
value
)
{
return
;
}
setState
(()
{
childRestorationEnabled
.
value
=
value
;
});
}
@override
void
dispose
()
{
childRestorationEnabled
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Counter
(
restorationId:
restoreChild
?
'counter'
:
null
,
);
}
@override
String
get
restorationId
=>
'foo'
;
}
class
Counter
extends
StatefulWidget
{
const
Counter
({
this
.
restorationId
});
final
String
restorationId
;
@override
State
<
Counter
>
createState
()
=>
CounterState
();
}
class
CounterState
extends
State
<
Counter
>
with
RestorationMixin
{
final
RestorableInt
count
=
RestorableInt
(
0
);
@override
String
get
restorationId
=>
widget
.
restorationId
;
@override
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
count
,
'counter'
);
}
@override
void
dispose
()
{
count
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
OutlinedButton
(
onPressed:
()
{
setState
(()
{
count
.
value
++;
});
},
child:
Text
(
'Counter:
${count.value}
'
,
),
);
}
}
class
TestWidget
extends
StatefulWidget
{
const
TestWidget
({
@required
this
.
restorationId
});
final
String
restorationId
;
@override
State
<
TestWidget
>
createState
()
=>
TestWidgetState
();
}
class
TestWidgetState
extends
State
<
TestWidget
>
with
RestorationMixin
{
List
<
RestorationBucket
>
buckets
=
<
RestorationBucket
>[];
List
<
bool
>
flags
=
<
bool
>[];
@override
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
buckets
.
add
(
oldBucket
);
flags
.
add
(
initialRestore
);
}
int
toggleCount
=
0
;
@override
void
didToggleBucket
(
RestorationBucket
oldBucket
)
{
super
.
didToggleBucket
(
oldBucket
);
toggleCount
++;
}
@override
String
get
restorationId
=>
widget
.
restorationId
;
@override
Widget
build
(
BuildContext
context
)
{
return
const
Text
(
'hello'
);
}
}
packages/flutter/test/widgets/root_restoration_scope_test.dart
View file @
39a46bed
...
@@ -296,8 +296,8 @@ void main() {
...
@@ -296,8 +296,8 @@ void main() {
};
};
final
RestorationBucket
secondRoot
=
RestorationBucket
.
root
(
manager:
binding
.
restorationManager
,
rawData:
secondRawData
);
final
RestorationBucket
secondRoot
=
RestorationBucket
.
root
(
manager:
binding
.
restorationManager
,
rawData:
secondRawData
);
binding
.
restorationManager
.
rootBucket
=
SynchronousFuture
<
RestorationBucket
>(
secondRoot
);
binding
.
restorationManager
.
rootBucket
=
SynchronousFuture
<
RestorationBucket
>(
secondRoot
);
firstRoot
..
decommission
()..
dispose
();
await
tester
.
pump
();
await
tester
.
pump
();
firstRoot
.
dispose
();
expect
(
state
.
bucket
,
isNot
(
same
(
firstBucket
)));
expect
(
state
.
bucket
,
isNot
(
same
(
firstBucket
)));
expect
(
state
.
bucket
.
read
<
int
>(
'foo'
),
22
);
expect
(
state
.
bucket
.
read
<
int
>(
'foo'
),
22
);
...
@@ -362,8 +362,8 @@ void main() {
...
@@ -362,8 +362,8 @@ void main() {
expect
(
state
.
bucket
,
isNotNull
);
expect
(
state
.
bucket
,
isNotNull
);
binding
.
restorationManager
.
rootBucket
=
SynchronousFuture
<
RestorationBucket
>(
null
);
binding
.
restorationManager
.
rootBucket
=
SynchronousFuture
<
RestorationBucket
>(
null
);
root
..
decommission
()..
dispose
();
await
tester
.
pump
();
await
tester
.
pump
();
root
.
dispose
();
expect
(
binding
.
restorationManager
.
rootBucketAccessed
,
2
);
expect
(
binding
.
restorationManager
.
rootBucketAccessed
,
2
);
expect
(
find
.
text
(
'Hello'
),
findsOneWidget
);
expect
(
find
.
text
(
'Hello'
),
findsOneWidget
);
...
...
packages/flutter_test/lib/src/restoration.dart
View file @
39a46bed
...
@@ -55,6 +55,14 @@ class TestRestorationManager extends RestorationManager {
...
@@ -55,6 +55,14 @@ class TestRestorationManager extends RestorationManager {
handleRestorationUpdateFromEngine
(
enabled:
true
,
data:
data
.
binary
);
handleRestorationUpdateFromEngine
(
enabled:
true
,
data:
data
.
binary
);
}
}
/// Disabled state restoration.
///
/// To turn restoration back on call [restoreFrom].
void
disableRestoration
()
{
_restorationData
=
null
;
handleRestorationUpdateFromEngine
(
enabled:
false
);
}
@override
@override
Future
<
void
>
sendToEngine
(
Uint8List
encodedData
)
async
{
Future
<
void
>
sendToEngine
(
Uint8List
encodedData
)
async
{
_restorationData
=
TestRestorationData
.
_
(
encodedData
);
_restorationData
=
TestRestorationData
.
_
(
encodedData
);
...
...
packages/flutter_test/test/restoration_test.dart
View file @
39a46bed
...
@@ -88,7 +88,7 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
...
@@ -88,7 +88,7 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
double
doubleValue
=
1.0
;
// Not restorable.
double
doubleValue
=
1.0
;
// Not restorable.
@override
@override
void
restoreState
(
RestorationBucket
oldBucket
)
{
void
restoreState
(
RestorationBucket
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
stringValue
,
'string'
);
registerForRestoration
(
stringValue
,
'string'
);
registerForRestoration
(
intValue
,
'int'
);
registerForRestoration
(
intValue
,
'int'
);
}
}
...
...
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