react-native-reanimated icon indicating copy to clipboard operation
react-native-reanimated copied to clipboard

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

Open alexco2 opened this issue 2 years ago • 28 comments

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

  • [x] Android
  • [ ] iOS
  • [ ] Web

alexco2 avatar Dec 13 '21 00:12 alexco2

Issue validator

The issue is valid!

github-actions[bot] avatar Dec 13 '21 00:12 github-actions[bot]

Just as a small update. Tried it with version 2.3.1. The bug is still there

alexco2 avatar Dec 15 '21 10:12 alexco2

@piaskowyk Did you have a chance to test my snippet? Can I support you somehow? Others seem to have the problem as well: https://github.com/PedroBern/react-native-collapsible-tab-view/issues/221#issuecomment-997474612

alexco2 avatar Dec 21 '21 10:12 alexco2

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

Omelyan avatar Dec 26 '21 15:12 Omelyan

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}  />

fhugoduarte avatar Dec 27 '21 19:12 fhugoduarte

@fhugoduarte Are you sure you are running rea 2.3.0? Could you try my code snippet?

alexco2 avatar Dec 27 '21 21:12 alexco2

@fhugoduarte Are you sure you are running rea 2.3.0? Could you try my code snippet?

From my package.json: "react-native-reanimated": "~2.3.1", "react-native": "0.64.3",

fhugoduarte avatar Dec 27 '21 22:12 fhugoduarte

@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}
/>

BabuStack avatar Dec 28 '21 08:12 BabuStack

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;
    },
  });

danilichev avatar Jan 26 '22 20:01 danilichev

Just as an information. The issue still persists with version 2.5.0. @piaskowyk do you have any updates on the progress of this bug?

alexco2 avatar Mar 22 '22 19:03 alexco2

I experience a similar issue on iOS with version 2.5.0. onEndDrag / onMomentumEnd are called on the simulator, but they do not get called when the app is deployed to a device.

jp-23 avatar Mar 30 '22 01:03 jp-23

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.

andreialecu avatar Apr 04 '22 17:04 andreialecu

I can confirm this issues. Sad this isn't resolved since Dec :( Anything we can do to help?

hirbod avatar Apr 06 '22 21:04 hirbod

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

alexco2 avatar Apr 06 '22 22:04 alexco2

Just leaving a comment to bring more attention to this 🙏 It's also a blocker for us to upgrade to RN 0.68

Spejbl avatar Apr 20 '22 12:04 Spejbl

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

andreialecu avatar Apr 20 '22 12:04 andreialecu

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 :)

alexco2 avatar Apr 25 '22 13:04 alexco2

@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.

hirbod avatar May 04 '22 02:05 hirbod

@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 really agree. I think it is not to much to ask for at least a small update so the community knows how to handle this issue. Also the amount of reactions clearly show a big interest in this issue. Sorry for pinging all of you, but @piaskowyk does not seem to be responsible for this issue anymore.

@kmagiera @Szymon20000 @jkadamczyk

alexco2 avatar May 04 '22 08:05 alexco2

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.

andreialecu avatar May 04 '22 08:05 andreialecu

@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}
/>

@andreialecu So basically what @BabuStack found out, right? At least for @danilichev this workaround did not help. I will try this workaround later today and give you feedback. But apparently this is a somewhat hacky workaround.

alexco2 avatar May 04 '22 09:05 alexco2

Indeed, yes, I missed that comment. I didn't really look into this until today so it seems I stumbled upon the same fix.

It does seem to fix the repro, and based on the code in react-native-collapsible-tab-view that uses onMomentumEnd it should be good enough even if it's called multiple times.

I'm working on testing it myself in a real app soon.

andreialecu avatar May 04 '22 09:05 andreialecu

@andreialecu if you release a patch for react-native-collapsible-tab-view I can test it as well in my current production app. I hope this issue gets resolved anyway soon so we don't need any workarounds :)

alexco2 avatar May 04 '22 10:05 alexco2

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.

andreialecu avatar May 04 '22 10:05 andreialecu

@andreialecu Can you still navigate by pressing the tabs when the header is not fully collapsed or extended (e.g. extended 50%)

alexco2 avatar May 04 '22 14:05 alexco2

@andreialecu even with the workaround, there are still issues. Scrollview for example does not snap anymore and navigating between tabs by clicking the header does not work as well.

alexco2 avatar May 07 '22 21:05 alexco2

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. 🙂

andreialecu avatar May 07 '22 22:05 andreialecu

This is actually not reanimated related problem, it's that RN on Android doesn't sent momentum events by default:

https://github.com/facebook/react-native/blob/0e06185f43763f2e8daecbd4dd5dd53917a795ff/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java#L138-L149

One can turn it on either with https://github.com/facebook/react-native/blob/ef6ab3f5cad968d7b2c9127d834429b0f4e1b2cf/Libraries/Components/ScrollView/ScrollView.js#L1741-L1744 or https://github.com/facebook/react-native/blob/ef6ab3f5cad968d7b2c9127d834429b0f4e1b2cf/Libraries/Components/ScrollView/ScrollView.js#L385-L391

savelichalex avatar Jul 07 '22 16:07 savelichalex

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

henrymoulton avatar Aug 18 '22 11:08 henrymoulton

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?

tomekzaw avatar Jan 13 '23 13:01 tomekzaw