facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
118.97k stars 24.31k forks source link

[FlatList] FlatList and VirtualizedList Scroll performance is laggy after 30+ rows . #13413

Closed PARAGJYOTI closed 2 months ago

PARAGJYOTI commented 7 years ago

Description

Flatlist or VirtualizedList Scroll lags horribly after 30-40 rows . I am fetching the same data on onEndReached . Upto 30 rows its looks fine , but after that scrolling drops several frames . When I disable virtualization , scroll becomes normal but but responsiveness goes away . I tried disabling virtualizing on scrolling velocity this way .

  isVirtualizationTrue(e){
  var dOffset=(e.nativeEvent.contentOffset.y- this.state.lastOffset)
  var dt=(e.timeStamp-this.state.lastTimeStamp) 
  var velocity = dOffset/dt

  var isInstant=velocity-this.state.lastVelocity>.01

  if(velocity<1 && !isInstant){

      return false
  }
  if(velocity>1){
      return true
  }
  if(velocity <.25){
      return  true
  }

}
But again , there's problem for the unmounted Component that are removed from the views , which takes long time to show up again .

Is there any way to improve the scroll performance ?

Here's my sample code

  <FlatList

    shouldItemUpdate={(props,nextProps)=>
       { 
         return props.item!==nextProps.item

}  }

   onEndReached={this.onRefresh.bind(this)}
   onEndReachedThreshold={200}

    onRefresh={this.onRefresh.bind(this)}
    refreshing={this.props.Feed.isFetching }
    data={this.state.items} 
    renderItem={this.renderItem.bind(this)}  />

My data is sort of large and nested object by the way . And data contains High quality Images . Per row there's two Items . But I implemented it without using numColums for testing with Virtualizedlist instead of Flatlist , but same result . Is it due to the High quality Image or I am doing something wrong ?

Additional Information

shanakaf commented 6 years ago

You can fix the issue by retrieving the data to Flatlist using the pagination technique in you backend. Please refer this video https://www.youtube.com/watch?v=rY0braBBlgw

sitompul commented 6 years ago

@shanakaf that's not the problem here.

dancherb commented 6 years ago

got excited about legacyImplementation but getting the error:

TypeError: Requested keys of a value that is not an object.

This error is located at: in MetroListView (at FlatList.js:632) in FlatList (at NotificationScreen.js:97) in RCTView (at View.js:78) in View (at NotificationScreen.js:92) in Screen (created by Connect(Screen)) in Connect(Screen) (at SceneView.js:17) in SceneView (at ResourceSavingSceneView.js:55) in RCTView (at View.js:78) in View (at ResourceSavingSceneView.js:48) in RCTView (at View.js:78) in View (at ResourceSavingSceneView.js:39) in ResourceSavingSceneView (at TabView.js:35) in RCTView (at View.js:78) in View (created by ViewPagerAndroid) in AndroidViewPager (at ViewPagerAndroid.android.js:238) in ViewPagerAndroid (at TabViewPagerAndroid.js:127) in TabViewPagerAndroid (at TabViewAnimated.js:71) in RCTView (at View.js:78) in View (at TabViewAnimated.js:194) in TabViewAnimated (at TabView.js:192) in TabView (at withCachedChildNavigation.js:69) in withCachedChildNavigation(TabView) (at TabNavigator.js:34) in Unknown (at createNavigator.js:13) in Navigator (at createNavigationContainer.js:226) in NavigationContainer (at SceneView.js:17) in SceneView (at DrawerScreen.js:21) in DrawerScreen (at withCachedChildNavigation.js:69) ...

wenkangzhou commented 6 years ago

no one works for me,legacyImplementation cause ListHeaderComponentListFooterComponentnot work;disableVirtualizationis deprecated.

filipemerker commented 6 years ago

As my current project rely a lot on FlatLists, I took the time to create this article gathering every technique I could find around the internet that should enhance a FlatList.

Any contribution would be really helpful!

diwakarshuklar commented 6 years ago

@bamne123's solution worked for me. It is running great.

leandrodragani commented 6 years ago

This is still happening, in android a FlatList with a few images consumes a lot of memory and is not beign released, iOS works ok

lancygoyal commented 6 years ago

Hi Folks, Please have a look on demo for listview performance in React native with images(5mb each).

React Native Experiment

Used - RecyclerListView & FastImage

Ligia-Andreica commented 6 years ago

