In this blog, we will create an image download system and save it into local storage. This can be accessed by our application locally without needing to download it again. It can be useful when we want to make images available offline. The user's bandwidth usage decreases as the image is downloaded only once.

Creating the Handler

First of all, let's create a class that will handle downloading and saving images. This must be derived from MonoBehaviour as we need to use a coroutine for downloading.

It will have a constant string path consisting of the location where downloaded images will be saved. Then, the DownloadImage method will have onSuccess and onFailed callbacks. onSuccess  will have Texture2d data that is either downloaded or loaded from cache and onFailure will have an exception containing the reason for failure.

We will have to pass imageUrl and fileName as parameters. imageUrl is the URL of the file, and the fileName is the name of the file saved in local storage.

public class ImageDownloader: MonoBehaviour {
  const string path = "Assets/downloadedImages/";

  public void DownloadImage(string imageUrl, string fileName, Action < Texture2D > onSuccess, Action < Exception > onFailure, bool reDownload = false) {
    // We will download and save image from here
  }
}
Image Downloader with Callback

UnityWebRequest

For downloading images, we will use Unity's UnityWebRequestTexture. It can be used by making a coroutine.

Let's create a IEnumerator IE_ImageDownloader to handle the downloading process. This will have a success/failure callback and a imageUrl parameter.

To download an image, we first need to create a request. This can be created using GetTexture method of UnityWebRequestTexture. The image URL should be passed to it. This request will be used to send requests using the SendWebRequest method.

After the request is sent, we need to check the status or result. If the result is a success, then we can get our texture using the DownloadHandlerTexture provided by request.

// coroutine for downloading image
private IEnumerator IE_ImageDownloader(string imageUrl, Action < Texture2D > onSuccess, Action < Exception > onFailure) {
  // create web request and send
  UnityWebRequest request = UnityWebRequestTexture.GetTexture(imageUrl);
  yield return request.SendWebRequest();

  // check if download succeeded
  if (request.result == UnityWebRequest.Result.Success) {
    //extract texture
    DownloadHandlerTexture textureDownloadHandler = (DownloadHandlerTexture) request.downloadHandler;
    Texture2D texture = textureDownloadHandler.texture;

    if (texture == null) {
      onFailure?.Invoke(new Exception("image not available"));
      yield break;
    }

    onSuccess?.Invoke(texture);
    yield break;
  }

  // error occured when downloading image 
  onFailure?.Invoke(new Exception(request.error));
}
Image Downloader

Saving and Downloading Image

After the image is downloaded, we need to save it. We need to create a method SaveImage to do so, which will take the image and fileName as parameters.

Before saving an image, we should be sure that a path exists. This can be done by using the Directory.Exists method. If the path doesn't exist, we will create that path using the Directory.CreateDirectory method.

When the required path is created, we can save files in it with a provided filename. This filename will be used when fetching the images later.

// save image in device for later use
private static void SaveImage(Texture2D image, string filename) {
  string savePath = Application.persistentDataPath;
  try {
    // check if directory exists, if not create it
    if (!Directory.Exists(savePath)) {
      Directory.CreateDirectory(savePath);
    }
    File.WriteAllBytes(savePath + filename, image.GetRawTextureData());
  } catch (Exception e) {
    Debug.Log(e.Message);
  }
}
Saving Image

We also need to create a method to load the saved images. To handle it, let's create a GetImage method with fileName as parameter.

We also need to check if the texture exists before we can start using it. It can be checked using the File.Exists method. After confirming the file's existence, load it using the File.ReadAllBytes method.

// get texture stored in device if exists, if doesn't exists, return null
private static Texture2D GetImage(string fileName) {
  string savePath = Application.persistentDataPath;

  try {
    //first check if texture exists , if exists, start fetching
    if (File.Exists(savePath + fileName)) {
      byte[] bytes = File.ReadAllBytes(savePath + fileName);
      Texture2D texture = new Texture2D(1, 1);
      texture.LoadRawTextureData(bytes);
      return texture;
    }

    return null; // texture not found so return null
  } catch (Exception e) {
    return null;
  }
}
Getting Saved Image

Downloading and Handling Cache Images

We have created a system to download, save, and load images. Now let's manage the process of downloading and storing images as well.

Notice that we have included reDownload as an optional parameter. This will be useful when we want to force the download by ignoring previously downloaded images. The algorithm for loading images is,

  1. Try to load the image from local storage if it was previously downloaded. If ReDownload is true, there is no need to check as we are forced to download.
  2. If an image is found, return the success state and the loaded image.
  3. If the image is not found, start downloading.
  4. On download success, save this image into local storage and return success.
  5. If the download fails, return the failed state.
Downloading and Saving Process
public void DownloadImage(string imageUrl, string fileName, Action < Texture2D > onSuccess, Action < Exception > onFailure, bool reDownload = false) {
  if (!reDownload) {
    Texture2D texture = GetImage(fileName);

    if (texture != null) {
      onSuccess?.Invoke(texture);
      return;
    }
  }

  StartCoroutine(IE_ImageDownloader(imageUrl, texture => {
    SaveImage(texture, fileName);
    onSuccess?.Invoke(texture);
  }, onFailure));

}
Downloading, Saving and Loading Images

This completes the downloading and saving system. It can be further extended to add an expiry system for the downloaded file and make separate save locations depending on image type.

Thank you for reading! More articles coming soon!