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

Large Flatlist with PanGestureHandler and useAnimatedReaction - ANR

Open Hajan39 opened this issue 2 years ago • 1 comments

Description

A react-native application must have a list of orders where each order can have fast actions. Everything is working smoothly when there are less than 100 items. Currently, there are 164 items, and when the user is scrolling fast, it renders slow (that is OK), but sometimes the app crashes. Every list item should close when another is opened.

Expected behavior

Not crash

Actual behavior & steps to reproduce

When the app loads, you will scroll up and down fast. On random occasions, the app freezes, and in a few seconds, the ANR dialog appears. This ANR will appear in Crashlytics

#30 pc 0x1768f8 libreanimated.so at com.swmansion.reanimated.NativeProxy$EventHandler.receiveEvent(Native method) at com.swmansion.reanimated.NativeProxy$EventHandler.receiveEvent(NativeProxy.java:65) at com.swmansion.gesturehandler.react.RNGestureHandlerEvent.dispatch(RNGestureHandlerEvent.kt:40) at com.swmansion.reanimated.NodesManager.handleEvent(NodesManager.java:517) at com.swmansion.reanimated.NodesManager.onEventDispatch(NodesManager.java:491) at com.facebook.react.uimanager.events.EventDispatcherImpl.dispatchEvent(EventDispatcherImpl.java:116) at com.swmansion.gesturehandler.ReactContextExtensionsKt.dispatchEvent(ReactContextExtensions.kt:9) at com.swmansion.gesturehandler.react.RNGestureHandlerModule.sendEventForDirectEvent(RNGestureHandlerModule.kt:613) at com.swmansion.gesturehandler.react.RNGestureHandlerModule.sendEventForReanimated(RNGestureHandlerModule.kt:599) at com.swmansion.gesturehandler.react.RNGestureHandlerModule.onHandlerUpdate(RNGestureHandlerModule.kt:519) at com.swmansion.gesturehandler.react.RNGestureHandlerModule.access$onHandlerUpdate(RNGestureHandlerModule.kt:22) at com.swmansion.gesturehandler.react.RNGestureHandlerModule$eventListener$1.onHandlerUpdate(RNGestureHandlerModule.kt:325) at com.swmansion.gesturehandler.GestureHandler.dispatchHandlerUpdate(GestureHandler.kt:87) at com.swmansion.gesturehandler.GestureHandlerOrchestrator.deliverEventToGestureHandler(GestureHandlerOrchestrator.kt:282) at com.swmansion.gesturehandler.GestureHandlerOrchestrator.deliverEventToGestureHandlers(GestureHandlerOrchestrator.kt:215) at com.swmansion.gesturehandler.GestureHandlerOrchestrator.onTouchEvent(GestureHandlerOrchestrator.kt:44) at com.swmansion.gesturehandler.react.RNGestureHandlerRootHelper.dispatchTouchEvent(RNGestureHandlerRootHelper.kt:95) at com.swmansion.gesturehandler.react.RNGestureHandlerRootView.dispatchTouchEvent(RNGestureHandlerRootView.kt:37) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3923) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3597) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3923) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3597) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3923) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3597) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3923) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3597) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3923) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3597) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3923) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3597) at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:975) at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1962) at android.app.Activity.dispatchTouchEvent(Activity.java:4257) at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69) at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:933) at android.view.View.dispatchPointerEvent(View.java:15335) at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:7681) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:7454) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6789) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6846) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6812) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:7010) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6820) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:7067) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6793) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6846) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6812) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6820) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6793) at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:10241) at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:10089) at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:10045) at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:10373) at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:259) at android.os.MessageQueue.nativePollOnce(Native method) at android.os.MessageQueue.next(MessageQueue.java:335) at android.os.Looper.loopOnce(Looper.java:186) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8633) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1133)

Snack or minimal code example

Implementation of the list:

import {FlatList, RefreshControl, View} from 'react-native';
import {useSharedValue} from 'react-native-reanimated';

