-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Fix janks and memory leaks in CupertinoPageTransition and CupertinoFullscreenDialogTransition [prod-leak-fix]
#146999
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
Changes from all commits
939d8db
a2758a1
72a5113
8f3c106
67f3775
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -644,7 +644,10 @@ void main() { | |||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| testWidgets('Fullscreen route animates correct transform values over time', (WidgetTester tester) async { | ||||||||||||||||||||||||||||||||||||
| testWidgets('Fullscreen route animates correct transform values over time', | ||||||||||||||||||||||||||||||||||||
| // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] | ||||||||||||||||||||||||||||||||||||
| experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']), | ||||||||||||||||||||||||||||||||||||
| (WidgetTester tester) async { | ||||||||||||||||||||||||||||||||||||
| await tester.pumpWidget( | ||||||||||||||||||||||||||||||||||||
| CupertinoApp( | ||||||||||||||||||||||||||||||||||||
| home: Builder( | ||||||||||||||||||||||||||||||||||||
|
|
@@ -712,11 +715,27 @@ void main() { | |||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 40)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(7.4, epsilon: 0.1)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 50)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(3, epsilon: 0.1)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 50)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(0, epsilon: 0.1)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Give time to the animation to finish and update its status to | ||||||||||||||||||||||||||||||||||||
| // AnimationState.completed, so the reverse curved can be used in the next | ||||||||||||||||||||||||||||||||||||
| // step. | ||||||||||||||||||||||||||||||||||||
| await tester.pumpAndSettle(const Duration(milliseconds: 1)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Exit animation | ||||||||||||||||||||||||||||||||||||
| await tester.tap(find.text('Close')); | ||||||||||||||||||||||||||||||||||||
| await tester.pump(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 50)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(156.3, epsilon: 0.1)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 50)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(308.1, epsilon: 0.1)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 40)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(411.03, epsilon: 0.1)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
@@ -818,25 +837,173 @@ void main() { | |||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 40)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-263.0, epsilon: 1.0)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 50)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-265.0, epsilon: 1.0)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 50)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-266.0, epsilon: 1.0)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Give time to the animation to finish and update its status to | ||||||||||||||||||||||||||||||||||||
| // AnimationState.completed, so the reverse curved can be used in the next | ||||||||||||||||||||||||||||||||||||
| // step. | ||||||||||||||||||||||||||||||||||||
| await tester.pumpAndSettle(const Duration(milliseconds: 1)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Exit animation | ||||||||||||||||||||||||||||||||||||
| await tester.tap(find.text('Close')); | ||||||||||||||||||||||||||||||||||||
| await tester.pump(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| await tester.pump(const Duration(milliseconds: 50)); | ||||||||||||||||||||||||||||||||||||
| expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-197.0, epsilon: 1.0)); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
| void _updateCurveDirection(AnimationStatus status) { | |
| _curveDirection = switch (status) { | |
| AnimationStatus.dismissed || AnimationStatus.completed => null, | |
| AnimationStatus.forward || AnimationStatus.reverse => _curveDirection ?? status, | |
| }; | |
| } |
flutter/packages/flutter/lib/src/animation/animations.dart
Lines 434 to 436 in 89a4ffa
| bool get _useForwardCurve { | |
| return reverseCurve == null || (_curveDirection ?? parent.status) != AnimationStatus.reverse; | |
| } |
flutter/packages/flutter/lib/src/animation/animations.dart
Lines 450 to 457 in 89a4ffa
| double get value { | |
| final Curve? activeCurve = _useForwardCurve ? curve : reverseCurve; | |
| final double t = parent.value; | |
| if (activeCurve == null) { | |
| return t; | |
| } | |
| if (t == 0.0 || t == 1.0) { |
Now that the CurvedAnimation is stored in the state and reused, it keeps using the forward curve and doesn't "jump" to the reversedCurve, which I believe fixes a jank/jump that currently exists on the master channel.
For the test, I decided the modify it so the push animation finishes and the pop uses the reversedCurved by changing the durations from 40ms to 50ms. But I can revert those duration changes and make the pop use the forward curve instead.
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.
Rather than changing an existing test, can you add a new one? This feel like a potential regression.
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.
+1 on leaving this test alone as much as possible. It does look like logically the animation wouldn't finish by the time the original test tries to go back, so the jump makes sense. I'd rather the test was left as it was to check for regression, even with the jump, and probably renamed. A new test that correctly waits for the animation to finish, then goes back would be preferable.
Does the original test still fail, and how?
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.
Those were the minimum changes I found to "let this test alone as much as possible".
What was happening in this test:
- Push using the forward animation (500ms)
- The push is interrupted with a pop at 400ms
- The pop uses the reversed animation (but should not because it leads to a discontinuity).
After my changes without any modification on the test:
- Push using the forward animation (500ms)
- The push is interrupted with a pop at 400ms
- The pop uses the forward animation for the reverse animation (there is no discontinuity)
- The test fails at the line
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-83.0, epsilon: 1.0));Expected: -83
Actual value: -261
(Comment for reference: https://github.com/flutter/flutter/pull/146999/files#r1575569345)
To make this test pass, I have 2 options:
a. I let the push being interrupted and I need to change the expected values to match the use of the forward curve instead of the reverse curved.
b. I let the push animation complete with more pumps so the reverse animation for the pop uses the reversed curve and I don't need to change the values in the expects.
I have implemented both options. The one linked to the comment is a.. And a bit below, there is the group('Interrupted push', () { ... }); that implements b.
Is that what you are expecting?
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.
@MitchellGoodwin , does this help?
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.
Thank you for the explanation. That all makes sense and the two tests LGTM. Thank you for adjusting the old on and adding a new one.
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.
Is this related to the memory leak? If not, maybe this should be a separate change.
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.
Well now this PR is not about memory leaks anymore since #147133 got merged in between. Maybe I should rename this PR. Would "Fix jank in cupertino page transition" sounds nice to you ?
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.
That sounds like a great idea. Can you update the PR description to what issue this is fixing now as well? Thank you!
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 updated the title and the description of the PR. Tell me if something is unclear