Tutorial hero
Lesson icon

Using the Capacitor Filesystem API to Store Photos

Originally published April 16, 2018 Time 9 mins

When we take a photo on an iOS device, the resulting file is stored inside of a temporary folder called tmp. If we use the Camera API to retrieve a photo, the resulting URI will look like this:

file:///private/var/mobile/Containers/Data/Application/5C3AFB5A-8A33-4152-932D-32889401E966/tmp/photo-1.jpg

If we are interested in long-term storage then this isn’t the best solution. The way you approach this will depend on what it is your application is trying to achieve. You might want to send this photo to a server, or maybe you want to store the base64 data that is returned, in which case it isn’t going to matter if this temporary file gets deleted. However, for this example, we are going to rely on displaying a photo in an application by referencing the URI of the file on the device directly.

In this tutorial, we will be covering how to use Capacitor to retrieve a photo from the user’s camera or photo library, and then moving that photo into a permanent storage folder. I use the term “permanent” loosely, as of course data can always be deleted. If it is important that the photos in your application are never lost, you should investigate also storing them elsewhere (e.g.on a server). There is also the option to save a copy of any photos your users take to the user’s own photo library, so if the data ever is lost at least they will have their own copy of it.

In order to take a photo and move it to permanent storage, we will need to use both the Camera API and the Filesystem API that Capacitor provides. I’ve dicussed using the Camera API with Ionic before, but the Filesystem API will allow us to work directly with files on the device – we can create files, modify files, delete files, create directories, and more. One of the goals of Capacitor was to implement a Filesystem API that would be easier to use than the existing File API in Cordova.

1. Install Capacitor

In order to work with Capacitor, you will need to install its dependencies and initialise a project. The generic way to add Capacitor to your application is described here: Adding Capacitor to an existing web app - however, if you are using the Ionic CLI you can also use the ionic integrations enable capacitor command.

Whether you are using Ionic/Angular, Ionic/Stencil, or something else, it doesn’t really have any bearing on the result. All we really need here is the Camera API and FileSystem API from Capacitor.

2. Import the Camera and Filesystem APIs

As I mentioned, we are going to need to use both the Camera and Filesystem APIs. The Camera API will allow us to retrieve a photo from the user, and the Filesystem API will allow us to move it into permanent storage.

To set these up, you will need to import the following:

import {
  Capacitor,
  Plugins,
  CameraResultType,
  FilesystemDirectory,
} from '@capacitor/core';

and create a reference to the Camera and Filesystem:

const { Camera, Filesystem } = Plugins;

We are also importing the Capacitor runtime itself, which we will need in order to access Capacitor’s convertFileSrc method which converts the URI returned by the Filesystem API into one that is readable/displayable in our webview.

3. Take a Photo

In order to achieve the functionality we want, we mostly just use the Camera API as we usually would, with one important distinction:

const options = {
  resultType: CameraResultType.Uri,
};

Camera.getPhoto(options).then(
  (photo) => {
    console.log(photo);
  },
  (err) => {
    console.log(err);
  }
);

It is important that we supply the resultType option and use the CameraResultType.Uri value. If we supply this option, the result we receive will contain a path property that contains the path to the file on the device (in the temporary folder). We need this in order to copy it elsewhere.

4. Move the Photo to Permanent Storage

With a reference to the photo in the temporary folder, we can move it to another folder using the Filesystem API. Let’s take a look at how to do that, and then talk through it:

takePhoto(){

    const options = {
      resultType: CameraResultType.Uri
    };

    Camera.getPhoto(options).then(
      photo => {
        Filesystem.readFile({
          path: photo.path
        }).then(
          result => {
            let date = new Date(),
              time = date.getTime(),
              fileName = time + ".jpeg";

            Filesystem.writeFile({
              data: result.data,
              path: fileName,
              directory: FilesystemDirectory.Data
            }).then(
              () => {
                Filesystem.getUri({
                  directory: FilesystemDirectory.Data,
                  path: fileName
                }).then(
                  result => {
                    let path = Capacitor.convertFileSrc(result.uri);
                    console.log(path);
                  },
                  err => {
                    console.log(err);
                  }
                );
              },
              err => {
                console.log(err);
              }
            );
          },
          err => {
            console.log(err);
          }
        );
      },
      err => {
        console.log(err);
      }
    );

}

OR if you prefer you can use the async/await syntax which simplifies the code a bit:

  async takePhoto() {
    const options = {
      resultType: CameraResultType.Uri
    };

    const originalPhoto = await Camera.getPhoto(options);
    const photoInTempStorage = await Filesystem.readFile({ path: originalPhoto.path });

    let date = new Date(),
      time = date.getTime(),
      fileName = time + ".jpeg";

    await Filesystem.writeFile({
      data: photoInTempStorage.data,
      path: fileName,
      directory: FilesystemDirectory.Data
    });

    const finalPhotoUri = await Filesystem.getUri({
      directory: FilesystemDirectory.Data,
      path: fileName
    });

    let photoPath = Capacitor.convertFileSrc(finalPhotoUri.uri);
    console.log(photoPath);
  }

There are three stages here. First, we read the data from the file that was created by the Camera API. We then use that data to write a new file into the data directory. We use the current time to create a unique file name. Then we use getUri to return the URI of the file that we just created in the data directory. I suspect in future we might be able to grab that URI directly from the result of the writeFile method, but for now, this method does not return data about the file that was written.

If we take a look at the URI that is returned, it will look something like this (on Android):

file:///data/user/0/com.joshmorony.example/files/1564467264719.jpeg

If we want to display this image in an Ionic application it won’t work, as we cannot display an image using an <img> tag that loads over the file:// protocol. However, Capacitor provides a way to access these files that is compatible with a web view. All we need to do is run the result through Capacitor.convertFileSrc.

which will result in a URI like the following:

http://localhost/_capacitor_file_/data/user/0/com.joshmorony.example/files/1564467264719.jpeg

You could now safely display that inside of an <img> tag:

<img
  src="http://localhost/_capacitor_file_/data/user/0/com.joshmorony.example/files/1564467264719.jpeg"
/>

Summary

We now have our photo stored outside of the tmp folder which is more likely to be wiped by the operating system at some point. Although we are specifically looking at moving a photo around the file system in this example, the Filesystem API is very powerful and can be used in a multitude of situations with different file types. To see the other types of file operations that this API can perform, you should take a look at the documentation.

Learn to build modern Angular apps with my course