react-native-reanimated
react-native-reanimated copied to clipboard
Large Flatlist with PanGestureHandler and useAnimatedReaction - ANR
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
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?