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

[Android] Fix onMomentumScrollEnd similar to iOS #45187

Closed
wants to merge 4 commits into from

Conversation

Biki-das
Copy link
Contributor

@Biki-das Biki-das commented Jun 26, 2024

Summary:

in iOS on a scroll generated programatically, the onMomentScrollEnd is fired, though in case of android the same does not happen, this PR tries to implement the same behaviour for android as well, while diving through the code it seems we have two extra onMomentumScrollEnd events. Only one event should be fired.

iOS Behaviour on Programmatic Scroll

Screen.Recording.2024-06-26.at.8.18.46.PM.mov
Screen.Recording.2024-06-26.at.8.50.00.PM.mov

Android Behaviour on Programmatic Scroll

Screen.Recording.2024-06-26.at.8.19.28.PM.mov
Screen.Recording.2024-06-26.at.8.51.15.PM.mov

If closely observed we can see the onMomentumScrollEnd does not gets called in Android unlike to iOS.

Changelog:

[Android] [Fixed] - fix onMomentum scroll end similar to iOS

Test Plan:

i have added updates to the FlatList example and ScrollViewSimple
here is a ScreenRecording of onMomentumScrollEnd firing in android after the code changes

Screen.Recording.2024-06-26.at.8.12.08.PM.mov
Screen.Recording.2024-06-26.at.8.41.55.PM.mov

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Jun 26, 2024
@Biki-das
Copy link
Contributor Author

cc @NickGerleman @javache

@Biki-das
Copy link
Contributor Author

@cipolleschi would also love to know your thoughts as well.

@Biki-das Biki-das changed the title [Android ]fix onMomentum scroll end similar to iOS [Android ]fix onMomentumScrollEnd similar to iOS Jun 26, 2024
@analysis-bot
Copy link

Platform Engine Arch Size (bytes) Diff
android hermes arm64-v8a 20,300,225 -15,772
android hermes armeabi-v7a n/a --
android hermes x86 n/a --
android hermes x86_64 n/a --
android jsc arm64-v8a 23,497,181 -16,121
android jsc armeabi-v7a n/a --
android jsc x86 n/a --
android jsc x86_64 n/a --

Base commit: e6dd44d
Branch: main

@cipolleschi
Copy link
Contributor

Android is not really my area of expertise, so @javache and @cortinico would be better reviewers.

A couple of points though:

  • The 2 different events could be there because of Old Arch/New Arch variants
  • The Android code in the PR seems to tackle only the Old Architecture. Could you see if the New Architecture has the same problem? We are hyper focused on the New Arch, so Old Arch-only fixes are lower priorities for us.

@cortinico
Copy link
Contributor

  • The Android code in the PR seems to tackle only the Old Architecture. Could you see if the New Architecture has the same problem? We are hyper focused on the New Arch, so Old Arch-only fixes are lower priorities for us.

Yup basically this.
The PR looks quite involved so I'm unsure we have capacity to review/import it at the moment unless is a New Architecture regression @Biki-das

@Abbondanzo
Copy link
Contributor

This looks like an issue with both New Architecture and Old (bridgeless mode is enabled from screen captures). I'm happy to work with you @Biki-das to import and land these changes since this is a gap in platforms we'd like to close

@Biki-das
Copy link
Contributor Author

Biki-das commented Nov 5, 2024

@Abbondanzo happy to coordinate to land this, let me know how can i help 😃

@Abbondanzo
Copy link
Contributor

@Abbondanzo happy to coordinate to land this, let me know how can i help 😃

Awesome! Would you be able to rebase and convert your changes to Kotlin? Both ReactScrollView and ReactHorizontalScrollView have been converted to Kotlin recently. I can import after that 😄

@Biki-das
Copy link
Contributor Author

Biki-das commented Nov 5, 2024

@Abbondanzo happy to coordinate to land this, let me know how can i help 😃

Awesome! Would you be able to rebase and convert your changes to Kotlin? Both ReactScrollView and ReactHorizontalScrollView have been converted to Kotlin recently. I can import after that 😄

Cool,i would try to do the same tomorrow, its been a long time of the code, need to check it back and forth, thanks for your help, seeking to land this.

@Biki-das Biki-das force-pushed the fix-android-momentumScroll branch from 109a3b5 to 0e382c3 Compare November 6, 2024 15:23
@Biki-das
Copy link
Contributor Author

Biki-das commented Nov 6, 2024

@Abbondanzo ? were the files you mentioned converted to Kotlin, as i rebased it to the latest main,? am i doing something wrong?
As far as i can see Both ReactScrollView and ReactHorizontalScrollView are still .java files.

https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java

https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java

@Abbondanzo
Copy link
Contributor

@Abbondanzo ? was this file converted to Kotlin, as i rebased it to the latest main,? am i doing something wrong?

