Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TabBarView desynchronized after animation interruption #132748

Merged
merged 1 commit into from
Sep 12, 2023

Conversation

bleroux
Copy link
Contributor

@bleroux bleroux commented Aug 17, 2023

Description

This PR fixes a DrivenScrollActivity issue which surfaced in TabBar/TabBarView desynchronization (see #132293).

A comment in DrivenScrollActivity constructor states that _end will not be called if the animation controller is disposed.
While investigating this tab bar view issue, I noticed that the _end method of a first DrivenScrollActivity was called after this activity was disposed (and in the tab bar view case, this leads to goBallistic and goIdle being called and inteferring with a new DrivenScrollActivity that just started).

Filing this PR as a WIP to gather feedback because It seems strange that this issue never surfaced before so maybe there is something wrong on how TabBarView calls PageController.animateTo(), but after hours of investigation It seems that DrivenScrollActivity._end being called when it is not expected is a good candidate (and the small change in this PR fixes the TabBarView issue). It is also possible that this issue requires very specific conditions to surface.

In the TabBarView case this issue surfaced after #122021 that made it possible to interrupt a pending tab bar view animation and start a new one (before that PR, a tab bar view animation had to end before a new one is started).

Related Issue

Fixes #132293.

Tests

Adds 1 tests.
I will add a second one in scroll_activity_test.dart if this fix is valid.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. labels Aug 17, 2023
@Piinks Piinks self-requested a review August 17, 2023 16:15
@bleroux bleroux force-pushed the tab_bar_view_desynchronized branch from 3591bff to 022037a Compare August 17, 2023 20:56
Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

I think this approach makes sense, just a question below for clarity. Thanks for your patience while I did a bit of digging on this. It reminded me of #14452

@@ -648,7 +648,9 @@ class DrivenScrollActivity extends ScrollActivity {
}

void _end() {
delegate.goBallistic(velocity);
if (!_completer.isCompleted) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should there be a check to see if the controller has been disposed like the comment said? Or was that comment incorrect?

Copy link
Contributor Author

@bleroux bleroux Aug 22, 2023

Choose a reason for hiding this comment

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

From my understanding, the comment I removed states that _end should not be called at all but I agree that it could be read as _end will have no effect if the _controller is disposed.

Looking at the Git history this code had very few changes since its introduction (#9575).
But looking more closely it looks like at this time, DrivenScrollActivity._end was implemented this way:

  void _end() {
    delegate?.goBallistic(0.0);
  }

And the DrivenScrollActivity.dispose method was the same as now:

  @override
  void dispose() {
    _completer.complete();
    _controller.dispose();
    super.dispose();
  }

But the ActivityScroll.dispose method was:

  void dispose() {
    _delegate = null;
  }

Which means _end had no effect once dispose was called.

So it looks like a bug was introduced during the null safety migration, see #64672.
It changed ActivityScroll.dispose to:

  void dispose() { }

So to answer your question:

Should there be a check to see if the controller has been disposed like the comment said? Or was that comment incorrect?

Yes there should be a check, but AFAIK, there is no standard way to check if the animation controller was disposed so I chose to rely on the completer state.

I plan to update the PR to replace the check on completer with a check on an ‘_isDisposed’ instance variable, that will make the code more self explanatory. I will also add a narrowed test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I investigated this further:

  • The comment (.whenComplete(_end); // won't trigger if we dispose _controller first) is right, it is expected that the whenComplete parameter is not called when a running animation controller is disposed.
  • But in our case, the animation controller completes just before being disposed (this has to be on the exact same frame). So at the time the _end function is called the animation is disposed.
  • It seems the issue surfaced because of Futures resolution order : _controller.whenComplete is executed before the new activity start but the Future it returned is executed after the start of the new activity. I tried hard to write a more narrowed test (one not relying on TabBarController) but I was not able to figure out one exposing the same Future ordering.

@Piinks I updated the PR with an _isDisposed flag and some comments.

@bleroux bleroux force-pushed the tab_bar_view_desynchronized branch 5 times, most recently from b204be2 to b7a7a21 Compare August 25, 2023 11:52
@bleroux bleroux marked this pull request as ready for review August 25, 2023 14:00
@bleroux bleroux requested a review from Piinks August 25, 2023 14:01
Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

Wow! Excellent digging into the history behind this. That you for being so thorough. I think this is nearly ready, just a small tweak below. :)

@@ -628,11 +628,12 @@ class DrivenScrollActivity extends ScrollActivity {
)
..addListener(_tick)
..animateTo(to, duration: duration, curve: curve)
.whenComplete(_end); // won't trigger if we dispose _controller first
.whenComplete(_end); // won't trigger if we dispose _controller before it completes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like we have the same condition in BallisticScrollActivity.

I think having the _isDisposed flag makes sense, it's not dissimilar from the mounted check that is available elsewhere.

Can you move the isDisposed flag to the super class - ScrollActivity - so we can use it the same way in _end for BallisticScrollActivity and DrivenScrollActivity?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great!
I pushed an update where _isDisposed is moved to ScrollActivity.

@bleroux bleroux force-pushed the tab_bar_view_desynchronized branch from b7a7a21 to 0abd171 Compare August 28, 2023 06:59
@bleroux bleroux requested a review from Piinks August 28, 2023 13:52
Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

Flutter_LGTM

@Piinks Piinks added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 11, 2023
@auto-submit
Copy link
Contributor

auto-submit bot commented Sep 11, 2023

auto label is removed for flutter/flutter/132748, due to - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Sep 11, 2023
@Piinks
Copy link
Contributor

Piinks commented Sep 11, 2023

Hmm I think a rebase would fix the issue here, but it is not enabled for me to be able to update it from here.

@bleroux bleroux added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 12, 2023
@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Sep 12, 2023
@auto-submit
Copy link
Contributor

auto-submit bot commented Sep 12, 2023

auto label is removed for flutter/flutter/132748, due to - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@Piinks
Copy link
Contributor

Piinks commented Sep 12, 2023

Checking on Google testing

@Piinks
Copy link
Contributor

Piinks commented Sep 12, 2023

The failure here is an instance of https://github.com/flutter/flutter/wiki/Understanding-Google-Testing#my-cl-reached-max-number-of-retry-why-and-how-do-i-resolve-this

I am going to set the check to green manually.

@Piinks Piinks added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 12, 2023
@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Sep 12, 2023
@auto-submit
Copy link
Contributor

auto-submit bot commented Sep 12, 2023

auto label is removed for flutter/flutter/132748, due to - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@Piinks Piinks merged commit 083ac65 into flutter:master Sep 12, 2023
66 of 67 checks passed
@bleroux bleroux deleted the tab_bar_view_desynchronized branch September 15, 2023 13:44
Mairramer pushed a commit to Mairramer/flutter that referenced this pull request Oct 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Working with tabs - TabBar/TabBarView desync
2 participants