For me, adding removeClippedSubviews and choosing a relevant keyExtractor did the trick. (Eg keyExtractor = {(item) => item} for a list of unique strings).

ex3ndr commented 6 years ago

In my case the problem was in using contentContainerStyle with paddings and this causes FlatList to go crazy. Replace paddings with header/footers solves a problem.

UPD: Header and footer doesn't solve a problem, but adding them as part of my data (first and last element) works.

kelset commented 6 years ago

using contentContainerStyle with paddings and this causes FlatList to go crazy

uhm that seems a faily "focused" issue, would you mind opening an issue dedicated to that?

rendomnet commented 6 years ago

Same problem here. 2 items in FlatList with images of 200kb and crash on release build.

getDanArias commented 6 years ago

My use case:

I was able to make it super smooth and fast on an Moto G6 Android phone by making the following changes:

Performance boosters

I turned Perf Monitor on. JS Dev Mode off. JS Minify on.

Before the changes, on the Moto G6...

After loading the app:

UI: Oscillated around 2 - 60 fps. 130 dropped so far. 6 stutters so far. JS: Oscillated between 8 - 59 fps.

While scrolling fast from top to bottom to top twice. These were the results:

UI: Oscillated around 2-58 fps. 519 dropped so far. 26 stutters so far. JS: Oscillated between 3 - 57 fps.

It got laggier as I got to the bottom of the list and even laggier as I scrolled back up.

After applying the performance boosters to the FlatList, on the Moto G6...

After loading the app:

UI: Oscillated around 47- 59 fps. 29 dropped so far. 3 stutters so far. JS: Oscillated between 43 - 59 fps.

While scrolling fast from top to bottom to top twice. These were the results:

UI: Stable around 59 fps. 40 dropped so far. 3 stutters so far. JS: Stable around 59 fps.

The performance vastly improved!

When using Image instead of FastImage there is a bit of fast fading in when scrolling fast. Images at the very bottom will have its placeholder image and then load. With FastImage the fading is gone, images load instantaneously and stay loaded in subsequent scrolls. I think FastImage overcomes the tradeoff of gradual loading brought up by removeClippedSubviews={true}.

Will the performance be better without images?

Removing the FastImage element, on the Moto G6...

After loading the app:

UI: Oscillated fast around 3- 59 fps. 55 dropped so far. 3 stutters so far. JS: Oscillated between 4 - 59 fps.

While scrolling fast from top to bottom to top twice. These were the results:

UI: Stable around 59 fps. 68 dropped so far. 3 stutters so far. JS: Stable around 59 fps.

There's not much difference. It was better when having FastImage.

I am very pleased on how performant the list became! I am sure that more can be done to improve it. But so far, without having to implement infinite scrolling techniques, the properties of FlatList alone where fantastic for this use case.

Edit: I apologize. While scrolling with the performance boosters, the JS fps oscillate between 35 - 59 fps on the first top to bottom scroll using FastImage. Subsequent scrolling oscillates between 49 - 59 fps.

leandrodragani commented 6 years ago

Better than FastImage, use resizeMethod="resize" on your image to configure Fresco in android, it gives you a cpu overhead but ram goes much lower

ithustle commented 5 years ago

@kelset, I see you as batman, superman, The Avengers of the React Native... Any solution? I'm diying with this. Please, save me.

Using RN 0.57.4 React 16.6.1

If I remove the Image, Flatlist works fine... otherwise, my app go away

kelset commented 5 years ago

I suggest that you try the solutions listed above, such as:

We are working hard on improving Android perfs from the ground up (a part of this should already land in 0.58) - but also a lot of work should be doable by changing your code. I recommend this article to better understand what in your app may be creating a ton of overhead.

amirpervaiz086 commented 5 years ago

@ithustle any workaround yet? I think we need to dig it deeper further simply using components as it is wont work. I have a bit complex UI i.e nested flatList. Its like each cell has its own horizontal flatList. it works great on iPhone X but breaking on low end devices due to high memory.

janiokq commented 5 years ago

If it's a performance problem caused by listview You can try this library react-native-nlist

shanakaf commented 5 years ago

https://github.com/Flipkart/recyclerlistview solved the performance issue for me

igsm commented 5 years ago

I needed to render 300 checkbox-list-items. I naively tried to set initialNumToRender={20}and maxToRenderPerBatch={280}. There was a slight delay (~700ms ± 100) in the beginning, but then surprisingly I got a complete list in one maxToRenderPerBatch go and could interact with it smoothly.

