Skip to content

Conversation

goderbauer
Copy link
Member

Description

Enabled the Scrollable widgets (ListView, GridView, PageView, etc) to restore their scroll offset with the state restoration framework.

The change is slightly awkward because Scrollables already had a restoration mechanism with PageStorage. The plan is to eventually replace that mechanism with the new state restoration approach once state restoration supports keeping state information around even when the owning widget is getting disposed.

Related Issues

#62916

Tests

I added the following tests:

  • Added for all migrated Scrollables and the new RestorationManager.flushData method.

Checklist

Before you create this PR, confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Did any tests fail when you ran them? Please read Handling breaking changes.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Aug 6, 2020
@goderbauer goderbauer requested a review from Hixie August 6, 2020 21:10
@goderbauer goderbauer force-pushed the restorableScrollables branch from b96f1d2 to 84e6337 Compare August 7, 2020 20:18
@goderbauer goderbauer force-pushed the restorableScrollables branch from 84e6337 to a3d72db Compare August 7, 2020 20:20
void _doSerialization() {
Future<void> _doSerialization() {
if (!_serializationScheduled) {
return Future<void>.value(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just make the function async and return;.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though... why do you not return the same future as the other call to doSerialization?
Or throw? Seems weird to do nothing when called redundantly, and return a future that completes before the earlier "real" call...


for (final RestorationBucket bucket in _bucketsNeedingSerialization) {
bucket.finalize();
}
_bucketsNeedingSerialization.clear();
sendToEngine(_encodeRestorationData(_rootBucket!._rawData));
final Future<void> result = sendToEngine(_encodeRestorationData(_rootBucket!._rawData));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then you can just await here


for (final RestorationBucket bucket in _bucketsNeedingSerialization) {
bucket.finalize();
}
_bucketsNeedingSerialization.clear();
sendToEngine(_encodeRestorationData(_rootBucket!._rawData));
final Future<void> result = sendToEngine(_encodeRestorationData(_rootBucket!._rawData));

assert(() {
_debugDoingUpdate = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

making this async is... exciting

/// Restoration data is automatically flushed to the engine at the end of a
/// frame. Since a change in restoration data is usually accompanied by
/// scheduling a frame (e.g. because the restoration data is modified inside a
/// [State.setState] call) it is uncommon to call this method directly.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

took me a while to understand these two sentences. How about:

A change in restoration data is usually accompanied by scheduling a frame (because the restoration data is modified inside a [State.setState] call, because it is usually something that affects the interface). Restoration data is automatically flushed to the engine at the end of a frame. As a result, it is uncommon to need to call this method directly.

/// frame. Since a change in restoration data is usually accompanied by
/// scheduling a frame (e.g. because the restoration data is modified inside a
/// [State.setState] call) it is uncommon to call this method directly.
/// However if restoration data is changed without triggering a frame this
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frame, this

/// scheduling a frame (e.g. because the restoration data is modified inside a
/// [State.setState] call) it is uncommon to call this method directly.
/// However if restoration data is changed without triggering a frame this
/// method must be called to ensure that the updated restoration data is send
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sent

/// [State.setState] call) it is uncommon to call this method directly.
/// However if restoration data is changed without triggering a frame this
/// method must be called to ensure that the updated restoration data is send
/// to the engine in a timely manner. An example for such a use case is the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for -> of

/// However if restoration data is changed without triggering a frame this
/// method must be called to ensure that the updated restoration data is send
/// to the engine in a timely manner. An example for such a use case is the
/// [Scrollable], where the final to be persisted scroll offset after a scroll
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"final to be persisted scroll offset" is dubious, not sure what you mean

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded.

/// Calling this method is a no-op if a frame is already scheduled. In that
/// case, the restoration data will be flushed to the engine at the end of
/// that frame. If this method is called and no frame is scheduled, the
/// current restoration data is directly send to the engine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sent

/// that frame. If this method is called and no frame is scheduled, the
/// current restoration data is directly send to the engine.
Future<void> flushData() async {
assert(!_debugDoingUpdate);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how can we guarantee this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just ensures that you don't call flushData from a RestorationBucket's finalize method (which is called during serialization).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah now that it's not async that works


@override
void didUpdateValue(double oldValue) {
notifyListeners();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even if it didn't change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RestorableValue only calls didUpdateValue if old and new value are different.

@Hixie
Copy link
Contributor

Hixie commented Aug 8, 2020

LGTM modulo the async logic in the restoration code

@goderbauer
Copy link
Member Author

I removed the async logic in the restoration code. It's not necessary and was just leftover from a different approach.

@goderbauer goderbauer mentioned this pull request Aug 10, 2020
13 tasks
@goderbauer
Copy link
Member Author

I'll intend to land this when the tree goes green unless I hear otherwise.

@fluttergithubbot
Copy link
Contributor

This pull request is not suitable for automatic merging in its current state.

  • Please get at least one approved review before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

@fluttergithubbot fluttergithubbot merged commit 25de941 into flutter:master Aug 11, 2020
mingwandroid pushed a commit to mingwandroid/flutter that referenced this pull request Sep 6, 2020
@goderbauer goderbauer deleted the restorableScrollables branch November 11, 2020 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants