Figuring out performance issues - react-native

For the last few days I've been fighting performance issues of FlatList inside application I've created.
FlatList consists of static header and x rows. In the measured case there are 62 rows, every one of them consists of 4 components - one of them is used 4 times, which sums up to 7 row cells. Every cell of this list is either TouchableNativeFeedback or TouchableOpacity (for testing purpose those have attached () => null to onPress. There is shouldComponentUpdate used on few levels (list container, row, single cell) and I think render performance is good enough for that kind of list.
For consistent measurements I've used initialNumToRender={data.length}, so whole list renders at once. List is rendered using button, data loading isn't part of my measurement - it's preloaded to component local state.
According to attached Chrome Performance Profile JS thread takes 1.33s to render component. I've used CPU slowdown x6 to simulate Android device more accurately.
However, list shows up on device at around 15s marker, so actual render from button press to list showing up takes more than 14s!
What I am trying to figure out is what happens between JS rendering component and component actually showing up on screen, because device is
unresponsive whole that time. Every touch event is registered, but it is played out only when the list is finally on screen.
I've attached trace from chrome dev tools, systrace taken using android systrace tool and screen from android profiler (sadly I couldn't find option to export the latter).
Tracing was run almost simultaneously - the order is systrace, android profiler, chrome dev tools.
What are the steps that I should make to help me understand what's going on while the app freezes?
Simple reproduction application (code in src.js, first commit)
Chrome Performance Profile
Systrace HTML
Android Profiler