Ah, nope, my mistake! It hasn't been converted to Kotlin yet. Saw someone else working on a Kotlin conversion but that hasn't merged yet, so no need to wait

@facebook-github-bot
Copy link
Contributor

@Abbondanzo has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@Biki-das
Copy link
Contributor Author

Biki-das commented Nov 6, 2024

i see an internal linter fail action, obviously only meta employees are allowed to seek that, @Abbondanzo could you check and confirm the same.

Copy link
Contributor

@Abbondanzo Abbondanzo left a comment

Choose a reason for hiding this comment

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

i see an internal linter fail action, obviously only meta employees are allowed to seek that, @Abbondanzo could you check and confirm the same.

Yep, that's an easy fix on our end. I pulled this in and gave it a test. I've got a few change requests first but otherwise this is looking stellar!

@@ -926,6 +927,7 @@ private void handlePostTouchScrolling(int velocityX, int velocityY) {
}

if (mSendMomentumEvents) {
enableFpsListener();
Copy link
Contributor

Choose a reason for hiding this comment

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

Could these tweaks to the FPS listener be removed? Event dispatching for touch events shouldn't be changing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

shall i just remove them as far as i can remember they are for per monitoring.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would restore the original behavior here, removing modifications of the enable/disableFpsListener methods from this method

} else {
scrollView.scrollTo(data.mDestX, data.mDestY);
ReactScrollViewHelper.emitScrollMomentumEndEvent(scrollView);
Copy link
Contributor

Choose a reason for hiding this comment

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

While iOS does this for non-animated views, this is a bug on the iOS side that we should avoid introducing into Android for the sake of parity. The onMomentumScrollEnd event should only fire after onMomentumScrollBegin, driven by a user-activated fling or animated scrollTo

@@ -733,7 +734,6 @@ public void run() {
mPostTouchRunnable = null;
if (mSendMomentumEvents) {
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

mSendMomentumEvents should only be used to guard from emitting events to JS, the NativeAnimatedModule should still receive scroll end here regardless (and FPS listener disabled)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

? could you brief this up and what should i do, i seems to have lost context of the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

The brace should be added back here and the if block should only contain the event emit, not the few lines that follow it.

@Biki-das
Copy link
Contributor Author

Biki-das commented Nov 6, 2024

thanks for the review, will need to take a look and remember since its been a while i wrote the fixes.

Copy link
Contributor Author

@Biki-das Biki-das left a comment

Choose a reason for hiding this comment

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

@Abbondanzo

Have introduced a few changes

  1. A boolean flag mMomentumScrollBeginFired is introduced to track whether onMomentumScrollBegin has been fired.
  2. The handleSmoothScrollMomentumEvents method checks this flag before emitting the onMomentumScrollEnd event.
  3. The fling method is overridden to set this flag to true when onMomentumScrollBegin is fired.

@@ -926,6 +927,7 @@ private void handlePostTouchScrolling(int velocityX, int velocityY) {
}

if (mSendMomentumEvents) {
enableFpsListener();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

shall i just remove them as far as i can remember they are for per monitoring.

Biki-das and others added 2 commits November 6, 2024 23:55
…react/views/scroll/ReactScrollView.java

Co-authored-by: Peter Abbondanzo <peter@abbondanzo.com>
…react/views/scroll/ReactScrollView.java

Co-authored-by: Peter Abbondanzo <peter@abbondanzo.com>
@Biki-das
Copy link
Contributor Author

Biki-das commented Nov 6, 2024

Oops i see we have a fling method already, i have losed a lot of context with the code actually, so any extra help is appreciated on the requests raised.

@Abbondanzo
Copy link
Contributor

As a follow-up, I've made two modifications to iOS that should fix the way scroll momentum events are fired:
#47468
#47471

@Abbondanzo
Copy link
Contributor

Oops i see we have a fling method already, i have losed a lot of context with the code actually, so any extra help is appreciated on the requests raised.

I can reimport this PR in its current state and make a few modifications internally. If you want to reintroduce FPS listener stuffs, it can go in a separate PR if you're okay with that.

@facebook-github-bot
Copy link
Contributor

@Abbondanzo has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@Biki-das
Copy link
Contributor Author

Biki-das commented Nov 7, 2024

@Abbondanzo sorry for the late reply, sure that works , we can introduce the FPS listener stuff on a seperate PR mentioning the same PR, so we can keep track of the same rest i shall revert it back to where we did not had the fling override? i see you imported the PR again so let me know how can i make this easy for you to make modifications internally.

@Abbondanzo
Copy link
Contributor

@Abbondanzo i shall revert it back to where we did not had the fling override? i see you imported the PR again so let me know how can i make this easy for you to make modifications internally.

@Biki-das It's okay as-is. After taking a closer look into how we perform animations for smooth scrolling, I'm exploring moving this all into ReactScrollViewHelper. More specifically, I think we can just emit momentum scroll events from the animation listener here:

object : Animator.AnimatorListener {
override fun onAnimationStart(animator: Animator) {
val scrollState = scrollView.reactScrollViewScrollState
scrollState.isCanceled = false
scrollState.isFinished = false
}
override fun onAnimationEnd(animator: Animator) {
scrollView.reactScrollViewScrollState.isFinished = true
updateFabricScrollState<T>(scrollView)
}
override fun onAnimationCancel(animator: Animator) {
scrollView.reactScrollViewScrollState.isCanceled = true
}
override fun onAnimationRepeat(animator: Animator) = Unit
})
}

This way, we need not maintain a runnable in each scroll view class and we don't have to manually invoke the smooth scroll helper in each callsite. Also, it ensures momentum scroll events are properly fired for scroll views that use pagination (which privately invoke smooth scroll via fling under the hood).

@Biki-das
Copy link
Contributor Author

Biki-das commented Nov 7, 2024

@Abbondanzo i shall revert it back to where we did not had the fling override? i see you imported the PR again so let me know how can i make this easy for you to make modifications internally.

@Biki-das It's okay as-is. After taking a closer look into how we perform animations for smooth scrolling, I'm exploring moving this all into ReactScrollViewHelper. More specifically, I think we can just emit momentum scroll events from the animation listener here:

object : Animator.AnimatorListener {
override fun onAnimationStart(animator: Animator) {
val scrollState = scrollView.reactScrollViewScrollState
scrollState.isCanceled = false
scrollState.isFinished = false
}
override fun onAnimationEnd(animator: Animator) {
scrollView.reactScrollViewScrollState.isFinished = true
updateFabricScrollState<T>(scrollView)
}
override fun onAnimationCancel(animator: Animator) {
scrollView.reactScrollViewScrollState.isCanceled = true
}
override fun onAnimationRepeat(animator: Animator) = Unit
})
}

