react-native-reanimated: [Android] onMomentumBegin/ -End not being called in Animated.ScrollView

Description

onMomentumEnd is not being called anymore since Version 2.3.0 in Animated.ScrollView. An animated FlatList or SecionList (with createAnimatedComponent) still calls onMomentumEnd. Am I doing something wrong in my code? In Version 2.2.4 it still worked. This causes a bug in this library as well: https://github.com/PedroBern/react-native-collapsible-tab-view/issues/221

Expected behavior

I expect onMomentumEnd to be called like in version 2.2.4 and lower.

Actual behavior & steps to reproduce

Scroll the screen of the code I provided. “onMomentumEnd” will not be printed in the console.

Snack or minimal code example

import React from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedScrollHandler,  useSharedValue } from 'react-native-reanimated';

export default function App() {
  const translationY = useSharedValue(0);
  const isScrolling = useSharedValue(false);

  const scrollHandler = useAnimatedScrollHandler({
    onScroll: (event) => {
      translationY.value = event.contentOffset.y;
    },
    onBeginDrag: (e) => {
      isScrolling.value = true;
      console.log('onBeginDrag');
    },
    onEndDrag: (e) => {
      isScrolling.value = false;
      console.log('onEndDrag');
    },
    onMomentumEnd: () => {
      console.log('onMomentumEnd');
    },
  });

  return (
    <View style={{flex:1}} >
      <Animated.ScrollView
        onScroll={scrollHandler}
        scrollEventThrottle={16}>
          <View style={{height:250, width:300, backgroundColor:"red"}}></View>
          <View style={{height:250, width:300, backgroundColor:"blue"}}></View>
          <View style={{height:250, width:300, backgroundColor:"yellow"}}></View>
          <View style={{height:250, width:300, backgroundColor:"purple"}}></View>
          <View style={{height:250, width:300, backgroundColor:"green"}}></View>
      </Animated.ScrollView>
    </View>
  );
}

Package versions

  • React Native: 0.64.3
  • React Native Reanimated: 2.3.0
  • NodeJS: 16.6.2
  • Xcode: - Just tested on android
  • Java & Gradle: I tested in expo SDK 44 beta

Affected platforms

  • Android
  • iOS
  • Web

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 49
  • Comments: 30 (9 by maintainers)

Commits related to this issue

Most upvoted comments

@alexco2 Even I was facing the same issue and after spending hours of debugging I found that, if we add onMomentumScrollEnd prop to the FlatList or ScrollView, then onMomentumEnd handler was triggered correctly This is what I did:

<Animated.ScrollView
  ref={ref}
  onMomentumScrollEnd={() => {}}
  {...otherProps}
/>

Everyone affected by this, don’t forget to upvote the top post. Perhaps it helps to get it more visibility.

Hello maintainers, what would it take to get some attention to this? (🙏 @piaskowyk)

It’s blocking users of react-native-collapsible-tab-view from upgrading to RN 0.68 because Reanimated versions above 2.3.0 break the library due to this bug.

Hello @kmagiera @piaskowyk @Szymon20000 @jkadamczyk, this issue is now the third most interacted issue (regarding thumbs up) in rea. Could you give us an update on the current state? Is there anything we could do to support you in solving this issue? Thanks 😃

Thanks @alexco2 for reporting this issue. Also, thank you @fhugoduarte for providing a workaround in https://github.com/software-mansion/react-native-reanimated/issues/2735#issuecomment-1001714779 as well as @savelichalex for the explanation why it works this way in https://github.com/software-mansion/react-native-reanimated/issues/2735#issuecomment-1177923431.

The problem occurs only on Android (both on Paper and Fabric), onMomentumEnd is properly emitted on iOS. I’ve submitted a PR that should fix it, see #3948.

Apart from this, using the example @alexco2 provided in the issue description, I can confirm @andreialecu’s observation from https://github.com/software-mansion/react-native-reanimated/issues/2735#issuecomment-1117067988 that onMomentumEnd callback is always called exactly 3 times with the same event values (personally, I would expect it to be called exactly once):

