Skip to content

Commit 290abfb

Browse files
committed
docs(react): update saving photos page
1 parent 7e9bb4d commit 290abfb

File tree

1 file changed

+138
-103
lines changed

1 file changed

+138
-103
lines changed
Lines changed: 138 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,115 @@
11
---
2+
title: Saving Photos to the Filesystem
23
sidebar_label: Saving Photos
34
---
45

56
# Saving Photos to the Filesystem
67

7-
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 lost.
8+
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.
89

910
## Filesystem API
1011

11-
Fortunately, saving them to the filesystem only takes a few steps. Begin by opening the `usePhotoGallery` hook (`src/hooks/usePhotoGallery.ts`), and get access to the `writeFile` method from the `Filesystem` class:
12+
Fortunately, saving them to the filesystem only takes a few steps. Begin by creating a new class method, `savePicture()`, in the `usePhotoGallery()` method.
1213

13-
:::note
14-
We will use the `writeFile` method initially, but we will use the others coming up shortly, so we'll go ahead and import them now.
15-
:::
16-
17-
Next, create a couple of new functions in `usePhotoGallery`:
18-
19-
```tsx
14+
```ts
2015
import { useState, useEffect } from 'react';
21-
import { isPlatform } from '@ionic/react';
2216
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
2317
import { Filesystem, Directory } from '@capacitor/filesystem';
2418
import { Preferences } from '@capacitor/preferences';
25-
import { Capacitor } from '@capacitor/core';
2619

2720
export function usePhotoGallery() {
2821
// Same old code from before.
2922

30-
// CHANGE: Add in new function to save pictures
23+
// CHANGE: Add the `savePicture()` method.
3124
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
32-
const base64Data = await base64FromPath(photo.webPath!);
33-
const savedFile = await Filesystem.writeFile({
34-
path: fileName,
35-
data: base64Data,
36-
directory: Directory.Data,
37-
});
38-
39-
// Use webPath to display the new image instead of base64 since it's
40-
// already loaded into memory
4125
return {
42-
filepath: fileName,
43-
webviewPath: photo.webPath,
26+
filepath: 'soon...',
27+
webviewPath: 'soon...',
4428
};
4529
};
4630

47-
// Same old code from before.
31+
return {
32+
addNewToGallery,
33+
photos,
34+
};
4835
}
4936

