-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Fix TabBarView desynchronized after animation interruption #132748
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
Conversation
3591bff to
022037a
Compare
Piinks
left a comment
There was a problem hiding this 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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
_endfunction is called the animation is disposed. - It seems the issue surfaced because of Futures resolution order :
_controller.whenCompleteis executed before the new activity start but theFutureit returned is executed after the start of the new activity. I tried hard to write a more narrowed test (one not relying onTabBarController) 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.
b204be2 to
b7a7a21
Compare
Piinks
left a comment
There was a problem hiding this 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. :)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
b7a7a21 to
0abd171
Compare
Piinks
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
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. |
|
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. |
0abd171 to
73d84d2
Compare
73d84d2 to
ff5ca34
Compare
|
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. |
|
Checking on Google testing |
|
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. |
|
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. |

Description
This PR fixes a
DrivenScrollActivityissue which surfaced in TabBar/TabBarView desynchronization (see #132293).A comment in
DrivenScrollActivityconstructor states that_endwill not be called if the animation controller is disposed.While investigating this tab bar view issue, I noticed that the
_endmethod of a firstDrivenScrollActivitywas called after this activity was disposed (and in the tab bar view case, this leads togoBallisticandgoIdlebeing called and inteferring with a newDrivenScrollActivitythat 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
TabBarViewcallsPageController.animateTo(), but after hours of investigation It seems thatDrivenScrollActivity._endbeing 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.