onBeginDrag
onEndDrag
onMomentumEnd {"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 4026.54541015625}, "contentSize": {"height": 5000, "width": 392.7272644042969}, "eventName": "55onMomentumScrollEnd", "layoutMeasurement": {"height": 778.9091186523438, "width": 392.7272644042969}, "responderIgnoreScroll": true, "target": 55, "velocity": {"x": 0, "y": 0}}
onMomentumEnd {"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 4026.54541015625}, "contentSize": {"height": 5000, "width": 392.7272644042969}, "eventName": "55onMomentumScrollEnd", "layoutMeasurement": {"height": 778.9091186523438, "width": 392.7272644042969}, "responderIgnoreScroll": true, "target": 55, "velocity": {"x": 0, "y": 0}}
onMomentumEnd {"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 4026.54541015625}, "contentSize": {"height": 5000, "width": 392.7272644042969}, "eventName": "55onMomentumScrollEnd", "layoutMeasurement": {"height": 778.9091186523438, "width": 392.7272644042969}, "responderIgnoreScroll": true, "target": 55, "velocity": {"x": 0, "y": 0}}

On the other hand, ReactScrollViewHelper#emitScrollMomentumEndEvent is also called 3 times (checked by setting a breakpoint inside the method), so I guess it’s the correct behavior from Reanimated side (to intercept all three events that were emitted) and probably there’s an issue somewhere deeper (RN/Android).

Also, the original name of the event is onMomentumScrollEnd, but useAnimatedScrollHandler accepts the worklet callback in onMomentumEnd field (without Scroll). Do you think we should add support for the original name (so effectively both onMomentumEnd and onMomentumScrollEnd work), deprecate the old name in v2 and completely remove it in v3 or just leave it as it is now?

@piaskowyk it would be amazing if we could have an update on the progress of this issue 😃

Guys, passing a function to onMomentumScrollEnd prop works for me. The onMomentumScrollEnd is called using the useAnimatedScrollHandler hook, as expected.

  const handleScrollHandler = useAnimatedScrollHandler({
    onMomentumScrollEnd: () => {
      // was called
    }
  })

  function handleMomentumScrollEnd() {
    // workaround reanimated 2.3 momentum scroll is not called
  }
  
  <Animated.ScrollView onScroll={handleScrollHandler}  onMomentumScrollEnd={handleMomentumScrollEnd}  />

Not only onMomentumEnd but also onMomentumBegin is not called either. It’s very annoying.

I don’t think there’s a need to patch it, you can just try <Tabs.ScrollView ... onMomentumScrollEnd={() => {}} />

Edit: I can confirm I’m now testing on Reanimated 2.8.0 and the above workaround seems to help.

I can confirm this issues. Sad this isn’t resolved since Dec 😦 Anything we can do to help?

Just ran into this, the workaround works but would be good to understand if there’s anything we can do to help solve this.

Try react-native-collapsible-tab-view@rc

https://github.com/PedroBern/react-native-collapsible-tab-view/pull/255

Let’s move this discussion over there as to not hijack this thread. 🙂

I just found out something interesting.

Seems that if I add an empty onMomentumScrollEnd handler to the ScrollView, it starts firing the event and the repro at the very top starts logging onMomentumEnd

Screenshot 2022-05-04 at 11 46 07

Example:

<Animated.ScrollView onScroll={scrollHandler} 
scrollEventThrottle={16} 
// add this:
onMomentumScrollEnd={()=>{}}>

Can anyone else confirm this? Note that it also seems to log onMomentumEnd three times, so something is weird with it.

@piaskowyk sorry to be the 20th person who pings you here, but this issue really needs some hands on to get resolved. It is actually a very annoying blocker 😕

Can we do something to get some traction here? Obviously nobody here has enough native knowledge to provide a PR.

I’ve got this bug too (reanimated 2.3.1). Unfortunately, the previous workaround didn’t help me. In my case, I needed to handle the change of the current item in my horizontal ScrollView. I’ve solved it with a set of reanimated hooks, so maybe it can be helpful for someone:

const offsetX = useSharedValue(0);

  const currentIndex = useDerivedValue(
    () => Math.round(offsetX.value / galleryWidth),
    [galleryWidth],
  );

  useAnimatedReaction(
    () => currentIndex.value,
    (index, prevIndex) => {
      if (index !== prevIndex && typeof onChangeItem === 'function') {
        runOnJS(onChangeItem)(items[index]);
      }
    },
    [currentIndex, items, onChangeItem],
  );

  const onScroll = useAnimatedScrollHandler({
    onScroll: event => {
      offsetX.value = event.contentOffset.x;
    },
  });

Issue validator

The issue is valid!