P.S. Given that the (reusable) ListItem component is quite complex, as it considers several scenarios for rendering.

joshuapinter commented 5 years ago

Make sure you test that on slower devices - not just an emulator - to get a better indication of real world performance. On Jan 14, 2019, 3:33 PM -0700, Igor Smirnov notifications@github.com, wrote:

I needed to render 300 checkbox-list-items. I naively tried to set initialNumToRender={20} and maxToRenderPerBatch={280}. There was a slight delay (~700ms ± 100) in the beginning, but then surprisingly I got a complete list in one maxToRenderPerBatch go. — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

igsm commented 5 years ago

Also these configuration helped to achieve better performance. initialNumToRender={30} maxToRenderPerBatch={30} windowSize={101}. I don't know, at the end it felt that it is about the best combination of the properties, perhaps, it is a quick fix for someone.

doryanB commented 5 years ago

Hi, I have the same problem on my react-native V0.54 app... a FlatList with images (more than 300)... the app work fine with IOS, but crash with Android... I saw that the RAM was exploding (from ~120MB to +300MB which lead to a crash). Even when I limit the number of cards, I go from ~120MB to ~220MB only with 30 images Tests made on simulator Adroid with 1Go Ram on Android 5.1.1 - API 22

I Tried to change component, use libs more and more complex, but nothing is working... Seems that FlatList with Images (react-native image and fast-image gives the same results) lead to a hard memory explosion on Android

I went as far as I can, someone tried to resolve this ( and perhaps succeed ?)

faxioman commented 5 years ago

