Skip to content

NestedScrollView's outer scrollable jumping with BouncingScrollPhysics due to double precision errors #138319

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

Merged
merged 10 commits into from
Jan 10, 2024

Conversation

Michal-MK
Copy link
Contributor

@Michal-MK Michal-MK commented Nov 12, 2023

This PR fixes scrolling issues with NestedScrollView using the BouncingScrollPhysics. In one of the steps of the calculation, we can reach a state where the position of the inner scrollable is set to a double value that falls within precisionErrorTolerance of 0 but we were using == with 0 rather than checking for a precision. My posts in the linked issue show the current behavior, and how I reached to conclusion (the code in this PR). This PR only addresses the "jumping" of the outer scrollable.

Fixes #136199

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • All existing and new tests are passing.

I have not finished a test for this since I have never done so and therefore have 0 experience writing tests in Flutter, so any help there would be appreciated. I am also not sure how to test double precision errors in general. I did run all the nested_scroll_view_tests.dart locally and there are no failures.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: scrolling Viewports, list views, slivers, etc. labels Nov 12, 2023
@goderbauer goderbauer requested review from goderbauer and Piinks and removed request for goderbauer November 14, 2023 23:16
@goderbauer
Copy link
Member

@Michal-MK thanks for the contribution. Looks like this PR is failing some checks. Can you please take a look and fix those up? Thanks!

@Michal-MK
Copy link
Contributor Author

@goderbauer I can decode the Linux Analyze check, which is clear to me, I have a TODO in tests, but the issue is I do not know how to write tests for this fix. For the other 6 failing checks I have no idea what the output is trying to tell me. I looked at the "undestading a LUCI build failure", but that to me appears to describe something else.

�05:52:02� Test failed.
��������������������������������������������������������
For your convenience, the error messages reported above are repeated here:
  �  �  

@Michal-MK
Copy link
Contributor Author

Apart from tree-status all tests should pass, I'll wait and see if a test for this will be needed/asked for.

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.

Thanks for the contribution @Michal-MK!
This will need a test to verify it works as expected, and to prevent us from accidentally undoing this in the future. :)

@Michal-MK
Copy link
Contributor Author

Michal-MK commented Nov 17, 2023

Okay so I tried something, the issue is that I do not fully understand the test as I just while(true)'d until a set of inputs caused the if to be hit with the faulty values. Then I searched for a place where the wrong value would appear, and it appeared in the outer scrollable's controller.

I replaced another == 0 check that may render the first one obsolete, and added a test that will fail if the outer scroll controller's listener is called. Since that is the place I found manifests the precision error to the user.

image

This is the output before the fix. (here it passes since I added the assert(false) after)
After the fix it does not trigger the outer controller's callback:

image

All other tests in NestedScrollView pass.

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.

Hey @Michal-MK welcome. Thanks for contributing!

await tester.pumpWidget(build());

