androidreact-nativeoptimizationreact-native-flatlist

MASSIVE memory spikes in React Native when images are rendered in a list


I am going to post a description of the problem we face and steps we tried to take it to address it, as well as screenshots from profiler.

For some context, we are small startup where we have a list of snaps/moments displayed in chronological order in 2 columns. Naturally, a user can view hundreds of such images as they scroll. The issue is that our memory spikes a lot, and we can't figure out how to control it.

We use the library here as we found it to be better than its alternatives for following reasons:

  1. Image from React Native: Terrible on Android and doesn't load the images at all
  2. react-native-fast-image: Library is not maintained at all
  3. react-native-turbo-image: Sluggish on Android as we have to disable hardware bitmaps. If we don't, then it crashes due to react-navigation

We face the spikes with all libraries, except for Image but that's because this library doesn't load the images in the first places.

For rendering, we use Flatlist and have utilized all props, but for now the following props are being used to control memory:

  1. initialNumToRender = 4
  2. removeClippedSubviews = true
  3. maxToRenderPerBatch = 4
  4. windowSize = 4

We used the onEndReached prop to trigger a fetch for next posts. This fetch consists of making firebase calls and updating the list that we pass in the data field for flatlist. Inside each item, images are fetched with CDNs and we have verified that all images are < 5MB and ~95% are <= 1MB.

Flashlist doesn't help with memory and also the images flicker a lot when scrolled fast, and that is unacceptable.

We have tried clearing the cache of FasterImage with clearCache, and have tried calling the GC manually but neither approach works.

The app initially starts off with ~500-600MB on Android, and rises up to 2GB when scrolled fast enough. Capturing the heap dump reveals that majority of the Native Memory is occupied by Bitmap (android.graphics). I have attached a screenshot of the same. Heap dump sorted by Native Memory

Distribution of memory usage

Is there a way to reduce the memory footprint by manually calling some function? Has anyone faced this issue in React Native? Any help would be appreciated. Also, we face this in iOS too but I am the Android dev and don't have the logs for iOS. For iPhones, the maximum spike is ~850MB.

Our flatlist code looks like this:

import { FasterImageView } from '@candlefinance/faster-image'

<FlatList
  onEndReached={() => fetch(true)}
  onEndReachedThreshold={0.5}
  ref={scrollRef}
  style={{ flex: 1, overflow: 'hidden' }}
  initialNumToRender={4}
  windowSize={4}
  maxToRenderPerBatch={4}
  removeClippedSubviews={true}
  data={Object.keys(postsKey)}
  keyExtractor={item => item}
  renderItem={({ item, index }) => (
    <View key={index} style={{ overflow: 'hidden', marginTop: 20 }}>
      <Text style={{ ...getFontFamily('Montserrat', 600), fontSize: 17, lineHeight: 18, color: '#000', marginBottom: 10, marginLeft: 15 }}>{item}</Text>
      <FlatList
        scrollEnabled={false}
        initialNumToRender={4}
        windowSize={4}
        maxToRenderPerBatch={4}
        removeClippedSubviews={true}
        data={postsKey[item]}
        renderItem={({ item, i }) => <PostCard key={i} post={item} index={i} posts={allPosts} setDisplayIndex={setDisplayIndex} userStreaks={userStreaks} userProfilePictures={userProfilePictures} />}
        keyExtractor={item => item.id}
        numColumns={2}
        showsHorizontalScrollIndicator={false}
      />
    </View>
  )}
  refreshControl={
    <RefreshControl refreshing={refreshing} onRefresh={() => fetch(false)} tintColor={DEFAULT_COLOR} />
  }
/>

Solution

  • I had this problem with rendering multiple charts in a flatlist.

    Shopify solved the problem with this alternative that replaces react-svg with skia. Skia is a much more efficient rendering engine.

    Since you have a flatlist inside a flatlist I'd suggest giving it ago.

    https://shopify.github.io/flash-list/

    it's also possible you're just using a development build and heavy renders don't perform well outside of the optimizations applied in a production build.