software-mansion / react-native-gesture-handler

Declarative API exposing platform native touch and gesture system to React Native.
https://docs.swmansion.com/react-native-gesture-handler/
MIT License
6.13k stars 982 forks source link

Swipeable significantly decrease FlashList/FlatList performance #3141

Closed olivertylsar closed 2 weeks ago

olivertylsar commented 1 month ago

Description

I noticed in my app that wrapping a render item in a Swipeable component (whether using the Legacy or Reanimated version) leads to a significant drop in FPS and a general decrease in the performance of FlashList/FlatList.

I tested this in your example code and was able to reproduce the issue.

https://github.com/user-attachments/assets/d4cd9724-8a02-474a-9b92-961cd8c6549f

https://github.com/user-attachments/assets/cca59a7b-edf7-41dd-bc94-97f7c170de07

Steps to reproduce

In the example app, install @shopify/flash-list (alternative code with FlatList is commented). Replace code in swipeableReanimation/index.tsx with code below and open the screen.

It happens only on Android for me, with both JS Debug mode on and off.

import { Text, StyleSheet, View, Alert, FlatList } from 'react-native';

import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
import {
  FlashList,
  ListRenderItem,
  useBenchmark,
  useFlatListBenchmark,
} from '@shopify/flash-list';

function RightAction() {
  return <Text style={styles.rightAction}>Text</Text>;
}

type Item = {
  id: string;
  title: string;
};

const generateItems = (count: number): Item[] => {
  return Array.from({ length: count }, () => {
    const randomNumber = Math.random().toString(36).substring(2, 8);
    return {
      id: randomNumber,
      title: `Title ${randomNumber}`,
    };
  });
};

const data = generateItems(200);

const _RenderItemView = (item: Item) => (
  <View style={styles.swipeable}>
    <Text>{item.title}</Text>
  </View>
);

const RenderItemViewMemoed = memo(_RenderItemView);

export default function Example() {
  const renderItem: ListRenderItem<Item> = ({ item }) => {
    return (
      <ReanimatedSwipeable renderRightActions={RightAction}>
        <RenderItemViewMemoed {...item} />
      </ReanimatedSwipeable>
    );
  };

  const flashListRef = useRef<FlashList<Item> | null>(null);
  useBenchmark(flashListRef, (callback) => {
    Alert.alert('result', callback.formattedString);
  });

  return (
    <FlashList
      ref={flashListRef}
      data={data}
      renderItem={renderItem}
      estimatedItemSize={50}
      keyExtractor={(item) => item.id}
    />
  );

  // Alternatively measure performance with FlatList
  // const flatList = useRef<FlatList<Item> | null>(null);
  // useFlatListBenchmark(
  //   flatList,
  //   (callback) => {
  //     Alert.alert('result', callback.formattedString);
  //   },
  //   {
  //     targetOffset: 10000,
  //   }
  // );
  //
  // return (
  //   <FlatList
  //     ref={flatList}
  //     data={data}
  //     renderItem={renderItem}
  //     keyExtractor={(item) => item.id}
  //   />
  // );
}

const styles = StyleSheet.create({
  leftAction: { width: 50, height: 50, backgroundColor: 'crimson' },
  rightAction: { width: 50, height: 50, backgroundColor: 'purple' },
  separator: {
    width: '100%',
    borderTopWidth: 1,
  },
  swipeable: {
    height: 50,
    backgroundColor: 'papayawhip',
    alignItems: 'center',
  },
});

Snack or a link to a repository

https://snack.expo.io/

Gesture Handler version

2.20.0

React Native version

0.73.8

Platforms

Android

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

Paper (Old Architecture)

Build type

Release mode

Device

Real device

Device model

Samsung S21

Acknowledgements

Yes

github-actions[bot] commented 1 month ago

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

efstathiosntonas commented 1 month ago

This smells like onLayout events pollution, maybe measure can come into play?

latekvo commented 3 weeks ago

Hi @olivertylsar,

Could you please let me know if this PR works well for you?

This smells like onLayout events pollution, maybe measure can come into play?

@efstathiosntonas thank you for the insight. The issues were mostly caused by lack of memoization, excessive JS-UI synchronisations and SharedValues having their value props observed in the dependency arrays of useMemo hooks. I could be wrong, but as far as I know onLayout had nothing to do with these issues.

olivertylsar commented 3 weeks ago

Hi @latekvo,

I tried the FlashList example in the PR's branch and the results are much better now. The FPS still drops but only to something like 68 on average. I can only see a little of the blank spaces, the improvement is significant. Thanks for looking into it. 👍