Looking at the source code you posted, I don't think this is a React rendering problem.
The issue is that you're doing too much work in your render method and the helper methods you are calling during the render pass.
Every time you call .filter, .forEach or .map on an array, the entire list is iterated over n times. When you do this for m components, you get a computational complexity of O(n * m).
For example, this is the TransportPaymentsListItem render method:
/**
* Render table row
*/
render() {
const {
chargeMember,
obligatoryChargeNames,
sendPayment,
eventMainNavigation
} = this.props;
/**
* Filters obligatory and obligatory_paid_in_system charges for ChargeMember
*/
const obligatoryChargesWithSys = this.props.chargeMember.membership_charges.filter(
membershipCharge =>
membershipCharge.type === "obligatory" ||
membershipCharge.type === "obligatory_paid_in_system"
);
/**
* Filters obligatory charges for ChargeMember
*/
const obligatoryCharges = obligatoryChargesWithSys.filter(
membershipCharge => membershipCharge.type === "obligatory"
);
/**
* Filters obligatory adjustments for ChargeMember
*/
const obligatoryAdjustments = this.props.chargeMember.membership_charges.filter(
membershipCharge =>
membershipCharge.type === "optional" &&
membershipCharge.obligatory === true
);
/**
* Filters obligatory trainings for ChargeMember
*/
const obligatoryTrainings = this.props.chargeMember.membership_charges.filter(
membershipCharge =>
membershipCharge.type === "training" &&
membershipCharge.obligatory === true
);
/**
* Filters paid obligatory adjustments for ChargeMember
*/
const payedObligatoryTrainings = obligatoryTrainings.filter(
obligatoryTraining =>
obligatoryTraining.price.amount ===
obligatoryTraining.amount_paid.amount
);
// This one does a .forEach as well...
const sums = this.calculatedSums(obligatoryCharges);
// Actually render...
return (
In the sample code there are 11 such iterator calls. For the dataset of 62 rows you have used, the array iterators are called total of 4216 times! Even if every iterator is very simple comparison, simply iterating through all those lists is too slow and blocks the main JS thread.
To solve this, you should lift the state transformation higher up in the component chain, so that you can only do the iteration once and build up a view model that you can pass down the component tree and render declaratively without additional work.

I was trying different things for a while now, I was even considering wrapping native Android RecyclerView, but to be fair, it seemed quite a challenge as I've had no prior experience with native Android code.
One of the things I've tried over the past few days was using react-native-largelist, but it didn't deliver promised performance improvement. To be fair, it might've been even slower than FlatList, but I didn't take exact measurements.
After few days of googling, coding and profiling I've finally managed to get my hands on this Medium post, which references recyclerlistview package and it seems to provide better experience than FlatList. For profiled case rendering time dropped to about 2s, including 300ms of JS thread work.
It's mandatory to notice that improvement of initial rendering comes from reduced number of items rendered (11 in my case). FlatList with initialNumToRender={11} setting renders initially in about the same time.
In my case initial rendering, while still important, isn't the only thing that matters. FlatList performance drops for larger lists mainly because while scrolling it keeps all rendered rows in memory, while recyclerlistview recycles rendered rows putting in new data.
Reason of observer performance improvement for re-rendering is actually simple to test. I've added console.log to shouldComponentUpdate in my row component and counted how many rows is actually re-rendered. For my row height and test device resolution recyclerlistview re-renders only 17 rows, while FlatList triggers shouldComponentUpdate for every item in dataset. It's also worth noticing that number of rows re-rendered for recyclerlistview is not dependent on dataset size.
My conclusion is that FlatList performance may degrade even more with bigger datasets, while recyclerlistview speed should stay at similar level.
TouchableNativeFeedback inside recyclerlistview also seem to be more responsive, as the animation fires up without delay, but I can't explain that behaviour looking at profilers.
There's definitely still room for improvement in my row component, but for now I am happy with overall list rendering performance.
Simple reproduction application with recyclerlistview (code in src.js, second commit)
Chrome Performance Profile (recyclerlistview)
Systrace HTML (recyclerlistview)

Just give one more source to try.
You can enable Hermes in android/app/build.gradle like this.
project.ext.react = [
entryFile : "index.js",
enableHermes: true, // clean and rebuild if changing
]
My app not crashing but the touchable will stop working after some heavy process. When this happens, the process is still running ok, the app's drawer is still working. But all touchable in my app stop working without any error message. I spent 3 days to find a solution and finally tried hermes. On react native docs, it says
Hermes is an open-source JavaScript engine optimized for running React Native apps on Android.

Related

Using State For React Native Navigation

I am fairly new to React Native and I am building an app that will be using navigation. I have a solution for navigating screens that seems to work just fine however I do not see many people using this approach. I am using state in a navigation component to render a specific screen(or component).
The main solution(the documented solution) I see is using this package
#react-navigation/native #react-navigation/native-stack
My current solution works like this:
const [screen, setScreen] = useState(0)
const returnScreen = () => {
switch (screen) {
case 0:
return <AccountScreen/>
}
}
When you click a menu icon, it would change the state to say 1, and render a different screen.
Can someone help me understand why this is a bad approach or an uncommon approach(if it is)? My app will only have 5-7 screens, I am mainly looking to understand why the library is the community approach and if I am missing something.
If you wanted the ability to navigate to other screens to exist outside your menu/drawer you will have to either prop drill your setScreen function or create a context to make it accessible to other components. While this isnt hard to do, it is something that all #react-navigation navigators do without requiring additional code.
If you wanted to enable going to the previous screen, your current navigation code becomes obsolete. Just returning a screen is no longer enough, as you need to track and modify navigation history. So you write more code that you have to test and debug (keep in mind that #react-navigation/stack already does this).
If you are certain that your app will remain small and your navigation features wont increase too much then your approach is completely fine. But if your app will increase in size then you probably will find yourself implementing, testing, and debugging features that have already been implemented, tested, and debugged by the #react-navigation community

Running cleanup when a react native app is closed

In my react-native app's code:
I am monitoring AppState changes
When AppState changes to 'inactive' or 'background', I write about 2000 key/value pairs of data to AsyncStorage
When the app starts I read this data
When I test the app on android (didn't test on iOS yet):
If I minimize the app, the data is written as expected. I can see that by closing the app after minimizing it, and restarting it
However, if I click on the 'Recent' button in the navigation bar (which changes AppState to 'inactive') and immediately click on the 'X' button at the top right of the app, the data doesn't seem to be written, or at least not all of it (I am not checking all 2000 values, just few of them). If I try to write only few values, they are written
Question:
The explanation to the above behavior seems to be simple: the app has enough time to perform few operations before I click on 'close', but not to write 2000 values. Is there a way to perform the writing before the app is closed?
Actually it's not recommended to do any async function or anything that could take too long. Because you don't have the control of how much time it will take to write the 2000 values. The app can go to background and the SO can kill the proccess of your app. When the app is in background is the SO that controls it.

React Native Redux performance issue app freeze

I'm building a React Native application (using Redux(Thunk)) where I lately have been facing performance issues that I haven't been able to find a good solution to.
The issue is that the UI/App is freezing/blocked every time I call an action, whether the state changes or not. The app is dealing with accounts data, and the issue started when the account count grew to about 3k accounts. I tested the following:
The time it takes to create the new state in the reducer.
For unnecessary renders.
Any manipulation on the data done in mapStateToProps or elsewhere.
None of the above is taking long or seems to be the issue. The example that I am testing on is a list view of the accounts, with an animated pop up for filtering. When I add a filter in the pop up and saves it (updates the store and closes pop up), I can see the accounts filtering instantly behind the pop up, but the UI/pop up animations is frozen for 2-3 seconds, before closing.
The data is stored with id's as index for accounts and a separate object for filters:
{
accountByIds: { acc1: {...}, acc2: {...} },
filters: {...}
}
I have been wondering whether using ImmutableJS would help, but as the application have grown rather large and I am not sure this would help, as the issue doesn't seem to be to many renders etc., I haven't changed it.
I am currently updating the store like below:
return {
...state,
filters: {
...state.filters,
newFilter
}
}
In sum up, it seems that when the store updates, the appropriate renders are called right away, but then the app seems to freeze up for a moment.
If anyone is able to shed any light on this or point me in the right direction I would appreciate it.

React native Image prefetch

I am having difficulties to understand Image prefetch. In the doc's there is not much explanation about it:
"Prefetches a remote image for later use by downloading it to the disk
cache"
Could you please help me understand the following about Image prefetch:
Suppose a user uploads a profile image, and the image's URL is stored in the AsyncStorage.
Should I run Image.prefetch(UserStore.profileImageUrl) only once after successful upload. And use prefetched image in the components normally like <Imagesource={{uri: UserStore.profileImageUrl}}/>
Or should I always run Image.prefetch(UserStore.profileImageUrl) before using that image in the component, then only run <Imagesource={{uri: UserStore.profileImageUrl}}/>
Suppose, later on, the user changes their profile image by uploading a new image and after successful upload, I will prefetch the new image. Will the previously cached image still exist on the disk?
If yes, won't it occupy a lot of space in the device if there are lots of prefetched images?
Is there any way to manually remove the prefetched image from the disk?
With the above questions in mind, if there are alternate solutions to achieve caching of images when using react native with expo, could you please help me with it.
It was indeed a question that I was dealing with for a while, and I learned a few things about Image.prefetch:
In the current React-Native version (0.48), this method is still in progress. To be more precise:
the ios implementation is still incomplete.
There is no complete guide on it.
There is no way to clear cache (you can check if the url is cached, however as far as I know you cannot clear it now).
As a result, I don't suggest you use it. Regardless, if you want to know how the API works, it is how:
Purpose
The purpose is quite obvious I think, this API:
Prefetches a remote image for later use by downloading it to the disk cache
It means that you can use Image.prefetch(url) in your constructor or componentWillMount. It tries to fetch image asynchronically, then, render your page with some kind of an ActivityIndicator, Finally, when the image is successfully fetched you can re-render your component.
Image.prefetch(url) actually saves the image to disk (not memory), as a result, whenever or wherever you try to use
<Image source={{uri:url}}/>
At firsts it checks the list of caches urls, if you have prefetched that url before (and it is located on the disk), it won't bother to re-fetch (unless you run function `Image.prefetch(url)' again (I am not sure if it works properly).
The implications of this issue are so complicated. It means that if you prefetch an image inside one component (for example <Component1/>), when you try to show this specific image in another component (for example <Component12>), It won't fetch the image and just uses the disk cache.
Therefore, either don't use this Image.prefetch at all (until there is a complete API, with cache control) or use it at your own risk.
on Android
On Android, you have 3 APIs for prefetch, and only one of them is presented in the documentation:
prefetch:
var response = Image.prefetch(imageUrl,callbackFunction)
Image.prefetch can have an optional second argument callbackFunction, which a function that runs Before fetching image. It can be written in the following format:
var response = Image.prefetch(imageUrl,()=>console.log('Image is being fetched'))
It might be worthy to note that, callbackFunction can have an argument called requestId, (indicating the number of prefetch among all other prefetches) which can be then used to abort the fetch.
var response = Image.prefetch(imageUrl,(id)=>console.log(id))
Moreover, response is a promise, you can use .then to do more after the image is pre-fetched.
abortPrefetch
Image.abortPrefetch(requestId) ;
used to abort the pending prefetch. requestId used as argument is the same as the one seen in prefetch.
queryCache
Image.queryCache([url1,url2, ...])
.then((data)=>console.log(data));
Used to check if a certain url is already cached, and if so where is it cached (disk or memory)
on IOS
I think that only Image.prefetch(url) is currently available on IOS, and there is no callback function to be called as the second argument.
if there are alternate solutions to achieve caching of images when
using react native with expo, could you please help me with it.
You may be interested in my higher order component module that adds performance related image caching and "permanent cache" functionality to the native <Image> component.
React Native Image Cache HOC
Tl;DR Code Example:
import imageCacheHoc from 'react-native-image-cache-hoc';
const CacheableImage = imageCacheHoc(Image);
export default class App extends Component<{}> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<CacheableImage style={styles.image} source={{uri: 'https://i.redd.it/rc29s4bz61uz.png'}} />
<CacheableImage style={styles.image} source={{uri: 'https://i.redd.it/hhhim0kc5swz.jpg'}} permanent={true} />
</View>
);
}
}
The first image will be cached until the total local cache grows past 15 MB (by default) then cached images are deleted oldest first until total cache is below 15 MB again.
The second image will be stored to local disk permanently. People use this as a drop in replacement for shipping static image files with your app.
Personally I would not overcomplicate things by writing over files over and over when the file changes, that's just asking for a massive headache. Instead I would create a unique filename for each upload. So the user's profile pic the first time they upload is "profile-uniqueIdHere.jpg" and when they change their profile pic you just create a new file "profile-anotherUniqueIdHere.jpg" and update their user data to reflect the new file location. For help with creating unique Ids look at react-native-uuid.

Prefetch Images from URL

I am trying to learn a little more about React Native's ability to prefetch images from the internet. My overall goal is to basically build a slideshow, but when rerendering with a different photo.. you don't see this weird white flash
One of my issues is the Image.Prefetch documentation is pretty limited and I am having a hard time figuring out how it works
First I create this.state.picArray and create an array of 4 pictures
picArray: ['https://snap-photos.s3.amazonaws.com/img-thumbs/960w/5ECBT47XF5.jpg',
'https://snap-photos.s3.amazonaws.com/img-thumbs/960w/8N1P2AHD0W.jpg',
'https://snap-photos.s3.amazonaws.com/img-thumbs/960w/SFJPODPJY4.jpg',
'https://snap-photos.s3.amazonaws.com/img-thumbs/960w/5ECBT47XF5.jpg'
]
I try to conduct the prefetching for each image I have in the array I created above in the componentDidMount
componentDidMount() {
var prefetchTask = Image.prefetch('https://snap-photos.s3.amazonaws.com/img-thumbs/960w/5ECBT47XF5.jpg');
var prefetchTask1 = Image.prefetch('https://snap-photos.s3.amazonaws.com/img-thumbs/960w/8N1P2AHD0W.jpg');
var prefetchTask2 = Image.prefetch('https://snap-photos.s3.amazonaws.com/img-thumbs/960w/SFJPODPJY4.jpg');
var prefetchTask4 = Image.prefetch('https://snap-photos.s3.amazonaws.com/img-thumbs/960w/5ECBT47XF5.jpg');
}
and then I show the pictures like this, using another variable as like an index, and when the picture is tapped incriments and shows the next picture in the array
<Image source={{ uri: this.state.picArray[this.state.id] }}
style={styles.deck} />
It might seem liek this has faster loading time but I am not sure that it does, is this the correct way to prefetch these images? My ultimate goal is to have no white flash inbetween switching pictures. Any help would be a huge thanks
ComponentWillMount should be the right callback, because it is called right before the mounting. If you won't pass the prefetched images through props the constructor may also work. If you pass them and eventually change them you may call the prefetch on the wrong items as a race condition.