Skip to content

Commit 6072689

Browse files
committed
docs(react): update adding mobile page
1 parent a386a0c commit 6072689

File tree

1 file changed

+139
-123
lines changed

1 file changed

+139
-123
lines changed
Lines changed: 139 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,184 @@
11
---
2+
title: Adding Mobile
23
sidebar_label: Adding Mobile
34
---
45

6+
<head>
7+
<title>Adding Mobile Support with React | Ionic Capacitor Camera</title>
8+
<meta
9+
name="description"
10+
content="Learn how to add mobile support to your Ionic Capacitor photo gallery app, enabling it to run on iOS, Android, and the web using one codebase."
11+
/>
12+
</head>
13+
514
# Adding Mobile
615

716
Our photo gallery app won’t be complete until it runs on iOS, Android, and the web - all using one codebase. All it takes is some small logic changes to support mobile platforms, installing some native tooling, then running the app on a device. Let’s go!
817

18+
## Import Platform API
19+
920
Let’s start with making some small code changes - then our app will “just work” when we deploy it to a device.
1021

11-
## Platform-specific Logic
22+
Import the Ionic [Platform API](../platform.md) into `usePhotoGallery.ts`, which is used to retrieve information about the current device. In this case, it’s useful for selecting which code to execute based on the platform the app is running on (web or mobile).
23+
24+
Add `isPlatform` to the imports at the top of the file to use the `isPlatform` method. `Capacitor` is also imported to help with file paths on mobile devices.
1225

