wcandillon / react-native-img-cache

Image Cache for React Native
https://hackernoon.com/image-caching-in-react-native-96d8df33ca84
Apache License 2.0
731 stars 143 forks source link

Is there anyway to pre-cache an image before using <CacheImage/> ? #65

Open escobar5 opened 7 years ago

escobar5 commented 7 years ago

I have a use case where I need to cache a set of images that I will be showing to the user in several screens, but I want to cache all of them before showing to be able to use the app offline.

Is this possible?

janreyho commented 7 years ago

+1

antondomratchev commented 7 years ago

@escobar5 @janreyho I was able to do this using ImageCache.get().on() which is what the CacheImage component uses under the covers. However I can't seem to figure out how to keep track of how many images have been downloaded or when the downloads have all been completed. For example:

export function cacheImages(images) {
  return (dispatch) => {
    return forEachIndexed((image, index) => {
      const observer = () => {
        dispatch(updateCacheProgress(index))
        ImageCache.get().dispose({ uri: image.url }, observer)
      }
      ImageCache.get().on({ uri: image.url }, observer, true)
    }, images)
  }
}

@wcandillon I am using this in a thunked redux action creator so when the app gets a list of images, I call ImageCache.get().on() on each image. However, my on handler gets called out of index order whenever the image resource is downloaded. I was wondering if you know of a way to keep track of the images being downloaded if I wanted to show the download progress to the user.

Thanks for the awesome package and a very readable source. :+1:

antondomratchev commented 7 years ago

@wcandillon This is a little off topic, let me know if I need to create a new issue for this. It would be a nice addition to the API if the observer handler would return both the path and the original URI on notify()

Currently the observer returns a path to the local resource which works great when used directly in CacheImage component. However when using the ImageCache observer as pre-caching mechanism it would be benefitial to also return the original URI to keep track of which images have been downloaded from the application side. I am going to implement this in a fork and would love to hear your feedback on it.

Selman555 commented 7 years ago

There should be a consistent way to pre-cache with just the URI as well. I have an issue with the parent container disappearing completely when the image is initially cached...

jankarres commented 6 years ago

@antondomratchev Thanks for your solution. I had the same problem as described in this issue.

The problem is that ImageCache only executes the given callback if the download was successful. The fact that the download was successful only means that the request received a positive response. The system does not take into account whether a file has been loaded.

The following workaround checks if the downloaded file contains any content and uses the state of ImageCache to determine whether the download has been aborted. This solution is useful for the moment. I sent a more sensible solution via PR.

import { ImageCache } from "react-native-img-cache";
import FetchBlob from "react-native-fetch-blob";

function preload(url: string) {
    return new Promise(function(resolve, reject) {
        // Create instance of ImageCache
        const instance = ImageCache.get();
        let finished = false;

        // Create interval to check if download has finished
        const downloadEndedIntervalId = setInterval(() => {
            if (instance.cache[url].downloading) {
                // Wait 50 ms after the download has finished to ensure that callback could be triggered
                setTimeout(() => {
                    if (!finished) { // Promise not yet resolved => download seems to be failed
                        finished = true;
                        clearInterval(downloadEndedIntervalId);

                        reject("DownloadFailed");
                    }
                }, 50);
            }
        }, 100);

        // Callback of download
        const callback = (localUrl) => {
            if (!finished) {
                finished = true;
                clearInterval(downloadEndedIntervalId);

                // Get stats of file (file exists, even if download failed, in case of a succesfull resolved request)
                FetchBlob.fs.stat(localUrl)
                    .then((stat) => {
                        // Check if downloaded file is larger then 0 bytes
                        if (stat && stat.size > 0) {
                            resolve(localUrl);
                        }
                        else { // File downloaded, but without content
                            reject("DownloadFailed");
                        }
                    });
            }
        };

        // Execute download of image
        instance.on({
            uri: url
        }, callback, true);
    });
}

If the PR has been accepted, you can use the following function where no timeouts and intervals are used. It returns the path on success and null on failure.

import { ImageCache } from "react-native-img-cache";

ImageCache.get().preload({
        uri: url
    }, (path) => {
        // Do something
    })

I wrote the code like the whole library with callbacks. For Promises, simply use the following wrappers

import { ImageCache } from "react-native-img-cache";

function preload(url: string) {
    return new Promise((resolve, reject) => {
        ImageCache.get().preload({
            uri: url
        }, (path) => {
            if (path) {
                resolve(path);
            }
            else {
                reject();
            }
        });
    });
}
piubellofelipe commented 6 years ago

@jankarres , using like your latest example. passing an invalid url actually caches an blank image :/

jankarres commented 6 years ago

@piubellofelipe The PR #74 is not yet merged by @wcandillon. For this reason, the code will not work (explained above).

piubellofelipe commented 6 years ago

ok, got it @jankarres . Thanks for the explanation :)

brownieboy commented 6 years ago

@jankarres,

Since your PR has not been accepted, I'm using your long preload() function above. Although it works, it's throwing multiple "Possible Unhandled Promise Rejection" warnings in the react-native console. Basically, one warning for every file that I try to preload, I think.

jankarres commented 6 years ago

@brownieboy You get a promise back from the function. Do you care about catching exceptions? Without any error message I can't help you.

brownieboy commented 6 years ago

@jankarres ,

Doh! Yeah, you're right. It was my calling code that was missing the rejection handling. All good now.