50-
// CHANGE: Add a function that allows the photo to be downloaded from the supplied path
51-
export async function base64FromPath(path: string): Promise<string> {
52-
const response = await fetch(path);
53-
const blob = await response.blob();
54-
return new Promise((resolve, reject) => {
55-
const reader = new FileReader();
56-
reader.onerror = reject;
57-
reader.onload = () => {
58-
if (typeof reader.result === 'string') {
59-
resolve(reader.result);
60-
} else {
61-
reject('method did not return a string');
62-
}
63-
};
64-
reader.readAsDataURL(blob);
65-
});
37+
export interface UserPhoto {
38+
filepath: string;
39+
webviewPath?: string;
6640
}
67-
68-
// Same old code from before.
6941
```
7042

71-
:::note
72-
The base64FromPath method is a helper util that downloads a file from the supplied path and returns a base64 representation of that file.
73-
:::
74-
75-
We pass in the `photo` object, which represents the newly captured device photo, as well as the fileName, which will provide a path for the file to be stored to.
43+
We can use this new method immediately in `addNewToGallery()`.
7644

77-
Next we use the Capacitor [Filesystem API](https://capacitorjs.com/docs/apis/filesystem) to save the photo to the filesystem. We start by converting the photo to base64 format, then feed the data to the Filesystem’s `writeFile` function.
78-
79-
Last, call `savePicture` and pass in the photo object and filename directly underneath the call to `setPhotos` in the `takePhoto` method. Here is the full method:
80-
81-
```tsx
82-
// Same old code from before.
45+
```ts
46+
import { useState, useEffect } from 'react';
47+
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
48+
import { Filesystem, Directory } from '@capacitor/filesystem';
49+
import { Preferences } from '@capacitor/preferences';
8350

8451
export function usePhotoGallery() {
85-
// Same old code from before.
52+
const [photos, setPhotos] = useState<UserPhoto[]>([]);
8653

87-
// CHANGE: Update the takePhoto function to utilize capacitor filesystem
88-
const takePhoto = async () => {
89-
const photo = await Camera.getPhoto({
54+
const addNewToGallery = async () => {
55+
// Take a photo
56+
const capturedPhoto = await Camera.getPhoto({
9057
resultType: CameraResultType.Uri,
9158
source: CameraSource.Camera,
9259
quality: 100,
9360
});
9461

95-
const newPhotos = [
96-
{
97-
filepath: fileName,
98-
webviewPath: photo.webPath,
99-
},
100-
...photos,
101-
];
102-
setPhotos(newPhotos);
62+
const fileName = Date.now() + '.jpeg';
63+
// CHANGE: Add `savedImageFile()`.
64+
// Save the picture and add it to photo collection
65+
const savedImageFile = await savePicture(capturedPhoto, fileName);
66+
67+
// CHANGE: Update state with new photo.
68+
setPhotos([savedImageFile, ...photos]);
10369
};
10470

105-
// Same old code from before
71+
// CHANGE: Add `savePicture()` method.
72+
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
73+
return {
74+
filepath: 'soon...',
75+
webviewPath: 'soon...',
76+
};
77+
};
78+
79+
return {
80+
addNewToGallery,
81+
photos,
82+
};
10683
}
10784

108-
// Same old code from before.
85+
export interface UserPhoto {
86+
filepath: string;
87+
webviewPath?: string;
88+
}
10989
```
11090

111-
`usePhotoGallery.ts` should now look like this:
91+
We'll use the Capacitor [Filesystem API](../../native/filesystem.md) to save the photo. First, convert the photo to base64 format.
11292

113-
```tsx
93+
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.
94+
95+
For now, create a new helper method, `convertBlobToBase64()`, to implement the necessary logic for running on the web.
96+
97+
```ts
11498
import { useState, useEffect } from 'react';
115-
import { isPlatform } from '@ionic/react';
11699
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
117100
import { Filesystem, Directory } from '@capacitor/filesystem';
118101
import { Preferences } from '@capacitor/preferences';
119-
import { Capacitor } from '@capacitor/core';
120102

121103
export function usePhotoGallery() {
122-
const [photos, setPhotos] = useState<UserPhoto[]>([]);
123-
const fileName = Date.now() + '.jpeg';
104+
// Same old code from before.
124105

106+
// CHANGE: Update the `savePicture()` method.
125107
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
126-
const base64Data = await base64FromPath(photo.webPath!);
108+
// Fetch the photo, read as a blob, then convert to base64 format
109+
const response = await fetch(photo.webPath!);
110+
const blob = await response.blob();
111+
const base64Data = (await convertBlobToBase64(blob)) as string;
112+
127113
const savedFile = await Filesystem.writeFile({
128114
path: fileName,
129115
data: base64Data,
@@ -138,50 +124,99 @@ export function usePhotoGallery() {
138124
};
139125
};
140126

141-
const takePhoto = async () => {
142-
const photo = await Camera.getPhoto({
127+
// CHANGE: Add the `convertBlobToBase64()` method.
128+
const convertBlobToBase64 = (blob: Blob) => {
129+
return new Promise((resolve, reject) => {
130+
const reader = new FileReader();
131+
reader.onerror = reject;
132+
reader.onload = () => {
133+
resolve(reader.result);
134+
};
135+
reader.readAsDataURL(blob);
136+
});
137+
};
138+
139+
return {
140+
addNewToGallery,
141+
photos,
142+
};
143+
}
144+
145+
export interface UserPhoto {
146+
filepath: string;
147+
webviewPath?: string;
148+
}
149+
```
150+
151+
`usePhotoGallery.ts` should now look like this:
152+
153+
```ts
154+
import { useState, useEffect } from 'react';
155+
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
156+
import { Filesystem, Directory } from '@capacitor/filesystem';
157+
import { Preferences } from '@capacitor/preferences';
158+
159+
export function usePhotoGallery() {
160+
const [photos, setPhotos] = useState<UserPhoto[]>([]);
161+
162+
const addNewToGallery = async () => {
163+
// Take a photo
164+
const capturedPhoto = await Camera.getPhoto({
143165
resultType: CameraResultType.Uri,
144166
source: CameraSource.Camera,
145167
quality: 100,
146168
});
147169

148-
const newPhotos = [
149-
{
150-
filepath: fileName,
151-
webviewPath: photo.webPath,
152-
},
153-
...photos,
154-
];
155-
setPhotos(newPhotos);
170+
const fileName = Date.now() + '.jpeg';
171+
// Save the picture and add it to photo collection
172+
const savedImageFile = await savePicture(capturedPhoto, fileName);
173+
174+
setPhotos([savedImageFile, ...photos]);
175+
};
176+
177+
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
178+
// Fetch the photo, read as a blob, then convert to base64 format
179+
const response = await fetch(photo.webPath!);
180+
const blob = await response.blob();
181+
const base64Data = (await convertBlobToBase64(blob)) as string;
182+
183+
const savedFile = await Filesystem.writeFile({
184+
path: fileName,
185+
data: base64Data,
186+
directory: Directory.Data,
187+
});
188+
189+
// Use webPath to display the new image instead of base64 since it's
190+
// already loaded into memory
191+
return {
192+
filepath: fileName,
193+
webviewPath: photo.webPath,
194+
};
195+
};
196+
197+
const convertBlobToBase64 = (blob: Blob) => {
198+
return new Promise((resolve, reject) => {
199+
const reader = new FileReader();
200+
reader.onerror = reject;
201+
reader.onload = () => {
202+
resolve(reader.result);
203+
};
204+
reader.readAsDataURL(blob);
205+
});
156206
};
157207

158208
return {
209+
addNewToGallery,
159210
photos,
160-
takePhoto,
161211
};
162212
}
163213

164-
export async function base64FromPath(path: string): Promise<string> {
165-
const response = await fetch(path);
166-
const blob = await response.blob();
167-
return new Promise((resolve, reject) => {
168-
const reader = new FileReader();
169-
reader.onerror = reject;
170-
reader.onload = () => {
171-
if (typeof reader.result === 'string') {
172-
resolve(reader.result);
173-
} else {
174-
reject('method did not return a string');
175-
}
176-
};
177-
reader.readAsDataURL(blob);
178-
});
179-
}
180-
181214
export interface UserPhoto {
182215
filepath: string;
183216
webviewPath?: string;
184217
}
185218
```
186219

187-
There we go! Each time a new photo is taken, it’s now automatically saved to the filesystem.
220+
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()](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) as a neat way to read the file into blob format, then FileReader’s [readAsDataURL()](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL) to convert the photo blob to base64.
221+
222+
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.

0 commit comments

Comments
 (0)