13-
First, we’ll update the photo saving functionality to support mobile. In the `savePicture` function, check which platform the app is running on. If it’s “hybrid” (Capacitor or Cordova, the two native runtimes), then read the photo file into base64 format using the `readFile` method. Also, return the complete file path to the photo using the Filesystem API. When setting the `webviewPath`, use the special `Capacitor.convertFileSrc` method ([details here](https://ionicframework.com/docs/core-concepts/webview#file-protocol)). Otherwise, use the same logic as before when running the app on the web.
26+
```ts
27+
import { useState, useEffect } from 'react';
28+
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
29+
import { Filesystem, Directory } from '@capacitor/filesystem';
30+
import { Preferences } from '@capacitor/preferences';
31+
// CHANGE: Add imports.
32+
import { isPlatform } from '@ionic/react';
33+
import { Capacitor } from '@capacitor/core';
1434

15-
```tsx
1635
// Same old code from before.
17-
export function usePhotoGallery() {
18-
// Same old code from before.
36+
```
1937

20-
// CHANGE: Update savePicture function
21-
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
22-
let base64Data: string | Blob;
23-
// "hybrid" will detect Cordova or Capacitor;
24-
if (isPlatform('hybrid')) {
38+
## Platform-specific Logic
39+
40+
First, we’ll update the photo saving functionality to support mobile. In the `savePicture` method, check which platform the app is running on. If it’s “hybrid” (Capacitor, the native runtime), then read the photo file into base64 format using the `Filesystem`'s' `readFile()` method. Otherwise, use the same logic as before when running the app on the web.
41+
42+
Update `savePicture` to look like the following:
43+
44+
```ts
45+
// CHANGE: Update the `savePicture()` method.
46+
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
47+
let base64Data: string | Blob;
48+
// CHANGE: Add platform check.
49+
// "hybrid" will detect mobile - iOS or Android
50+
if (isPlatform('hybrid')) {
51+
const file = await Filesystem.readFile({
52+
path: photo.path!,
53+
});
54+
base64Data = file.data;
55+
} else {
56+
// Fetch the photo, read as a blob, then convert to base64 format
57+
const response = await fetch(photo.webPath!);
58+
const blob = await response.blob();
59+
base64Data = (await convertBlobToBase64(blob)) as string;
60+
}
61+
62+
const savedFile = await Filesystem.writeFile({
63+
path: fileName,
64+
data: base64Data,
65+
directory: Directory.Data,
66+
});
67+
68+
// CHANGE: Add platform check.
69+
if (isPlatform('hybrid')) {
70+
// Display the new image by rewriting the 'file://' path to HTTP
71+
return {
72+
filepath: savedFile.uri,
73+
webviewPath: Capacitor.convertFileSrc(savedFile.uri),
74+
};
75+
} else {
76+
// Use webPath to display the new image instead of base64 since it's
77+
// already loaded into memory
78+
return {
79+
filepath: fileName,
80+
webviewPath: photo.webPath,
81+
};
82+
}
83+
};
84+
```
85+
86+
Next, add a new bit of logic in the `loadSaved()` method. On mobile, we can directly point to each photo file on the Filesystem and display them automatically. On the web, however, we must read each image from the Filesystem into base64 format. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Update the `loadSaved()` method:
87+
88+
```ts
89+
// CHANGE: Update the `loadSaved` method.
90+
const loadSaved = async () => {
91+
const { value: photoList } = await Preferences.get({ key: PHOTO_STORAGE });
92+
const photosInPreferences = (photoList ? JSON.parse(photoList) : []) as UserPhoto[];
93+
94+
// CHANGE: Add platform check.
95+
// If running on the web...
96+
if (!isPlatform('hybrid')) {
97+
for (const photo of photosInPreferences) {
2598
const file = await Filesystem.readFile({
26-
path: photo.path!,
99+
path: photo.filepath,
100+
directory: Directory.Data,
27101
});
28-
base64Data = file.data;
29-
} else {
30-
base64Data = await base64FromPath(photo.webPath!);
102+
photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
31103
}
32-
const savedFile = await Filesystem.writeFile({
33-
path: fileName,
34-
data: base64Data,
35-
directory: Directory.Data,
36-
});
104+
}
37105

38-
if (isPlatform('hybrid')) {
39-
// Display the new image by rewriting the 'file://' path to HTTP
40-
// Details: https://ionicframework.com/docs/building/webview#file-protocol
41-
return {
42-
filepath: savedFile.uri,
43-
webviewPath: Capacitor.convertFileSrc(savedFile.uri),
44-
};
45-
} else {
46-
// Use webPath to display the new image instead of base64 since it's
47-
// already loaded into memory
48-
return {
49-
filepath: fileName,
50-
webviewPath: photo.webPath,
51-
};
52-
}
53-
};
106+
setPhotos(photosInPreferences);
107+
};
108+
```
54109

55-
// Same old code from before.
56-
}
110+
Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS.
57111

58-
// Same old code from before.
59-
```
112+
`usePhotoGallery.ts` should now look like this:
60113

61-
Next, add a new bit of logic in the `loadSaved` function. On mobile, we can directly point to each photo file on the Filesystem and display them automatically. On the web, however, we must read each image from the Filesystem into base64 format. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Update the `loadSaved` function inside of `useEffect` to:
114+
```ts
115+
import { useState, useEffect } from 'react';
116+
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
117+
import { Filesystem, Directory } from '@capacitor/filesystem';
118+
import { Preferences } from '@capacitor/preferences';
119+
import { isPlatform } from '@ionic/react';
120+
import { Capacitor } from '@capacitor/core';
62121

63-
```tsx
64-
// Same old code from before.
65122
export function usePhotoGallery() {
66-
// Same old code from before.
123+
const [photos, setPhotos] = useState<UserPhoto[]>([]);
124+
125+
const PHOTO_STORAGE = 'photos';
67126

68127
useEffect(() => {
69-
// CHANGE: Update loadSaved function within useEffect
70128
const loadSaved = async () => {
71-
const { value } = await Preferences.get({ key: PHOTO_STORAGE });
129+
const { value: photoList } = await Preferences.get({ key: PHOTO_STORAGE });
130+
const photosInPreferences = (photoList ? JSON.parse(photoList) : []) as UserPhoto[];
72131

73-
const photosInPreferences = (value ? JSON.parse(value) : []) as UserPhoto[];
74132
// If running on the web...
75133
if (!isPlatform('hybrid')) {
76-
for (let photo of photosInPreferences) {
134+
for (const photo of photosInPreferences) {
77135
const file = await Filesystem.readFile({
78136
path: photo.filepath,
79137
directory: Directory.Data,
80138
});
81-
// Web platform only: Load the photo as base64 data
82139
photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
83140
}
84141
}
142+
85143
setPhotos(photosInPreferences);
86144
};
87-
}, []);
88-
89-
// Same old code from before.
90-
}
91145

92-
// Same old code from before.
93-
```
146+
loadSaved();
147+
}, []);
94148

95-
Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS. Next up, the part you’ve been waiting for - deploying the app to a device.
149+
const addNewToGallery = async () => {
150+
// Take a photo
151+
const capturedPhoto = await Camera.getPhoto({
152+
resultType: CameraResultType.Uri,
153+
source: CameraSource.Camera,
154+
quality: 100,
155+
});
96156

97-
`usePhotoGallery.ts` should now look like this:
157+
const fileName = Date.now() + '.jpeg';
158+
// Save the picture and add it to photo collection
159+
const savedImageFile = await savePicture(capturedPhoto, fileName);
98160

99-
```tsx
100-
import { useState, useEffect } from 'react';
101-
import { isPlatform } from '@ionic/react';
102-
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
103-
import { Filesystem, Directory } from '@capacitor/filesystem';
104-
import { Preferences } from '@capacitor/preferences';
105-
import { Capacitor } from '@capacitor/core';
161+
const newPhotos = [savedImageFile, ...photos];
162+
setPhotos(newPhotos);
106163

107-
const PHOTO_STORAGE = 'photos';
164+
Preferences.set({ key: PHOTO_STORAGE, value: JSON.stringify(newPhotos) });
165+
};
108166

109-
export function usePhotoGallery() {
110-
const [photos, setPhotos] = useState<UserPhoto[]>([]);
111-
const fileName = Date.now() + '.jpeg';
112167
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
113168
let base64Data: string | Blob;
114-
// "hybrid" will detect Cordova or Capacitor;
169+
// "hybrid" will detect mobile - iOS or Android
115170
if (isPlatform('hybrid')) {
116171
const file = await Filesystem.readFile({
117172
path: photo.path!,
118173
});
119174
base64Data = file.data;
120175
} else {
121-
base64Data = await base64FromPath(photo.webPath!);
176+
// Fetch the photo, read as a blob, then convert to base64 format
177+
const response = await fetch(photo.webPath!);
178+
const blob = await response.blob();
179+
base64Data = (await convertBlobToBase64(blob)) as string;
122180
}
181+
123182
const savedFile = await Filesystem.writeFile({
124183
path: fileName,
125184
data: base64Data,
@@ -128,7 +187,6 @@ export function usePhotoGallery() {
128187

129188
if (isPlatform('hybrid')) {
130189
// Display the new image by rewriting the 'file://' path to HTTP
131-
// Details: https://ionicframework.com/docs/building/webview#file-protocol
132190
return {
133191
filepath: savedFile.uri,
134192
webviewPath: Capacitor.convertFileSrc(savedFile.uri),
@@ -143,69 +201,27 @@ export function usePhotoGallery() {
143201
}
144202
};
145203

146-
useEffect(() => {
147-
const loadSaved = async () => {
148-
const { value } = await Preferences.get({ key: PHOTO_STORAGE });
149-
150-
const photosInPreferences = (value ? JSON.parse(value) : []) as UserPhoto[];
151-
// If running on the web...
152-
if (!isPlatform('hybrid')) {
153-
for (let photo of photosInPreferences) {
154-
const file = await Filesystem.readFile({
155-
path: photo.filepath,
156-
directory: Directory.Data,
157-
});
158-
// Web platform only: Load the photo as base64 data
159-
photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
160-
}
161-
}
162-
setPhotos(photosInPreferences);
163-
};
164-
}, []);
165-
166-
const takePhoto = async () => {
167-
const photo = await Camera.getPhoto({
168-
resultType: CameraResultType.Uri,
169-
source: CameraSource.Camera,
170-
quality: 100,
204+
const convertBlobToBase64 = (blob: Blob) => {
205+
return new Promise((resolve, reject) => {
206+
const reader = new FileReader();
207+
reader.onerror = reject;
208+
reader.onload = () => {
209+
resolve(reader.result);
210+
};
211+
reader.readAsDataURL(blob);
171212
});
172-
173-
const newPhotos = [
174-
{
175-
filepath: fileName,
176-
webviewPath: photo.webPath,
177-
},
178-
...photos,
179-
];
180-
setPhotos(newPhotos);
181-
Preferences.set({ key: PHOTO_STORAGE, value: JSON.stringify(newPhotos) });
182213
};
183214

184215
return {
216+
addNewToGallery,
185217
photos,
186-
takePhoto,
187218
};
188219
}
189220

190-
export async function base64FromPath(path: string): Promise<string> {
191-
const response = await fetch(path);
192-
const blob = await response.blob();
193-
return new Promise((resolve, reject) => {
194-
const reader = new FileReader();
195-
reader.onerror = reject;
196-
reader.onload = () => {
197-
if (typeof reader.result === 'string') {
198-
resolve(reader.result);
199-
} else {
200-
reject('method did not return a string');
201-
}
202-
};
203-
reader.readAsDataURL(blob);
204-
});
205-
}
206-
207221
export interface UserPhoto {
208222
filepath: string;
209223
webviewPath?: string;
210224
}
211225
```
226+
227+
Next up, the part you’ve been waiting for - deploying the app to a device.

0 commit comments

Comments
 (0)