This way, we need not maintain a runnable in each scroll view class and we don't have to manually invoke the smooth scroll helper in each callsite. Also, it ensures momentum scroll events are properly fired for scroll views that use pagination (which privately invoke smooth scroll via fling under the hood).

wow, that's a good approach i see, By centralising this functionality, we can avoid duplication, improve consistency, and ensure momentum scroll events are properly fired even for pagination-based scroll views. I am happy to see this fix being landed, it bugged me off few months back on the product experience, trying to do a specific feature and not being able to do it, and i felt, this is not just limited to me, a lot of other devs would also get bitten of this, so thank you for helping us out there, means a lot ❤️.

@facebook-github-bot
Copy link
Contributor

@Abbondanzo merged this pull request in c69e330.

@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @Biki-das in c69e330

When will my fix make it into a release? | How to file a pick request?

blakef pushed a commit that referenced this pull request Dec 4, 2024
Summary:
in iOS on a scroll generated programatically, the `onMomentScrollEnd` is fired, though in case of android the same does not happen, this PR tries to implement the same behaviour for android as well, while diving through the code it seems we have two extra `onMomentumScrollEnd` events. Only one event should be fired.

**iOS Behaviour on Programmatic Scroll**

https://github.com/facebook/react-native/assets/72331432/fb8f16b1-4db6-49fe-83a1-a1c40bf49705

https://github.com/facebook/react-native/assets/72331432/9842f522-b616-4fb3-b197-40817f4aa9cb

**Android Behaviour on Programmatic Scroll**

https://github.com/facebook/react-native/assets/72331432/c24d3f06-4e2a-4bef-81af-d9227a3b1a4a

https://github.com/facebook/react-native/assets/72331432/d4917843-730b-4bd7-90d9-33efb0f471a7

If closely observed we can see the `onMomentumScrollEnd` does not gets called in Android unlike to iOS.

[Android][Fixed] - Dispatch onMomentumScrollEnd after programmatic scrolling

Pull Request resolved: #45187

Test Plan:
i have added updates to the FlatList example and ScrollViewSimple
here is a ScreenRecording of `onMomentumScrollEnd` firing in android after the code changes

https://github.com/facebook/react-native/assets/72331432/f036d1a5-6ebf-47ba-becd-4db98a406b15

https://github.com/facebook/react-native/assets/72331432/8c788c39-3392-4822-99c5-6e320398714b

Reviewed By: javache

Differential Revision: D65539724

Pulled By: Abbondanzo

fbshipit-source-id: f3a5527ac5979f5ec0c6ae18d80fdc20c9c9c14b
@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @Biki-das in abbe117

When will my fix make it into a release? | How to file a pick request?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants