Storing Photos with the Cordova Camera and File Plugins

Matt Netkow - Oct 18 '19 - - Dev Community

Storing photos in a Cordova-based Ionic app can be challenging. Several concepts and layers of the app development stack are involved, including selecting the best Camera plugin configuration, saving files to permanent storage, and understanding how to render an image in a WebView.

In this post, we’ll use the Ionic Native Camera plugin to take photos, then save them to the app’s filesystem using the Ionic Native File plugin.

Note: For a guide covering similar concepts using Capacitor's Camera and File plugins, see Josh Morony’s great guide here.

Plugin Installation

First, install the two native plugins into your Ionic project:

# Camera plugin
ionic cordova plugin add cordova-plugin-camera
npm install @ionic-native/camera

# File plugin
ionic cordova plugin add cordova-plugin-file
npm install @ionic-native/file

Camera Plugin: Configuration and Capturing Photos

With both plugins installed, we can now configure the camera options. We use FILE_URI as the destination type to save the captured image to temporary file storage. Avoid DATA_URL, which returns the image as a base64 encoded string, because loading image data into a string is very memory intensive, often leading to app crashes.

const options: CameraOptions = {
  quality: 100,
  destinationType: this.camera.DestinationType.FILE_URI,
  encodingType: this.camera.EncodingType.JPEG
}

Next, call the getPicture method to open the camera on the device. Once the app user takes a photo and confirms it should be used, the path to the newly created image is stored in the tempImage variable.

const tempImage = await this.camera.getPicture(options);

Saving Photos to the Filesystem

As we see, using the Camera plugin to take a photo is straightforward. Saving them permanently to the filesystem, however, gets tricky fast. Why do we need to use the filesystem plugin to store photos anyway? It turns out that images captured by the Camera plugin are only stored in the app’s temporary storage. This keeps memory usage low, which is important of course on mobile devices, as well as useful when implementing features like letting the user preview the photo before storing it. However, this means that when the app is closed, the images will be deleted by the mobile OS.

As part of the following code, I’ll show examples of the filesystem values (all for iOS, though Android will look very similar) that each variable represents for each step of the photo saving implementation. It’s helpful to see these values when debugging code that uses the File plugin. Here’s an example value of tempImage:

file:///var/mobile/Containers/Data/Application/E4A79B4A-E5CB-4E0C-A7D9-0603ECD48690/tmp/cdv_photo_003.jpg

Let’s review each portion of the filepath:

  • file:///: The file protocol, which indicates that this is a file on the device.
  • E4A79B4A-E5CB-4E0C-A7D9-0603ECD48690: This is the app’s unique id (GUID). Yours will be different than mine.
  • /tmp/: The temp directory within this app.
  • cdv_photo_003.jpg: The filename of the photo.

Next, let’s save the photo to the filesystem, beginning with extracting different pieces of the temp image filepath, so we can work with the File plugin.

// Extract just the filename. Result example: cdv_photo_003.jpg
const tempFilename = cameraImage.substr(tempImage.lastIndexOf('/') + 1);

// Now, the opposite. Extract the full path, minus filename. 
// Result example: file:///var/mobile/Containers/Data/Application
// /E4A79B4A-E5CB-4E0C-A7D9-0603ECD48690/tmp/
const tempBaseFilesystemPath = tempImage.substr(0, tempImage.lastIndexOf('/') + 1);

// Get the Data directory on the device. 
// Result example: file:///var/mobile/Containers/Data/Application
// /E4A79B4A-E5CB-4E0C-A7D9-0603ECD48690/Library/NoCloud/
const newBaseFilesystemPath = this.file.dataDirectory;

Next, save the photo to the filesystem by copying it from temp storage to permanent file storage, while maintaining the original filename. There are other methods available to save files, but this is the simplest. Notice that after saving the file, the photo is now found under the Library/NoCloud/ directory instead of tmp/.

await this.file.copyFile(tempBaseFilesystemPath, tempFilename, 
                         newBaseFilesystemPath, tempFilename);

// Result example: file:///var/mobile/Containers/Data/Application
// /E4A79B4A-E5CB-4E0C-A7D9-0603ECD48690/Library/NoCloud/cdv_photo_003.jpg
const storedPhoto = newBaseFilesystemPath + tempFilename;

Rendering the Photo

With the image now stored into the filesystem, you’ll likely want to display it in the same app. There’s a not-so-obvious snag, though: Cordova and Capacitor apps are hosted on a local HTTP server and are served with the http:// protocol. As you’ll recall however, files are “hosted” using the file:// protocol, so we must rewrite the image from a device filepath to the local HTTP server by using the Ionic Native WebView-provided convertFileSrc( ) method.

const displayImage = this.webview.convertFileSrc(storedPhoto);

Then, render the image using an Ionic ion-image component.

<ion-img src="{{ displayImage }}"

Here’s the complete example:

const options: CameraOptions = {
  quality: 100,
  destinationType: this.camera.DestinationType.FILE_URI,
  encodingType: this.camera.EncodingType.JPEG
}

const tempImage = await this.camera.getPicture(options);
const tempFilename = cameraImage.substr(tempImage.lastIndexOf('/') + 1);
const tempBaseFilesystemPath = tempImage.substr(0, tempImage.lastIndexOf('/') + 1);

const newBaseFilesystemPath = this.file.dataDirectory;

await this.file.copyFile(tempBaseFilesystemPath, tempFilename, 
                         newBaseFilesystemPath, tempFilename);

const storedPhoto = newBaseFilesystemPath + tempFilename;
const displayImage = this.webview.convertFileSrc(storedPhoto);

Angular URL Sanitization

When using Angular, an extra step is required if using property data-binding. If an image won't render, it's likely being blocked. In the DevTools console, an error will appear mentioning unsafe/unsupported URLs.

We know our images are safe, so address this by using webview.convertFileSrc( ) as above, then the DOMSanitizer bypassSecurityTrustUrl function too:

import { DomSanitizer } from '@angular/platform-browser';

const resolvedImg = this.webview.convertFileSrc(storedPhoto);
const safeImg = this.sanitizer.bypassSecurityTrustUrl(resolvedImg);

The HTML template looks like:

<img [src]="safeImg" />

With a bit of understanding of how the Cordova File plugin works, we’re able to store photos properly in an Ionic app.

More Resources to Check Out

To see both Cordova and Capacitor versions of this tutorial's code, check out the demo app referenced during our recent “Building Capacitor Apps in Ionic Appflow” webinar.

You can continue to explore the Camera and File plugins in both Simon’s Ionic 4 Images Guide and Josh’s Using the Capacitor Filesystem API to Store Photos guide.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .