react-native: Long text in inverted Flatlist causes huge performance drop on Android

Description

I recently upgraded to react 0.63.2 thanks to the latest Expo SDK 39. I immediately noticed that my most important FlatLists took a huge performance hit on Android (iOs seem unaffected).

After investigating, I found out that it happens when using inverted on a FlatList that contains items with a lot of text (see snack).

The same Flatlist, with the same data, is perfectly smooth when not inverted.

I have yet to test it in production, as the latest Expo SDK is causing trouble that stops me from building the app.

React Native version:

react-native: https://github.com/expo/react-native/archive/sdk-39.0.0.tar.gz => 0.63.2

Steps To Reproduce

Provide a detailed list of steps that reproduce the issue.

  1. Create a Flatlist that renders items that contain a lot of text
  2. Set the Flatlist as inverted

Expected Results

The Flatlist should be as smooth when inverted as when normal.

Snack, code example, screenshot, or link to a repository:

https://snack.expo.io/@divone/4c8d68

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 40
  • Comments: 77 (12 by maintainers)

Commits related to this issue

Most upvoted comments

I also encounter this issue with my chat screen. It’s really hard to find to root cause.

  • Only happen on Android S10e device.
  • With inverted FlatList.
  • FlatList Item contains a Text with any given length.

It’s only take 10 items in the list for the performance to be hit really bad.

I’m using @cookiespam bandaid + patch-package to resolve the issue for now now.

Here is the the content of the patch:

react-native+0.63.3.patch

diff --git a/node_modules/react-native/Libraries/Lists/VirtualizedList.js b/node_modules/react-native/Libraries/Lists/VirtualizedList.js
index 9ec105f..a8f1d8c 100644
--- a/node_modules/react-native/Libraries/Lists/VirtualizedList.js
+++ b/node_modules/react-native/Libraries/Lists/VirtualizedList.js
@@ -11,6 +11,7 @@
 'use strict';
 
 const Batchinator = require('../Interaction/Batchinator');
+const Platform = require('../Utilities/Platform');
 const FillRateHelper = require('./FillRateHelper');
 const PropTypes = require('prop-types');
 const React = require('react');