Setting removeClippedSubviews={true} was working for me until the 0.58 release. With 0.59 my FlatList is lagging again on Android :(

faxioman commented 5 years ago

ops, sorry: I was checking the platform using Platform.android instead of Platform.OS === 'android' (change in rn 0.59). Anyway, removeClippedSubviews is the default for android now... and in my case does the trick.

mayconmesquita commented 5 years ago

Would be nice a native Recycler FlatList, like ListView.builder() in Flutter.

nihp commented 5 years ago

Any best solution for this in flatlist?

I am also facing the issue while scrolling the scrolling performance is not good in iOS. Smoothness is not there.

JanithaR commented 5 years ago

@nihp Give this a shot https://github.com/Flipkart/recyclerlistview

nihp commented 5 years ago

@JanithaR Any solution which is in Flatlist itself?

JanithaR commented 5 years ago

There are so many things you could do. For a start, could you let me know what you have already tried?

nihp commented 5 years ago

@JanithaR My Flatlist is like below. I have added legacyImplementation, shouldComponentUpdate an soon. Kindly refer below

Also, I can able to see a white space while scrolling a large list

  <FlatList
                          ref={(ref) => { this.flatListRef = ref; }}
                          data={Items}
                          renderItem={({ item }) => this.renderList(item)}
                          numColumns={2}
                          extraData={this.props.pagination}
                          keyExtractor={item => item.id}
                          onEndReached={this._handleMoreItems}
                          maxToRenderPerBatch={10}
                          initialNumToRender = {14}
                          shouldComponentUpdate= {this.shouldItemUpdate()}
                          onEndReachedThreshold={0.001}
                          ListFooterComponent={this._renderFooter}
                          legacyImplementation = {true}
                          bounces = {false}
                          onMomentumScrollEnd={e => this.scroll(e.nativeEvent.contentOffset)}
                        />
JanithaR commented 5 years ago

I'm not really sure why you have this, shouldComponentUpdate= {this.shouldItemUpdate()}

This hasn't solved any problems for me in the past. legacyImplementation = {true}

Instead of, renderItem={({ item }) => this.renderList(item)} write, renderItem={this.renderList}

Do what's said here. https://facebook.github.io/react-native/docs/optimizing-flatlist-configuration#list-items Try to make your list item components as light and restricted as possible.

nihp commented 5 years ago

I need to pass the item for sure.

renderList(item){
return (
  <TouchableOpacity activeOpacity = { .5 } >
        <View style={{backgroundColor: 'white', alignItems: 'center'}}>
          <FastImage style={{width: Width, height: Height, margin:6}}
            resizeMode={FastImage.resizeMode.contain}
            source={item.uri}/>
            {this.renderName(item.name)}
            {this.renderClass(item.class)}
        </View>
      </TouchableOpacity>
)}
nihp commented 4 years ago

I am also stuck with this.

xhirazi commented 4 years ago

+1

iagormoraes commented 4 years ago

+1

bhaskarGyan commented 4 years ago

@nihp , @xhirazi ,@iagorm,

Have you tried Hermes or react-native-v8? I was also facing similar issued, but in my case, react-native-v8 solved it for me.

I have also published the article regarding the same.

https://medium.com/walmartlabs/react-native-memory-profiling-jsc-vs-v8-vs-hermes-1626949a653b

shcheuk commented 4 years ago

My app memory drop from 1.3GB to 288MB after changing to react-native-v8. It can even handle 1000 + uncompress big pic in the flat list, general response become much faster, upgrade process was amazingly smooth. This is a true life savior. It should be the default engine for android. Thx bhaskarGyan!

gagangoku commented 4 years ago

@naqvitalha : After breaking my head with the jank caused by FlatList, I finally moved to recyclerlistview and am really loving it. For folks who are wondering about the scroll performance difference, here's a before and after video.

  1. FlatList sucks : https://drive.google.com/open?id=1JF8i7jVWSM3l7MU12hxWFd6glhKWpJsR

  2. RecyclerListView is great: https://drive.google.com/open?id=1JAZRYeetDbf5tvxZCnvtM6jY5WfMDEHR

There's a weird jank when scrolling with FlatList which starts happening beyond 70 items.

@naqvitalha : Please support reverse mode for chat use cases. ScaleY: -1 approach is non-intuitive, heavy on render (because of the extra transforms causing entire list to be re-rendered if a new message is added) and also doesn't float messages on top if there's only 1 or 2 messages.

Screenshot_20200313-032510

gagangoku commented 4 years ago

As requested, here is the source code for the RecyclerListView:

import {BaseScrollView, DataProvider, LayoutProvider, RecyclerListView} from 'recyclerlistview';

export class RecyclerViewNative extends React.PureComponent {
    constructor(props) {
        super(props);
        this.ref = React.createRef();

        const { data, inverted } = this.props;
        const array = inverted ? data.slice().reverse() : data;
        const dataProvider = new DataProvider((r1, r2) => {
            return r1 !== r2;
        }).cloneWithRows(array);

        this.state = {
            dataProvider,
        };

        this.layoutProvider = new LayoutProvider((i) => {
            return this.state.dataProvider.getDataForIndex(i).type;
        }, (type, dim) => {
            dim.width = WINDOW_INNER_WIDTH;
            dim.height = 100;           // TODO: Try to get this more accurate
        });
    }
    componentDidUpdate(prevProps, prevState, snapshot) {
        const oldData = prevProps.data;
        const newData = this.props.data;

        // TODO: Handle message deletes as well
        if (newData.length > oldData.length) {
            const array = this.props.inverted ? newData.slice().reverse() : newData;
            this.setState(prevState => ({
                dataProvider: prevState.dataProvider.cloneWithRows(array, 0)
            }));
        }
    }

    renderRow = (type, data) => {
        const { renderItem, inverted } = this.props;
        const elem = renderItem(data, data.idx);
        const transform = inverted ? [{ scaleY: -1 }] : [];
        return <View style={{ width: '100%', transform }}>{elem}</View>;
    };

    render() {
        const { inverted } = this.props;
        const { dataProvider } = this.state;
        const transform = inverted ? [{ scaleY: -1 }] : [];
        return (
            <View style={{ height: '100%', width: '100%', transform, paddingTop: 5 }}>
                <RecyclerListView rowRenderer={this.renderRow} ref={this.ref}
                                  forceNonDeterministicRendering={true}
                                  dataProvider={dataProvider}
                                  layoutProvider={this.layoutProvider}/>
            </View>
        );
    }
}

Older FlatList I was using:

import {FlatList, View} from 'react-native';
import cnsole from 'loglevel';

export default class ScrollableList extends React.PureComponent {
    constructor(props) {
        super(props);
        this.ref = React.createRef();
    }

    refElem = () => this.ref.current;

    keyExtractor = ({ item, index }) => index;
    renderItem = ({ item, index }) => {
        const { itemRenderFn } = this.props;
        return itemRenderFn(item, index);
    };
    onEndReached = () => {
        cnsole.info('onEndReached');
    };

    render() {
        const { style, flatListProps, inverted, data } = this.props;
        const dataProp = inverted ? data.slice().reverse() : data;
        const contentContainerStyle = inverted ? { flexGrow: 1, justifyContent: 'flex-end' } : {};
        return (
            <View style={{ ...style, display: 'flex', flexDirection: 'column' }} ref={this.ref}>
                <FlatList
                    inverted={inverted} contentContainerStyle={contentContainerStyle}
                    nestedScrollEnabled={true}
                    data={dataProp}
                    renderItem={this.renderItem}
                    keyExtractor={this.keyExtractor}
                    extraData={{}}
                    onEndReached={this.onEndReached}
                    {...flatListProps}
                />
            </View>
        );
    }
}
JanithaR commented 4 years ago

@gagangoku Looks great but not perfect. Unfortunately, as long as there's a bridge in RN we will have to be satisfied with this.

iagormoraes commented 4 years ago

The solution for this would be wrapping the native RecyclerView, Flatlist is just an abstraction of reusable cells, but it uses ScrollView under the hood.

@Edit: I did wrap the RecyclerView and the performance is ridiculously higher than Flatlist.

ghost commented 4 years ago

I dont know what to do with flatlist, more than 1000 way I used but still is not fast while scrolling even when put numColumns in horizontal mode app crash and closed.

ghost commented 4 years ago

@iagormoraes How have you used it bro

iagormoraes commented 4 years ago

@iagormoraes How have you used it bro

I've created a native component, with view manager class and created a class to wrap a RecyclerView. the list items I created in XML layout from android as was too complex to pass each element of the list item from RN to native.

ghost commented 4 years ago

@iagormoraes thank you, but I dont know, how can I create native android ios module for react-native

iagormoraes commented 4 years ago

@iagormoraes thank you, but I don't know, how can I create native android ios module for react-native

There is a tutorial on docs from RN but you can find other articles regarding it: https://reactnative.dev/docs/native-components-android

vitchell commented 4 years ago

I spent the last several days investigating this issue for myself. Wanted to quickly document the solution/workaround I found. I won't go into too much detail on how I got to to the fix that works for me, unless anyone finds this useful and wants info. Just note that I tried a whole lot of other stuff, including every solution I see in this thread.

Solution: something to the effect of this layout below. Render a piece of text at the top of the list. You might be able to use a list header, I didn't test quite that much since this has been a multi-day bug hunt. The most basic version of this which still works for me looks roughly like this:

render() {
  return (
    <View style={{ display: 'flex', flex: 1, }}>
      <Text style={{ fontSize: 0.1, }}>&nbsp;</Text>
      <View style={{ flex: 1 }}>{this.renderYourList()}</View>
    </View>
  );
}

This made a 10+ second scroll lag fully disappear - its totally normally performant for me now. It doesn't matter what the text is, I'm using a 0.1 font size &nbsp; that is colored the same as background right now in my app. But the text does have to be there - removing it or replacing the element with something else doesn't work (or at least, nothing I've found so far works). This does result in what appears to be a small margin/border on the top of the list, which works fine for me so I've not done anything more to it - I don't know if any further tweaks could effectively hide it but retain the fix.

I don't have a full understanding of why this works, I just know it worked for me. I assume it has something to do with blending, but I'm a bit too worn out to dig deeper right now. Hopefully this helps others, or helps FB address the underlying issue.

gilons commented 4 years ago

we are in 2020 and you can use the react-native flatlist prop windowSize={Number} . you can set your windowSize as large as you want depending on your performances tradeoffs .

for example for my case, I set windowSize to 201 which is equivalent to 201 view ports 100 below 100 above and the current one . With this the scroll was smooth enough . try this see

gagangoku commented 4 years ago

Try big lists Mr. Break, Flatlist is horrible for huge lists.

Regards, Gagan, CEO @HeloProtocol, +91-9008781096, LinkedIn https://www.linkedin.com/in/gagan-deep-singh87/

On Thu, 11 Jun 2020 at 00:54, Mr Break; notifications@github.com wrote:

we are in 2020 and you can use the react-native flatlist prop windowSize={Number} . you can set your window size case large as you want depending on your performances tradeoffs

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/facebook/react-native/issues/13413#issuecomment-642209248, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEFA4OGHNLYFA4YL7BCJXTRV7MX5ANCNFSM4DHBTFLQ .