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.
Related
According to the documentation of the package if you do this:
cache={FastImage.cacheControl.cacheOnly}
it should only show images from cache, not make any network requests.
However, I tried to clear cache and re run the app, it still fetches images:
const cacheImg = profile_pic_url && (<TouchableOpacity ><FastImage
source={{
uri: profile_pic_url.url,
priority: FastImage.priority.high,
}}
style={styles.userProfilePic}
cache={FastImage.cacheControl.cacheOnly}
resizeMode={FastImage.resizeMode.cover}
/></TouchableOpacity>);
To be sure I even clear in the componentDidMount:
FastImage.clearMemoryCache();
FastImage.clearDiskCache();
Is this a bug or am I missing something?!
If the cache is cleared, there's nothing to fetch locally, so it's forced to fetch the images remotely. I don't think it's possible to tell it not to fetch when there isn't anything in the cache - you would never get your image.
If Memory is big concern for you then try to use Image from react-native directly and remove react-native-fast-image. I was also facing the same issue. Tried with
FastImage.clearMemoryCache();
FastImage.clearDiskCache();
in componentWillUnMount()
It clear lots of memory but my target is not achieving so i simply use Image and its miracle for me.
I am creating an app using Expo/RN where the first screen has a fair amount (~20) images loaded, as the result of a network call. I am only working in iOS.
My issue is that the images load extremely slowly, most taking around a minute to load, and some never load but stay black. I am using the built in Image component, with a url supplied in the source object. The images load immediately when the mobile website is loaded in Safari.
Furthermore, the built-in caching seems to not work at all. When an image has loaded and should be shown in multiple places, it often doesn't appear in the second place at all. I have tried using "force-cache" with no effect.
I have tried the packages react-native-fast-image, which wouldn't run, and react-native-expo-image-cache, which gave me some results but memory leak issues and UI freezes.
Is there any solution? Should I just create my own caching component?
EDIT: The images are loaded in a FlatList. Each item is rendered by a custom component including an . The image simply takes the url as
<Image
style={[styles.image, smallCell ? { width: smallCellWidth } : {}]}
source={{ uri: project.imageUrl, cache: "force-cache" }}
opacity={0.85}
/>
It turns out the biggest issue was not on the RN side, but the API call used. I was downloading the original sized images from the server, rather than the resized ones, and they were so large that I was getting the above result.
However, using force-cache did have good results for me.
I have an app that makes heavy use of the Image component from React Native.
I understand that caching is good for remote images but I need to load local images which change regularly.
The Image component is caching the files and showing the cached version even when the local files change.
The question is how do I disable caching on local files but keep it for remote URLs (as I have a mix of local and remote)?
I would give a code example but literally it's as simple as
<Image source={{uri: 'file://image.png'}} />
Note: These are files that are created and changed by actions in the app so require('image.png') will not cut it. I use that for static images all the time and works great but it's static not dynamic.
I've also seen answers about random query params on the end of the string. That's very hacky in general so I wouldn't hire you for a job :) but apart from that it apparently doesn't work.
Cheers in advance!
To be really clear based on commments and answers to far....when the image file changes. It needs to change the image component immediately so caching and state need to be cleared and the new image is displayed.
A bit late to answer but you can add the query "?time=new Date()" in your variable, like that :
const [profilePicture] = useState(`${baseURL}${user.infoUser.image_profile}?time=${new Date()}`);
The main problem with this one is that is gonna refresh the image every time and oso the delay to display the image may be quite high.
Catching would be good for your app,so you don't want to stop. The solution to that sort of problem is to change the name of the image as well as the image itself. Changing the image without changing the name will do nothing.
You could have something like this
<Image source={{uri: `file://${imageName}`}} />,
that's the only piece of code you have posted so I can't really suggest more than that.
Better still you could store the imageName as part of state to be be able to re-render to a different anytime you feel like.
<Image source={{uri: `file://${this.state.imageName}`}} />
I think the problem is that there's no listener for changes on the image resource itself (and I don't think there's an library for that either). The only possible solution if you don't want to change the name of your file, would be to trigger an Event (or a state/redux - change) to force the component to reload everytime you change the Image in your code.
Otherwise you have to write your own library which always listens on image changes and reloads the component evertime something changes.
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.
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.