@@ -2185,9 +2186,14 @@ function describeNestedLists(childList: {
 }
 
 const styles = StyleSheet.create({
-  verticallyInverted: {
-    transform: [{scaleY: -1}],
-  },
+  verticallyInverted:
+    Platform.OS === 'android'
+      ? {
+          scaleY: -1,
+        }
+      : {
+          transform: [{scaleY: -1}]
+        },
   horizontallyInverted: {
     transform: [{scaleX: -1}],
   },

@cookiespam’s workaround works. for a less intrusive fix, we could “invert” twice ourselves, once in the container and once in every cell

return (
  <FlatList
    // ...
    style={{scaleY: -1}}
    renderItem={({item}) => {
      return (
        <View style={{scaleY: -1}}>
          {/* cell content */}
        </View
      );
    }}
  >
);

for ios, could just use the official inverted prop. however, that scaleY outside of transform seems undocumented. @cookiespam how did you come up with it? great thanks though

Facing this issue on a Samsung Galaxy Note10 running Android 11 as well with Expo SDK 39. Didn’t happen on Android 10.

Discovered that changing the verticallyInverted style prop from transform: [{scaleY: -1}] to scaleY: -1 in VirtualizedList fixes it for Android, however it does not invert on iOS.

For now, I have written a quick and dirty workaround to restore scaleY like so in my application’s index.js file:

// Add scaleY back to work around its removal in React Native 0.70.
import ViewReactNativeStyleAttributes from 'react-native/Libraries/Components/View/ReactNativeStyleAttributes'
ViewReactNativeStyleAttributes.scaleY = true

This allows you to continue using scaleY and avoid the aforementioned issue with the scroll bar being moved to the other side of the FlatList when using rotate instead of scaleY. This works fine in React Native 0.70.x, but might break in the future if support for the property is removed from the native Android View code.

@MayheMatan try this

index.js (in your app)

import ViewReactNativeStyleAttributes from 'react-native/Libraries/Components/View/ReactNativeStyleAttributes';
ViewReactNativeStyleAttributes.scaleY = true;

Then patch RN 0.71 (react-native+0.71.1.patch)

diff --git a/node_modules/react-native/Libraries/Lists/VirtualizedList.js b/node_modules/react-native/Libraries/Lists/VirtualizedList.js
index 2629142..4e60aad 100644
--- a/node_modules/react-native/Libraries/Lists/VirtualizedList.js
+++ b/node_modules/react-native/Libraries/Lists/VirtualizedList.js
@@ -29,6 +29,7 @@ import {findNodeHandle} from '../ReactNative/RendererProxy';
 import flattenStyle from '../StyleSheet/flattenStyle';
 import StyleSheet from '../StyleSheet/StyleSheet';
 import clamp from '../Utilities/clamp';
+import Platform from '../Utilities/Platform';
 import infoLog from '../Utilities/infoLog';
 import {CellRenderMask} from './CellRenderMask';
 import ChildListCollection from './ChildListCollection';
@@ -1840,9 +1841,7 @@ export default class VirtualizedList extends StateSafePureComponent<
 }
 
 const styles = StyleSheet.create({
-  verticallyInverted: {
-    transform: [{scaleY: -1}],
-  },
+  verticallyInverted: Platform.OS === 'android' ? { scaleY: -1 } : { transform: [{scaleY: -1}] },
   horizontallyInverted: {
     transform: [{scaleX: -1}],
   },

The fix is part of RN 0.72.4, you can upgrade and test 😊

The scaleY style does not work on react 0.70.0

https://github.com/facebook/react-native/issues/34607

But transform: [{ rotate: '180deg' }], does the trick!

Wow wow wow thank you guys this fix is a lifesaver !!! 🙏

I was actually trying to replace my FlatList by a ScrollView to see where the issue was coming from and noticed then that without inverting the FlatList the performance was fine.

Now I can keep my FlatList inverted, it works exactly same as before without changing any other props and I’m going from horrible 40-50fps (since updating to Android 11) to butter smooth 90fps 😍

the solution proposed by @vinceplusplus is what I will be using:

return (
  <FlatList
    // ...
    style={{scaleY: -1}}
    renderItem={({item}) => {
      return (
        <View style={{scaleY: -1}}>
          {/* cell content */}
        </View
      );
    }}
  >
);

Is someone considered making a pull request with @cookiespam and @pqkluan’s patch? I could make one in a couple of days when I get some free time but I also don’t want to steal your moment of glory haha

Still facing this in 0.71. This is an issue for all chat-like applications

In case it’s helpful to anybody, here’s the latest that’s necessary for an inverted list to play nice on Android 13 with React Native 0.70: https://phab.comm.dev/D6193

@vinceplusplus

I was just trying to invert the FlatList on my own without using the inverted prop and somehow accidentally used scaleY (was googling how to invert something using css) which had been deprecated.

A fix for this has landed on main already:

We are working on getting these commits in a next upcoming v0.72+ version. Meanwhile you can try apply the diffs from the commit 😊

For now, I have written a quick and dirty workaround to restore scaleY like so in my application’s index.js file:

// Add scaleY back to work around its removal in React Native 0.70.
import ViewReactNativeStyleAttributes from 'react-native/Libraries/Components/View/ReactNativeStyleAttributes'
ViewReactNativeStyleAttributes.scaleY = true

This allows you to continue using scaleY and avoid the aforementioned issue with the scroll bar being moved to the other side of the FlatList when using rotate instead of scaleY. This works fine in React Native 0.70.x, but might break in the future if support for the property is removed from the native Android View code.

This saved my day

What about RN 0.71? Same issue?

Yes, same issue. Tested on samsung galaxy S20 Note - Android 13.

Not entirely sure, but I think this PR fixes this issue as well!

Bottom refreshControl

For those who want to keep the refresh control at the bottom, you can wrap your flatlist in a view with scaleY:-1 instead of putting the style into the flat list.

return (
<View style={{scaleY: -1, flex: 1}}>
  <FlatList
    // ...
    renderItem={({item}) => {
      return (
        <View style={{scaleY: -1}}>
          {/* cell content */}
        </View
      );
    }}
  />
</View>
);

Hey @divonelnc thanks for the investigation! I noticed the same issue when running your example.

Throwing a transform style on the flatlist and children of scaleY: -1 also had performance issues for us on Android. However, rotating the list and children 180deg didn’t seem to have the same problem and kept the “inverted” functionality.

resolved my issue on react-native-gifted-chat with the solution from @pqkluan

As @nero2009 mention it has issue with Android devices for lag scrolling (not smooth like in iOS). If you are using react-native-gifted-chat and experience this issue with the RN version you have. Apply the solution mention on this thread.

using the scaleY: -1 instead of transform: [{scaleY: -1}] indeed seems to fix the performance issue, BUT (as usual there is a but) the refresh control becomes an issue.

It renders on the top of the screen as you attempt to scroll down and in order to actually scroll you’ll need to scroll up then down to “free” the pan responder somehow.

In react native versions 0.71+ to stop performance drop on Android turn off inverted FlatList and add this : style={{transform: [{rotate: ‘180deg’}]}}, also in add this style in renderItem that is in flat list., also add this if you want to add scroll bar on the right side you can use showsVerticalScrollIndicator={false}. This fixed our chat app where we had lag on many android device when we have 10k char messages.

@devoren Flatlist is faster than Flashlist inverted…

image

Patch for RN 0.67.5 is here. Thanks for @pqkluan https://github.com/facebook/react-native/issues/30034#issuecomment-806396274

react-native+0.67.5.patch

diff --git a/node_modules/react-native/Libraries/Lists/VirtualizedList.js b/node_modules/react-native/Libraries/Lists/VirtualizedList.js
index 2648cc3..a7e9f18 100644
--- a/node_modules/react-native/Libraries/Lists/VirtualizedList.js
+++ b/node_modules/react-native/Libraries/Lists/VirtualizedList.js
@@ -9,6 +9,7 @@
  */
 
 const Batchinator = require('../Interaction/Batchinator');
+const Platform = require('../Utilities/Platform');
 const FillRateHelper = require('./FillRateHelper');
 const ReactNative = require('../Renderer/shims/ReactNative');
 const RefreshControl = require('../Components/RefreshControl/RefreshControl');
@@ -2118,9 +2119,14 @@ function describeNestedLists(childList: {
 }
 
 const styles = StyleSheet.create({
-  verticallyInverted: {
-    transform: [{scaleY: -1}],
-  },
+  verticallyInverted:
+    Platform.OS === 'android'
+      ? {
+        scaleY: -1,
+      }
+      : {
+        transform: [{scaleY: -1}]
+      },
   horizontallyInverted: {
     transform: [{scaleX: -1}],
   },

VirtualizedList is the base component that FlatList uses to implement the list.

npx patch-package @react-native/virtualized-lists

patches/@react-native+virtualized-lists+0.72.6.patch

diff --git a/node_modules/@react-native/virtualized-lists/Lists/VirtualizedList.js b/node_modules/@react-native/virtualized-lists/Lists/VirtualizedList.js
index 8a23e81..ca17f79 100644
--- a/node_modules/@react-native/virtualized-lists/Lists/VirtualizedList.js
+++ b/node_modules/@react-native/virtualized-lists/Lists/VirtualizedList.js
@@ -30,6 +30,7 @@ import {
   View,
   StyleSheet,
   findNodeHandle,
+  Platform,
 } from 'react-native';
 import Batchinator from '../Interaction/Batchinator';
 import clamp from '../Utilities/clamp';
@@ -2056,9 +2057,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
 }
 
 const styles = StyleSheet.create({
-  verticallyInverted: {
-    transform: [{scaleY: -1}],
-  },
+  verticallyInverted: Platform.OS === 'android' ? { scaleY: -1 } : { transform: [{scaleY: -1}] },
   horizontallyInverted: {
     transform: [{scaleX: -1}],
   },

In React Native 0.72.3 the issue persist. Now the VirtualizedList is in another location @react-native/virtualized-lists/List

Same problem for 0.71.2

Just noting here that we observed that in Android 13 the impact of this is much, much worse than in previous versions of Android, at least in our case (a long list of messages in a chat interface.)

In Android 11 it was not so noticeable and in Android 13 it made the app completely unusable.

@vinceplusplus

I was just trying to invert the FlatList on my own without using the inverted prop and somehow accidentally used scaleY (was googling how to invert something using css) which had been deprecated.

I think I love you, thank you!