0

Maybe my question will sound foolish, but here it is...

How can we get the path to a cached image (in both iOS and Android)?

Here is my use case: I present a view on my App that lists images from the web --> I get an array of urls from Google Customer Search API based on the user's provided keywords...

<FlatList
   style={{ width: '100%' }}
   data={this.state.suggestions}
   numColumns={2}
   renderItem={(img) => {
     return (
         <TouchableOpacity onPress={() => this.selectImageHandler(img.item)} >
            <Image source={{ uri: img.item.link }} />
         </TouchableOpacity>
       )
    }}
    keyExtractor={(item, index) => index.toString()}
/>

The result looks like this:

enter image description here

Then the user presses on an image to select it, which then needs to store this image in the app's folder (PictureDir/myappfolder/ in Android, DocumentDir/myappfolder/ in iOS) ...

What I am doing right now is when the image is selected, I download it again:

selectImageHandler = (img) => {
   // (... pre code ...)
   RNFS.downloadFile({
     fromUrl: img.link, // e.g. "http://www.url.to/the/picture.png
     toFile: uri,  // e.g. "file://"+PictureDir+ "/myAppFolder/picturename.jpg"
   }).promise.then( res => {
   // (... post code ...)
}

It works fine! But it takes a bit of time, as it downloads again the image, but I feel this is doing it twice, as it was downloaded already a stored in the cache to be displayed.

So here comes my question again, is there a way to know where the image was stored in the cache, so that when the user pressed the image to save it, it will not download it again, but will rather move it from the cache folder to the app's folder?

Am I making any sense? Or is redownloading the right approach?

Thanks for your help!

4
  • Maybe you should investigate how to download a compressed or smaller image the first time around, as the size of the image is probably what makes the process so slow.. just a thought Commented Jan 29, 2019 at 14:28
  • @RachelGallen thanks for the suggestion, nevertheless I do not control the size of the image returned by the API. Hence my question on how to avoid duplication of work when the pictures (I believe) already exists stored in cache somewhere. Commented Jan 29, 2019 at 19:23
  • I know what your question was, but my suggestion was perhaps you SHOULD attempt to control the size of the downloaded image (as well) . It would definitely speed your app up. Commented Jan 29, 2019 at 19:27
  • Did you end up resolving this issue? Commented Oct 9, 2019 at 7:55

1 Answer 1

1

One way to avoid re-downloading images a few times might be to take over the control of downloading from a remote url from the <Image> component. Basically, you can download the remote image using the RNFS.downloadFile method and then supply the local URI (toFile value) as the image source. This requires a bit more work, of course, as we need to create a wrapper component, but this approach also provides options to control the display of an image while it's loading.

For example:

import React, { useState, useLayoutEffect } from 'react';
import RNFS from 'react-native-fs';
import { URL } from 'react-native-url-polyfill';

const CACHE_DIR = RNFS.DocumentDirectoryPath; // Cross-platform directory

function ImageCard ({ imgUrl }) {
  const [cachedImgUrl, setCachedImgUrl] =  useState(null);
  const [isImageLoading, setIsImageLoading] = useState(true);

  useLayoutEffect(() => {
    const getCachedImageUrl = async () => {
       try {
         const basename = new URL(imgUrl).pathname.split('/').pop();
         const localImgUrl = `file://${CACHE_DIR}/${basename}`;

         if (await RNFS.exists(localCacheUrl)) {
           setCachedImgUrl(localImgUrl);
         } else {
           const download = RNFS.downloadFile({
             fromUrl: imgUrl,
             toFile: localImgUrl,
           });
           const downloadResult = await download.promise;
           if (downloadResult.status === 200) {
             setCachedImgUrl(localImgUrl);
           }
         }
       } catch (err) {
         // handle error
       } finally {
         setIsImageLoading(false);
       }
    };
    
    getCachedImageUrl();
  }, [imgUrl]);

  if (isImageLoading || !cachedImgUrl) {
    // A better idea would be to return some `<Loader />` component, or some placeholder, like skeleton animation, etc. This is just an example.
    return null;
  }

  return (
    <Image source={{ uri: localImgUrl }} />;
  );
}

The <ImageCard /> component replaces the plain <Image /> component in the <FlatList /> and downloads from the remote image URL only once. The code above is simplified and it assumes that you have unique image names that you can use as the identifiers on the file system, and that the image urls don't include any search parameters, etc. Please be cautious and adapt the code for your needs before using it directly.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.