const openedSlider = useSharedValue<string>('');
  const renderOrderListItem = useCallback(
    ({item, index}: {item: OrderResponseObject; index: number}) => {
      return (
        <FastActionListItem
          id={item.orderNumber}
          openedSlider={openedSlider}
          onItemPress={onFastActionItemPress(item)}>
          <View>
             <Text>{item.orderNumber}</Text>
          <View>
        </FastActionListItem>
      );
    },
    [
      getCorrectName,
      onFastActionItemPress,
      onFastActionPress,
      openedSlider,
      ordersDistances,
      showDistance,
    ],
  );
return (<View>
   <FlatList<OrderResponseObject>
        testID="flat-list"
        keyExtractor={orderNumberKey}
        keyboardShouldPersistTaps="always"
        contentContainerStyle={styles.orderContainer}
        ref={ref}
        data={sortedData}
        ItemSeparatorComponent={itemSeparator}
        ListEmptyComponent={noOrdersPage}
        refreshing={isLoading}
        onRefresh={onRefresh}
        refreshControl={refreshControlItem}
        renderItem={renderOrderListItem}
      />
</View>)

Implementation of ListItem:

const FastActionListItem: React.FC<FastActionListItemProps> = React.memo(({children, openedSlider, id, onItemPress: handleItemPress}) => {
const translateX = useSharedValue<number>(0);
const {swipeSettings} = useApi();

useAnimatedReaction(
  () => openedSlider.value,
  (value, prev) => {
    if (id !== value && prev === id) {
      translateX.value = withTiming(0, {
        duration: 300,
        easing: Easing.bezier(0.33, 0.01, 0, 1),
      });
    }
  },
  [],
);

const snapPointsX = [
  -(
    constants.others.fastActionWidth
  ),
  0,
  constants.others.fastActionWidth,
];

const onItemPress = React.useCallback(
  (item: FastActionButtonTypePredefined | FastActionButtonTypeTag) => {
    translateX.value = withTiming(0, {easing: Easing.bezier(0.33, 0.01, 0, 1)});
    handleItemPress?.(item);
  },
  [handleItemPress, translateX],
);

const onGestureEvent = useAnimatedGestureHandler<
  PanGestureHandlerGestureEvent,
  {x: number; y: number}
>({
  onStart: (_event, ctx) => {
    ctx.x = translateX.value;
    openedSlider.value = id;
  },
  onActive: ({translationX}, ctx) => {
    translateX.value = Math.min(Math.max(ctx.x + translationX, snapPointsX[0]), snapPointsX[2]);
  },
  onEnd: ({translationX}, ctx) => {
    const snapPointX = snapPoint(ctx.x + translationX, 10, snapPointsX);
    translateX.value = withTiming(snapPointX, {
      duration: 300,
      easing: Easing.bezier(0.33, 0.01, 0, 1),
    });
  },
});
const style = useAnimatedStyle(() => ({
  backgroundColor: constants.defaultColors.light,
  alignItems: 'center',
  transform: [{translateX: translateX.value}],
}));

return (
  <View style={styles.width}>
    <FastActionButton
      left
      index={0}
      type={swipeSettings?.left || FastActionButtonTypePredefined.callClient}
      onPress={onItemPress}
    />
    
    <FastActionButton
      left={false}
      index={0}
      type={swipeSettings?.right || FastActionButtonTypePredefined.takePicture}
      onPress={onItemPress}
    />

    <PanGestureHandler activeOffsetX={[-50, 50]} onGestureEvent={onGestureEvent}>
      <Animated.View style={style}>
        {children}</Pressable>
      </Animated.View>
    </PanGestureHandler>
  </View>
  );
 },
);

Package versions

name version
react-native 0.68.2
react-native-reanimated 2.9.1
NodeJS v16.15.0
Java openjdk version "11.0.11"
Gradle 7.4.2

Affected platforms

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

Hajan39 avatar Jul 18 '22 08:07 Hajan39

Hello @Hajan39! Thanks for submitting the issue.

Unfortunately, it looks like the provided code snippets are not complete and also contain syntax errors, which makes it impossible for me to reproduce the crash.

Can you please prepare a minimal reproducible example and share it preferably as a link to GitHub repo?

tomekzaw avatar Jul 26 '22 11:07 tomekzaw