From 4bf7c4ba6692fab6a1b0d8711b4f7ae9cc8a6dae Mon Sep 17 00:00:00 2001 From: An Nguyen Date: Mon, 10 Jul 2023 11:05:04 -0500 Subject: [PATCH] CP-5865: Fix iOS performance regression with BigList (#746) --- app/components/BigList.tsx | 7 -- app/components/FlashList.tsx | 68 +++++++++++++++++++ .../shared/ActivityList/Transactions.tsx | 30 ++++++-- .../watchlist/components/WatchList.tsx | 18 ++++- ios/Podfile.lock | 6 ++ package.json | 3 +- yarn.lock | 28 +++++++- 7 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 app/components/FlashList.tsx diff --git a/app/components/BigList.tsx b/app/components/BigList.tsx index 3a77510682..8ac8624498 100644 --- a/app/components/BigList.tsx +++ b/app/components/BigList.tsx @@ -6,15 +6,10 @@ import { StyleProp, ViewStyle } from 'react-native' -import { - DragEndParams, - DraggableRenderItem -} from 'components/draggableList/types' interface BigListProps { data: TItem[] renderItem: (item: ListRenderItemInfo) => React.ReactElement - draggableListItem?: DraggableRenderItem ListEmptyComponent?: React.ComponentType | React.ReactElement refreshing?: boolean onRefresh?: () => void @@ -24,8 +19,6 @@ interface BigListProps { onEndReachedThreshold?: number refreshControl?: React.ReactElement estimatedItemSize?: number - isDraggable?: boolean - onDragEnd?: (params: DragEndParams) => void } /** diff --git a/app/components/FlashList.tsx b/app/components/FlashList.tsx new file mode 100644 index 0000000000..0d07799c83 --- /dev/null +++ b/app/components/FlashList.tsx @@ -0,0 +1,68 @@ +import { FlashList as ShopifyFlashList } from '@shopify/flash-list' +import React from 'react' +import { + ContentStyle, + ListRenderItem as FlashListRenderItem +} from '@shopify/flash-list/dist/FlashListProps' +import { RefreshControlProps } from 'react-native' + +interface AvaListProps { + data: TItem[] + renderItem: FlashListRenderItem + ItemSeparatorComponent?: React.ComponentType + ListEmptyComponent?: React.ComponentType | React.ReactElement + refreshing?: boolean + onRefresh?: () => void + keyExtractor: (item: TItem) => string + extraData?: unknown + contentContainerStyle?: ContentStyle + onEndReached?: () => void + onEndReachedThreshold?: number + refreshControl?: React.ReactElement | undefined + getItemType?: (item: TItem, index: number, extraData?: unknown) => string + estimatedItemSize?: number +} + +/** + * Performant list for large data set. This offer better performance than BigList. + * + * Warning: Do not use this component on Android as it causes intermittent crash + */ +const FlashList = ({ + data, + renderItem, + ItemSeparatorComponent, + ListEmptyComponent, + refreshing, + onRefresh, + keyExtractor, + extraData, + contentContainerStyle, + onEndReached, + onEndReachedThreshold, + refreshControl, + getItemType, + estimatedItemSize +}: AvaListProps) => { + return ( + + ) +} + +export default FlashList diff --git a/app/screens/shared/ActivityList/Transactions.tsx b/app/screens/shared/ActivityList/Transactions.tsx index d95d8e3f41..0fa44ac9be 100644 --- a/app/screens/shared/ActivityList/Transactions.tsx +++ b/app/screens/shared/ActivityList/Transactions.tsx @@ -4,8 +4,8 @@ import { Dimensions, StyleSheet, View, - ListRenderItemInfo, - FlatList + FlatList, + Platform } from 'react-native' import AvaText from 'components/AvaText' import ActivityListItem from 'screens/activity/ActivityListItem' @@ -27,6 +27,7 @@ import { BridgeTransaction } from '@avalabs/bridge-sdk' import { UI, useIsUIDisabled } from 'hooks/useIsUIDisabled' import { RefreshControl } from 'components/RefreshControl' import { usePostCapture } from 'hooks/usePosthogCapture' +import FlashList from 'components/FlashList' const SCREEN_WIDTH = Dimensions.get('window').width const BOTTOM_PADDING = SCREEN_WIDTH * 0.3 @@ -137,7 +138,7 @@ const Transactions = ({ ) } - const renderItem = ({ item }: ListRenderItemInfo) => { + const renderItem = (item: Item) => { // render section header if (typeof item === 'string') { return renderSectionHeader(item) @@ -186,10 +187,31 @@ const Transactions = ({ } const renderTransactions = () => { + if (Platform.OS === 'ios') { + return ( + renderItem(item.item)} + keyExtractor={keyExtractor} + contentContainerStyle={styles.contentContainer} + onEndReached={onEndReached} + onEndReachedThreshold={0.5} + ListEmptyComponent={TransactionsZeroState} + refreshControl={ + + } + getItemType={(item: Item) => { + return typeof item === 'string' ? 'sectionHeader' : 'row' + }} + estimatedItemSize={71} + /> + ) + } + return ( renderItem(item.item)} keyExtractor={keyExtractor} contentContainerStyle={styles.contentContainer} onEndReached={onEndReached} diff --git a/app/screens/watchlist/components/WatchList.tsx b/app/screens/watchlist/components/WatchList.tsx index 8cd84809fe..8c77b1c4b3 100644 --- a/app/screens/watchlist/components/WatchList.tsx +++ b/app/screens/watchlist/components/WatchList.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { StyleSheet, View } from 'react-native' +import { Platform, StyleSheet, View } from 'react-native' import WatchListItem from 'screens/watchlist/components/WatchListItem' import { useNavigation } from '@react-navigation/native' import AppNavigation from 'navigation/AppNavigation' @@ -21,6 +21,7 @@ import { import { DragEndParams } from 'components/draggableList/types' import DraggableList from 'components/draggableList/DraggableList' import BigList from 'components/BigList' +import FlashList from 'components/FlashList' import { WatchlistFilter } from '../types' const getDisplayValue = ( @@ -113,9 +114,22 @@ const WatchList: React.FC = ({ ) } + if (Platform.OS === 'ios') { + return ( + renderItem(item.item, item.index)} + ListEmptyComponent={EmptyComponent} + refreshing={false} + onRefresh={() => dispatch(onWatchlistRefresh)} + keyExtractor={keyExtractor} + estimatedItemSize={64} + /> + ) + } + return ( renderItem(item.item, item.index)} ListEmptyComponent={EmptyComponent} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d51895f739..233cccdfde 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -446,6 +446,8 @@ PODS: - React-Core - SDWebImage (~> 5.11.1) - SDWebImageWebPCoder (~> 0.8.4) + - RNFlashList (1.4.3): + - React-Core - RNFS (2.19.0): - React-Core - RNGestureHandler (2.9.0): @@ -602,6 +604,7 @@ DEPENDENCIES: - RNDeviceInfo (from `../node_modules/react-native-device-info`) - RNDominantColor (from `../node_modules/rn-dominant-color`) - RNFastImage (from `../node_modules/react-native-fast-image`) + - "RNFlashList (from `../node_modules/@shopify/flash-list`)" - RNFS (from `../node_modules/react-native-fs`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`) @@ -767,6 +770,8 @@ EXTERNAL SOURCES: :path: "../node_modules/rn-dominant-color" RNFastImage: :path: "../node_modules/react-native-fast-image" + RNFlashList: + :path: "../node_modules/@shopify/flash-list" RNFS: :path: "../node_modules/react-native-fs" RNGestureHandler: @@ -877,6 +882,7 @@ SPEC CHECKSUMS: RNDeviceInfo: 0a7c1d2532aa7691f9b9925a27e43af006db4dae RNDominantColor: 7c17c31201566a592ba4b2fbe2bb7e00df468753 RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 + RNFlashList: ade81b4e928ebd585dd492014d40fb8d0e848aab RNFS: fc610f78fdf8bfc89a9e5cc2f898519f4dba1002 RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39 RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 diff --git a/package.json b/package.json index 2e215043da..a0acb49012 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,8 @@ "vm-browserify": "0.0.4", "web3": "1.7.1", "xss": "1.0.14", - "zod": "3.20.2" + "zod": "3.20.2", + "@shopify/flash-list": "1.4.3" }, "resolutions": { "minimist": "1.2.6", diff --git a/yarn.lock b/yarn.lock index 6fa619946b..589aa5affe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5839,6 +5839,14 @@ xcode "3.0.1" yargs "^16.2.0" +"@shopify/flash-list@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@shopify/flash-list/-/flash-list-1.4.3.tgz#b7a4fe03d64f3c5ce9646859b49b9d95307f203d" + integrity sha512-jtIReAbwWzYBV0dQ6Io9wBX+pD0C4qQFMrb5/fkEvX8PYDgBl5KRYvpfr9WLLj8CV2Jsn1X0mYOsB+ysWrI/8g== + dependencies: + recyclerlistview "4.2.0" + tslib "2.4.0" + "@shopify/react-native-performance@4.1.2": version "4.1.2" resolved "https://registry.yarnpkg.com/@shopify/react-native-performance/-/react-native-performance-4.1.2.tgz#bd111af291b85ee125feaccd54c8e5f237a76ce7" @@ -17329,7 +17337,7 @@ lodash.capitalize@4.2.1: resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" integrity sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw== -lodash.debounce@^4.0.8: +lodash.debounce@4.0.8, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= @@ -20246,7 +20254,7 @@ prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@*, prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@*, prop-types@15.8.1, prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -21307,6 +21315,15 @@ recursive-readdir@^2.2.2: dependencies: minimatch "3.0.4" +recyclerlistview@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/recyclerlistview/-/recyclerlistview-4.2.0.tgz#a140149aaa470c9787a1426452651934240d69ef" + integrity sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A== + dependencies: + lodash.debounce "4.0.8" + prop-types "15.8.1" + ts-object-utils "0.0.5" + redux-flipper@2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/redux-flipper/-/redux-flipper-2.0.2.tgz#5deca22ef81e71253912fdf8a403f272db9ed27f" @@ -23560,6 +23577,11 @@ ts-node@10.9.1, ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +ts-object-utils@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/ts-object-utils/-/ts-object-utils-0.0.5.tgz#95361cdecd7e52167cfc5e634c76345e90a26077" + integrity sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA== + tsconfig-paths@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" @@ -23575,7 +23597,7 @@ tslib@1.14.1, tslib@^1.8.1, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0: +tslib@2.4.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==