Skip to content
New issue

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

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

Already on GitHub? # to your account

Fix useAnimatedScrollHandler not intercepting momentum events on Android #3948

Closed
wants to merge 2 commits into from

Conversation

tomekzaw
Copy link
Member

@tomekzaw tomekzaw commented Jan 13, 2023

Summary

Fixes #2735, see #2735 (comment) for details.

The main idea behind this PR is to enable emitting momentum events from the native side by enforcing sendMomentumEvents to be true.

First, I came up with the following patch in createAnimatedComponent.tsx, however this doesn't fully resolve the issue as setNativeProps is not supported on Fabric.

export default function createAnimatedComponent(
         viewTag = findNodeHandle(this._component.recyclerlistview_unsafe);
       }
 
+      if (Platform.OS === 'android' && componentName.endsWith('ScrollView')) {
+        // @ts-ignore fix typing
+        const eventNames = this.props?.onScroll.current.eventNames;
+        if (
+          eventNames.some((eventName: string) => eventName.includes('Momentum'))
+        ) {
+          this._setNativeProps({ sendMomentumEvents: true });
+        }
+      }
+
       for (const key in this.props) {
         const prop = this.props[key];
         if (
@@ -287,7 +297,7 @@ export default function createAnimatedComponent(
       }
     }
 
-    _updateFromNative(props: StyleProps) {
+    _setNativeProps(props: StyleProps) {
       if (options?.setNativeProps) {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         options.setNativeProps(this._component!, props);

Since we cannot use setNativeProps, I had to come up with a solution that changes the props and thus works on both architectures. Here's how it works: if useAnimatedScrollHandler has a momentum event listener and simultaneously Animated.ScrollView doesn't have any regular JS momentum event listener defined, we add a dummy listener for onMomentumEnd (chosen arbitrarily by me) which causes sendMomentumEvents to be true and thus enables emitting momentum events on the native side. Currently, the only two momentum events supported by React Native are onMomentumBegin and onMomentumEnd, see here, so I hard-coded these names as for now.

Another approach would be to call setSendMomentumEvents(true) in the native code which is probably better than adding a dummy listener on JS context.

Test plan

This PR can be tested on the example from issue description #2735.

Note that onMomentumEnd event callback will be called exactly three times, it's not an issue with Reanimated, see #2735 (comment) for details.

Also keep in mind that although _filterNonAnimatedProps gets executed during fast refresh (which is correct), for some reason value.current.eventNames isn't updated (i.e. it contains the event names from before the update, even if you add or remove some entries inside useAnimatedScrollHandler), therefore when testing out the changes most likely you will need to reload the app.

Make sure to test the following cases:

  1. No worklet momentum event listener in useAnimatedScrollHandler, no JS momentum event listener in Animated.ScrollView → dummy listener shouldn't be registered 😴
  2. Some worklet momentum event listener in useAnimatedScrollHandler, but no JS momentum event listener in Animated.ScrollView → dummy listener should be registered ✅
  3. Some worklet momentum event listener in useAnimatedScrollHandler and some JS momentum event listener in Animated.ScrollView → dummy listener shouldn't be registered (or overwritten) 😴
  const scrollHandler = useAnimatedScrollHandler({
    onBeginDrag: () => {
      isScrolling.value = true;
      console.log('onBeginDrag');
    },
    onEndDrag: () => {
      isScrolling.value = false;
      console.log('onEndDrag');
    },
    // onMomentumBegin: () => {
    //   console.log('onMomentumBegin');
    // },
    // onMomentumEnd: () => {
    //   console.log('onMomentumEnd');
    // },
  });

@tomekzaw tomekzaw changed the title Fix useAnimatedScrollHandler not intercepting momentum events Fix useAnimatedScrollHandler not intercepting momentum events on Android Jan 13, 2023
@tomekzaw tomekzaw marked this pull request as draft January 16, 2023 10:42
@tomekzaw tomekzaw force-pushed the @tomekzaw/fix-momentum-events branch from 1f2c5fe to 40fdea1 Compare January 20, 2023 12:58
Copy link
Contributor

@Latropos Latropos left a comment

Choose a reason for hiding this comment

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

I've tested that it works perfect with my other fix here #4238
(smooth Scroll not working on Android)

BeforeAfter
Screen.Recording.2023-03-21.at.09.54.10.mov
Screen.Recording.2023-03-21.at.09.53.39.mov

@hirbod
Copy link
Contributor

hirbod commented Jun 5, 2023

@tomekzaw, is anything holding this PR back? I just stumbled across an issue where onMomentumEnd is not working correctly on Android, especially in cases where the scroll was triggered via ref and imperative functions.

@hirbod
Copy link
Contributor

hirbod commented Jun 5, 2023

@tomekzaw I tried your approach, and it does fix the issue only when the scroll is made with a drag move. However, it doesn't trigger when I scroll via scrollToOffset.

@tomekzaw
Copy link
Member Author

tomekzaw commented Jun 7, 2023

is anything holding this PR back? I just stumbled across an issue where onMomentumEnd is not working correctly on Android, especially in cases where the scroll was triggered via ref and imperative functions.

@hirbod I'm not particularly happy with adding a dummy listener (props.onMomentumScrollEnd = dummyListener;), a better but definitely more complex approach would be to enable the flag directly in Java code.

I tried your approach, and it does fix the issue only when the scroll is made with a drag move. However, it doesn't trigger when I scroll via scrollToOffset.

I'm afraid this might be a separate problem. This PR already adds a dummy listener for momentum events so it should work the same way as in React Native. Perhaps we should install more listeners?

@hirbod
Copy link
Contributor

hirbod commented Jun 7, 2023

Yeah, the dummy listener is not the greatest solution, but somehow acceptable for now. I guess you're right that there is another problem, but not having onMomentumScrollEnd triggered with the imperative scroll methods is a bummer :/

Haven't tested if thats the case with stock Scrollview as well, I would need to verify it.

@tjzel
Copy link
Collaborator

tjzel commented Sep 14, 2023

@tomekzaw Is this PR still relevant? Recently I've confirmed that momentum events are intercepted on Android #5058

@szydlovsky
Copy link
Contributor

Seems to be working already 👍

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Android] onMomentumBegin/ -End not being called in Animated.ScrollView
5 participants