outerController.addListener(() {
assert(false); // We do not want this to be called.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why? The outer should scroll before the inner, why would the outer not scroll?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The way I understand the test is that we have:
tester.drag(find.byKey(innerKey), const Offset(0, 2000));

Which means dragging the finger down the screen (I believe). So we are immediately overscrolling and triggering the dampening effect of the spring. The total height is 300 + 64 + 800 from the individual components. 1164 < 2000. So we have technically 836 units to work with.

tester.fling(find.byKey(innerKey), const Offset(0, -600), 2000);

After that we immediately fling the finger up the screen. My understanding is that the total distance will be 600 units up and the 2000 just affects how quickly the 600 will happen.

Which equals 836 - 600 = 236. So we still have 236 units to spare before the outer scrollable should start scrolling. What does not fly is the spring does not actually "stretch" to 836 visually, but I have no idea how that is implemented. If this description makes sense I will add it to the test itself, but there is a possibility it is nonsense and I just do not understand what I wrote ;).

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, it may be easier to understand if you add checks on what the actual position is before and after each gesture. What is the value of position.pixels for the outer and inner each time? For the nested scroll view in general, the inner should not scroll before the outer has fully scrolled to its end.

If we are dragging down first, overscrolling the outer, then flinging up, I would expect the outer to consume some of the distance flinging up before the inner receives any.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"For the nested scroll view in general, the inner should not scroll before the outer has fully scrolled to its end." - True, but since in the default state, the outer is at zero we are immediately overscrolling inner scrollable.

"If we are dragging down first, overscrolling the outer, then flinging up, I would expect the outer to consume some of the distance flinging up before the inner receives any." - the outer is not being overscrolled: This is the default state:
image
And this is the state after pulling down:
image

The outer scrollable does not move, and I checked that by adding a listener to the outer scroll view and pulled downwards, and indeed, I got no prints. I.e. the callback was never invoked.

outerController.addListener(() {
  print('outerController: ${outerController.offset}, ${outerController.position}');
});

Since we are not supposed to assign a scroll controller to the inner scrollview I do not have output from that. But I did try a fling up, after overscrolling the inner scrollable, and it can actually be fairly strong and yet it does not trigger anything in the outer scrollable.

20231130_185335-small.mp4

The actual positions are in here:
#138319 (comment)
As can be seen, before it fluctuated between "immeasurable delta from 0" and 0 now it stays on 0. I do not know how to test that other than though the scroll controller not firing.

Copy link
Contributor

Choose a reason for hiding this comment

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

the outer is not being overscrolled

Ah right I see. The inner is using bouncing scroll physics. Yup. So the inner overscrolls at the top, but then when the test flings the inner up, I expect the outer to consume some of the distance before the inner does.

Since we are not supposed to assign a scroll controller to the inner scrollview I do not have output from that.

The inner scroll controller is accessible through the inner key you have created in the test.

Can you add checks that validate the inner and outer positions at each step to the 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.

Sure, I'll try.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, I added more asserts to the test, I did not find a way to obtain the controller from the previous setup so I just borrowed someone else's solution :). The outer scrollable does not move, it may be surprising but it's true (according to the expect() and the .addListener())

@goderbauer goderbauer requested a review from Piinks November 28, 2023 23:16
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.

@Michal-MK thanks for the update. It looks like there are a lot of failing tests currently, can you take a look?

await tester.pumpWidget(build());

outerController.addListener(() {
assert(false); // We do not want this to be called.
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, it may be easier to understand if you add checks on what the actual position is before and after each gesture. What is the value of position.pixels for the outer and inner each time? For the nested scroll view in general, the inner should not scroll before the outer has fully scrolled to its end.

If we are dragging down first, overscrolling the outer, then flinging up, I would expect the outer to consume some of the distance flinging up before the inner receives any.

@Michal-MK
Copy link
Contributor Author

Tests are failing randomly, as noted here: #138319 (comment). No idea how to decode the output. I just merged current master into it and it worked again. That was the case even before I added my test so i do not know what is causing it.

@Michal-MK
Copy link
Contributor Author

So it seems that Linux web_canvaskit_tests_4 and Linux web_tests_0 are now failing consistently, but their output is still illegible: â��â��â��â��â��â��â��â��â�

@Michal-MK
Copy link
Contributor Author

After spending more time with it I did find the tiny "show more details" button. There I finally got the actual output. Hmm why is GitHub's so broken then...? I modified the test to hopefully pass the two failing cases.


await tester.drag(find.byKey(innerKey), const Offset(0, 2000)); // Over-scroll the inner Scrollable to the bottom

const double endPosition = -1974.0862087158384;
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than this complicated value, you could use the greaterThan, moreOrLessEquals, etc tools that flutter_test provides.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since. we are essentially testing a double precision error, I included the exact value the controller had after the drag, but I can change it.

Copy link
Contributor Author

@Michal-MK Michal-MK Dec 12, 2023

Choose a reason for hiding this comment

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

On a second thought I like how the math adds up, because later in the code I do: endValue + 600 which would then not work... If I used moreOrLessEquals the delta would have to be really big. I could do moreOrLessEquals(-2000, delta: 100.0) since 2000 is the drag parameter, but I do not like how vague that becomes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok! We should probably add some context at least in a comment for future wonderers. Style guide has recommendations: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#avoid-mysterious-and-magical-numbers-that-lack-a-clear-derivation

expect(outer.offset, 0.0);
expect(inner.offset, endPosition + 600);

// Since there is a difference between scrolling on devices and the web absolute values are not checked...
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure I follow here. What was the failure this is working around?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Before I had concrete values at each step, as seen in this commit, I then rewrote it since tests on Web were failing due to the controller reporting different values than seen on native platforms. I can remove the comment if that is intended.

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.

This change looks good, with added context in the test for that value. I will be OOO on vacation after today, so if you would not like to wait, another reviewer can be requested on the Discord. This change will need two approvals. Thanks!

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.

Thanks for the update @Michal-MK, and happy new year! This LGTM!

@Michal-MK
Copy link
Contributor Author

Happy new year to you and all of the Flutter team. Awesome framework, glad I could contribute. :)

@Michal-MK
Copy link
Contributor Author

@Piinks Is there something on my part that needs doing? The "Google testing" test is now running for more than 5 days, is that intended?

@Piinks
Copy link
Contributor

Piinks commented Jan 8, 2024

This still needs a second review, but I have rebased it to kick the Google testing shard. 👍
Following up on the second review in https://discord.com/channels/608014603317936148/804482791517192273/1193960740856545290

@Piinks
Copy link
Contributor

Piinks commented Jan 8, 2024

Hmm, I was not able to rebase since there is a merge conflict. Can you rebase with the master branch @Michal-MK?

@github-actions github-actions bot added a: tests "flutter test", flutter_test, or one of our tests a: text input Entering text in a text field or keyboard related problems platform-ios iOS applications specifically labels Jan 8, 2024
@github-actions github-actions bot removed f: routes Navigator, Router, and related APIs. f: gestures flutter/packages/flutter/gestures repository. a: desktop Running on desktop f: focus Focus traversal, gaining or losing focus f: integration_test The flutter/packages/integration_test plugin labels Jan 8, 2024
@Michal-MK
Copy link
Contributor Author

Struggling with the rebase, give me a minute :)

@jmagman jmagman removed their request for review January 8, 2024 18:10
@Michal-MK
Copy link
Contributor Author

So, I finally won agains git rebase, unfortunately, the bot added some people for review, that was not intentional. @Piinks I do not see the Discord channel and I have not found any info on how to enable it on what is publicly available.

@christopherfujino christopherfujino removed their request for review January 8, 2024 19:39
@Piinks
Copy link
Contributor

Piinks commented Jan 8, 2024

Thanks for rebasing! Looks like that channel is restricted to contributors with commit access (members of flutter-hackers): https://github.com/flutter/flutter/wiki/Contributor-access

Sorry for sharing something inaccessible. The channel is for seeking reviewers, so it should get picked up soon. 👍

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

secondary LGTM

@Piinks Piinks added the autosubmit Merge PR when tree becomes green via auto submit App label Jan 10, 2024
@auto-submit auto-submit bot merged commit 7efed85 into flutter:master Jan 10, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 10, 2024
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Jan 10, 2024
flutter/flutter@126302d...b840a60

2024-01-10 [email protected] Roll Flutter Engine from 3269fd84460d to b361a60ae224 (1 revision) (flutter/flutter#141271)
2024-01-10 [email protected] Roll Flutter Engine from a5d446da5495 to 3269fd84460d (1 revision) (flutter/flutter#141264)
2024-01-10 [email protected] Roll Flutter Engine from 3ccf66bed335 to a5d446da5495 (2 revisions) (flutter/flutter#141252)
2024-01-10 [email protected] Roll Flutter Engine from e57e418c02ae to 3ccf66bed335 (1 revision) (flutter/flutter#141241)
2024-01-10 [email protected] Roll Flutter Engine from 7e6f3d847e01 to e57e418c02ae (1 revision) (flutter/flutter#141240)
2024-01-10 [email protected] Roll Flutter Engine from 1cf2e0a603c7 to 7e6f3d847e01 (1 revision) (flutter/flutter#141237)
2024-01-10 [email protected] Roll Flutter Engine from 32bbf8be8d2c to 1cf2e0a603c7 (1 revision) (flutter/flutter#141232)
2024-01-10 [email protected] Roll Flutter Engine from 5b9d2132b7cd to 32bbf8be8d2c (1 revision) (flutter/flutter#141229)
2024-01-10 [email protected] Roll Flutter Engine from 941f268fc8bb to 5b9d2132b7cd (1 revision) (flutter/flutter#141228)
2024-01-10 [email protected] Roll Flutter Engine from 542fea9edae4 to 941f268fc8bb (3 revisions) (flutter/flutter#141224)
2024-01-10 [email protected] `NestedScrollView`'s outer scrollable jumping with `BouncingScrollPhysics` due to `double` precision errors (flutter/flutter#138319)
2024-01-10 [email protected] Roll Flutter Engine from 8c1501f3956d to 542fea9edae4 (1 revision) (flutter/flutter#141217)
2024-01-10 [email protected] Fix or except leaks. (flutter/flutter#141081)
2024-01-09 [email protected] Roll Flutter Engine from 693af0c699c5 to 8c1501f3956d (1 revision) (flutter/flutter#141215)
2024-01-09 [email protected] TextStyle: In copyWith, stop ignoring debugLabel when receiver has none (flutter/flutter#141141)
2024-01-09 [email protected] Roll Flutter Engine from a35e3b026e1d to 693af0c699c5 (5 revisions) (flutter/flutter#141209)
2024-01-09 [email protected] Replace deprecated `exists` in podhelper.rb (flutter/flutter#141169)
2024-01-09 [email protected] Correctly handle null case in ProcessText.queryTextActions (flutter/flutter#141205)
2024-01-09 [email protected] Add environment variable to leak tracking bots. (flutter/flutter#141137)
2024-01-09 [email protected] Reapply "Dynamic view sizing" (#140165) (flutter/flutter#140918)
2024-01-09 [email protected] Introduce new Form validation method  (flutter/flutter#135578)
2024-01-09 [email protected] Update `RouteObserver` example and fix an error thrown (flutter/flutter#141166)
2024-01-09 [email protected] Upgrade leak_tracker. (flutter/flutter#141153)
2024-01-09 [email protected] [ci.yaml] Do not run packaging test on presubmit (flutter/flutter#141192)
2024-01-09 [email protected] Roll Flutter Engine from 036b39fa47fa to a35e3b026e1d (6 revisions) (flutter/flutter#141191)
2024-01-09 [email protected] Run tests on iOS 16 or iOS 17 (flutter/flutter#141178)
2024-01-09 [email protected] Remove conditions that depend on order. (flutter/flutter#141183)
2024-01-09 [email protected] Roll Flutter Engine from b3c8597df0e2 to 036b39fa47fa (1 revision) (flutter/flutter#141179)
2024-01-09 [email protected] resolved the issue of indeterminate CircularProgressIndicator.adaptive on Darwin  (flutter/flutter#140947)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
arc-yong pushed a commit to Arctuition/packages-arc that referenced this pull request Jun 14, 2024
flutter/flutter@126302d...b840a60

2024-01-10 [email protected] Roll Flutter Engine from 3269fd84460d to b361a60ae224 (1 revision) (flutter/flutter#141271)
2024-01-10 [email protected] Roll Flutter Engine from a5d446da5495 to 3269fd84460d (1 revision) (flutter/flutter#141264)
2024-01-10 [email protected] Roll Flutter Engine from 3ccf66bed335 to a5d446da5495 (2 revisions) (flutter/flutter#141252)
2024-01-10 [email protected] Roll Flutter Engine from e57e418c02ae to 3ccf66bed335 (1 revision) (flutter/flutter#141241)
2024-01-10 [email protected] Roll Flutter Engine from 7e6f3d847e01 to e57e418c02ae (1 revision) (flutter/flutter#141240)
2024-01-10 [email protected] Roll Flutter Engine from 1cf2e0a603c7 to 7e6f3d847e01 (1 revision) (flutter/flutter#141237)
2024-01-10 [email protected] Roll Flutter Engine from 32bbf8be8d2c to 1cf2e0a603c7 (1 revision) (flutter/flutter#141232)
2024-01-10 [email protected] Roll Flutter Engine from 5b9d2132b7cd to 32bbf8be8d2c (1 revision) (flutter/flutter#141229)
2024-01-10 [email protected] Roll Flutter Engine from 941f268fc8bb to 5b9d2132b7cd (1 revision) (flutter/flutter#141228)
2024-01-10 [email protected] Roll Flutter Engine from 542fea9edae4 to 941f268fc8bb (3 revisions) (flutter/flutter#141224)
2024-01-10 [email protected] `NestedScrollView`'s outer scrollable jumping with `BouncingScrollPhysics` due to `double` precision errors (flutter/flutter#138319)
2024-01-10 [email protected] Roll Flutter Engine from 8c1501f3956d to 542fea9edae4 (1 revision) (flutter/flutter#141217)
2024-01-10 [email protected] Fix or except leaks. (flutter/flutter#141081)
2024-01-09 [email protected] Roll Flutter Engine from 693af0c699c5 to 8c1501f3956d (1 revision) (flutter/flutter#141215)
2024-01-09 [email protected] TextStyle: In copyWith, stop ignoring debugLabel when receiver has none (flutter/flutter#141141)
2024-01-09 [email protected] Roll Flutter Engine from a35e3b026e1d to 693af0c699c5 (5 revisions) (flutter/flutter#141209)
2024-01-09 [email protected] Replace deprecated `exists` in podhelper.rb (flutter/flutter#141169)
2024-01-09 [email protected] Correctly handle null case in ProcessText.queryTextActions (flutter/flutter#141205)
2024-01-09 [email protected] Add environment variable to leak tracking bots. (flutter/flutter#141137)
2024-01-09 [email protected] Reapply "Dynamic view sizing" (#140165) (flutter/flutter#140918)
2024-01-09 [email protected] Introduce new Form validation method  (flutter/flutter#135578)
2024-01-09 [email protected] Update `RouteObserver` example and fix an error thrown (flutter/flutter#141166)
2024-01-09 [email protected] Upgrade leak_tracker. (flutter/flutter#141153)
2024-01-09 [email protected] [ci.yaml] Do not run packaging test on presubmit (flutter/flutter#141192)
2024-01-09 [email protected] Roll Flutter Engine from 036b39fa47fa to a35e3b026e1d (6 revisions) (flutter/flutter#141191)
2024-01-09 [email protected] Run tests on iOS 16 or iOS 17 (flutter/flutter#141178)
2024-01-09 [email protected] Remove conditions that depend on order. (flutter/flutter#141183)
2024-01-09 [email protected] Roll Flutter Engine from b3c8597df0e2 to 036b39fa47fa (1 revision) (flutter/flutter#141179)
2024-01-09 [email protected] resolved the issue of indeterminate CircularProgressIndicator.adaptive on Darwin  (flutter/flutter#140947)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App 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.

NestedScrollView scrolls inner view without scrolling outer view
3 participants