Skip to main content
Version: v8

Saving Photos to the Filesystem

We’re now able to take multiple photos and display them in a photo gallery on the second tab of our app. These photos, however, are not currently being stored permanently, so when the app is closed, they will be deleted.

Filesystem API

Fortunately, saving them to the filesystem only takes a few steps. Begin by creating a new class method, savePicture(), in the usePhotoGallery method.

import { ref, onMounted, watch } from 'vue';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';

export const usePhotoGallery = () => {
// Same old code from before.

// CHANGE: Add the `savePicture()` method.
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
return {
filepath: 'soon...',
webviewPath: 'soon...',
};
};

return {
photos,
addNewToGallery,
};
};

export interface UserPhoto {
filepath: string;
webviewPath?: string;
}

We can use this new method immediately in addNewToGallery().

import { ref, onMounted, watch } from 'vue';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';

export const usePhotoGallery = () => {
// Same old code from before.

// CHANGE: Update the `addNewToGallery()` method.
const addNewToGallery = async () => {
// Take a photo
const capturedPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
});

const fileName = Date.now() + '.jpeg';
// CHANGE: Add `savedImageFile()`.
// Save the picture and add it to photo collection
const savedImageFile = await savePicture(capturedPhoto, fileName);

photos.value = [savedImageFile, ...photos.value];
};

// CHANGE: Add the `savePicture()` method.
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
return {
filepath: 'soon...',
webviewPath: 'soon...',
};
};

return {
photos,
addNewToGallery,
};
};

export interface UserPhoto {
filepath: string;
webviewPath?: string;
}

We'll use the Capacitor Filesystem API to save the photo. First, convert the photo to base64 format.

Then, pass the data to the Filesystem's writeFile method. Recall that we display photos by setting the image's source path (src) to the webviewPath property. So, set the webviewPath and return the new Photo object.

For now, create a new helper method, convertBlobToBase64(), to implement the necessary logic for running on the web.

import { ref, onMounted, watch } from 'vue';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';

export const usePhotoGallery = () => {
// Same old code from before.

// CHANGE: Update the `savePicture()` method.
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
// Fetch the photo, read as a blob, then convert to base64 format
const response = await fetch(photo.webPath!);
const blob = await response.blob();
const base64Data = (await convertBlobToBase64(blob)) as string;

const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data,
});

// Use webPath to display the new image instead of base64 since it's
// already loaded into memory
return {
filepath: fileName,
webviewPath: photo.webPath,
};
};

// CHANGE: Add the `convertBlobToBase64()` method.
const convertBlobToBase64 = (blob: Blob) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onload = () => {
resolve(reader.result);
};
reader.readAsDataURL(blob);
});
};

return {
photos,
addNewToGallery,
};
};

export interface UserPhoto {
filepath: string;
webviewPath?: string;
}

usePhotoGallery.ts should now look like this:

import { ref, onMounted, watch } from 'vue';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';

export const usePhotoGallery = () => {
const photos = ref<UserPhoto[]>([]);

const addNewToGallery = async () => {
const capturedPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
});

const fileName = Date.now() + '.jpeg';
const savedImageFile = await savePicture(capturedPhoto, fileName);

photos.value = [savedImageFile, ...photos.value];
};

const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
// Fetch the photo, read as a blob, then convert to base64 format
const response = await fetch(photo.webPath!);
const blob = await response.blob();
const base64Data = (await convertBlobToBase64(blob)) as string;

const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data,
});

// Use webPath to display the new image instead of base64 since it's
// already loaded into memory
return {
filepath: fileName,
webviewPath: photo.webPath,
};
};

const convertBlobToBase64 = (blob: Blob) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onload = () => {
resolve(reader.result);
};
reader.readAsDataURL(blob);
});
};

return {
addNewToGallery,
photos,
};
};

export interface UserPhoto {
filepath: string;
webviewPath?: string;
}

Obtaining the camera photo as base64 format on the web appears to be a bit trickier than on mobile. In reality, we’re just using built-in web APIs: fetch() as a neat way to read the file into blob format, then FileReader’s readAsDataURL() to convert the photo blob to base64.

There we go! Each time a new photo is taken, it’s now automatically saved to the filesystem. Next up, we'll load and display our saved images.