Skip to content

Commit 6a21dbf

Browse files
docs(vue): add context to code blocks and small changes to surrounding text
1 parent 2233653 commit 6a21dbf

File tree

1 file changed

+250
-25
lines changed

1 file changed

+250
-25
lines changed

docs/vue/your-first-app/4-loading-photos.md

Lines changed: 250 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,57 +13,282 @@ Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](http
1313
Begin by defining a constant variable that will act as the key for the store at the top of the `usePhotoGallery` function in `src/composables/usePhotoGallery.ts`:
1414

1515
```tsx
16-
const PHOTO_STORAGE = 'photos';
16+
export const usePhotoGallery = () => {
17+
// CHANGE: Add the `PHOTO_STORAGE` key.
18+
const PHOTO_STORAGE = 'photos';
19+
const photos = ref<UserPhoto[]>([]);
20+
21+
const takePhoto = async () => {
22+
// Same old code from before.
23+
};
24+
25+
const convertBlobToBase64 = (blob: Blob) => {
26+
// Same old code from before.
27+
};
28+
29+
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
30+
// Same old code from before.
31+
};
32+
33+
return {
34+
photos,
35+
takePhoto
36+
};
37+
};
1738
```
1839

19-
Next, add a `cachePhotos` function that saves the Photos array as JSON to preferences:
40+
Next, add a `cachePhotos` method that saves the Photos array as JSON to preferences:
2041

2142
```tsx
22-
const cachePhotos = () => {
23-
Preferences.set({
24-
key: PHOTO_STORAGE,
25-
value: JSON.stringify(photos.value),
26-
});
43+
export const usePhotoGallery = () => {
44+
const PHOTO_STORAGE = 'photos';
45+
const photos = ref<UserPhoto[]>([]);
46+
47+
const takePhoto = async () => {
48+
// Same old code from before.
49+
};
50+
51+
const convertBlobToBase64 = (blob: Blob) => {
52+
// Same old code from before.
53+
};
54+
55+
// CHANGE: Add the `cachePhotos` method.
56+
const cachePhotos = () => {
57+
Preferences.set({
58+
key: PHOTO_STORAGE,
59+
value: JSON.stringify(photos.value),
60+
});
61+
};
62+
63+
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
64+
// Same old code from before.
65+
};
66+
67+
return {
68+
photos,
69+
takePhoto
70+
};
2771
};
2872
```
2973

30-
Next, use the Vue [watch function](https://v3.vuejs.org/guide/composition-api-introduction.html#reacting-to-changes-with-watch) to watch the `photos` array. Whenever the array is modified (in this case, taking or deleting photos), trigger the `cachePhotos` function. Not only do we get to reuse code, but it also doesn’t matter when the app user closes or switches to a different app - photo data is always saved.
74+
Next, use the Vue [watch function](https://vuejs.org/api/reactivity-core.html#watch) to watch the `photos` array. Whenever the array is modified (in this case, taking or deleting photos), trigger the `cachePhotos` method. Not only do we get to reuse code, but it also doesn’t matter when the app user closes or switches to a different app - photo data is always saved.
75+
76+
Add the call to the `watch` function above the return statement in `usePhotoGallery`:
3177

3278
```tsx
33-
watch(photos, cachePhotos);
79+
export const usePhotoGallery = () => {
80+
const PHOTO_STORAGE = 'photos';
81+
const photos = ref<UserPhoto[]>([]);
82+
83+
const takePhoto = async () => {
84+
// Same old code from before.
85+
};
86+
87+
const convertBlobToBase64 = (blob: Blob) => {
88+
// Same old code from before.
89+
};
90+
91+
const cachePhotos = () => {
92+
// Same old code from before.
93+
};
94+
95+
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
96+
// Same old code from before.
97+
};
98+
99+
// CHANGE: Add call to `watch` with `photos` array and `cachePhotos` method.
100+
watch(photos, cachePhotos);
101+
102+
return {
103+
photos,
104+
takePhoto
105+
};
106+
};
34107
```
35108

36-
Now that the photo array data is saved, create a function to retrieve the data when Tab2 loads. First, retrieve photo data from Preferences, then each photo's data into base64 format:
109+
Now that the photo array data is saved, we need a way to retrieve the data when Tab2 loads. Create a new method in `usePhotoGallery` called `loadSaved` which will first retrieve photo data from Preferences, then convert each photo's data to base64 format:
37110

38111
```tsx
39-
const loadSaved = async () => {
40-
const photoList = await Preferences.get({ key: PHOTO_STORAGE });
41-
const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
112+
export const usePhotoGallery = () => {
113+
const PHOTO_STORAGE = 'photos';
114+
const photos = ref<UserPhoto[]>([]);
42115

43-
for (const photo of photosInPreferences) {
44-
const file = await Filesystem.readFile({
45-
path: photo.filepath,
46-
directory: Directory.Data,
47-
});
48-
photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
49-
}
116+
const takePhoto = async () => {
117+
// Same old code from before.
118+
};
119+
120+
const convertBlobToBase64 = (blob: Blob) => {
121+
// Same old code from before.
122+
};
50123

51-
photos.value = photosInPreferences;
124+
const cachePhotos = () => {
125+
// Same old code from before.
126+
};
127+
128+
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
129+
// Same old code from before.
130+
};
131+
132+
// CHANGE: Add the `loadSaved` method.
133+
const loadSaved = async () => {
134+
const photoList = await Preferences.get({ key: PHOTO_STORAGE });
135+
const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
136+
137+
for (const photo of photosInPreferences) {
138+
const file = await Filesystem.readFile({
139+
path: photo.filepath,
140+
directory: Directory.Data,
141+
});
142+
photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
143+
}
144+
145+
photos.value = photosInPreferences;
146+
};
147+
148+
watch(photos, cachePhotos);
149+
150+
return {
151+
photos,
152+
takePhoto
153+
};
52154
};
53155
```
54156

55157
On mobile (coming up next!), we can directly set the source of an image tag - `<img src="x" />` - to each photo file on the Filesystem, displaying them automatically. On the web, however, we must read each image from the Filesystem into base64 format, because the Filesystem API stores them in base64 within [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood.
56158

57-
Finally, we need a way to call the `loadSaved` function when the Photo Gallery page is loaded. To do so, use the Vue [mounted lifecycle hook](https://v3.vuejs.org/guide/composition-api-introduction.html#lifecycle-hook-registration-inside-setup). Earlier we had already imported `onMounted` from Vue:
159+
Finally, we need a way to call the `loadSaved` method when the Photo Gallery page is loaded. To do so, use the Vue [mounted lifecycle hook](https://vuejs.org/api/options-lifecycle.html#mounted). Above the `usePhotoGallery` return statement where we added the call to `watch` earlier, add a call to the `onMounted` function and pass in the `loadSaved` method created above:
58160

59161
```tsx
60-
import { ref, onMounted, watch } from 'vue';
162+
export const usePhotoGallery = () => {
163+
const PHOTO_STORAGE = 'photos';
164+
const photos = ref<UserPhoto[]>([]);
165+
166+
const takePhoto = async () => {
167+
// Same old code from before.
168+
};
169+
170+
const convertBlobToBase64 = (blob: Blob) => {
171+
// Same old code from before.
172+
};
173+
174+
const cachePhotos = () => {
175+
// Same old code from before.
176+
};
177+
178+
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
179+
// Same old code from before.
180+
};
181+
182+
const loadSaved = async () => {
183+
// Same old code from before.
184+
};
185+
186+
// CHANGE: Add call to `onMounted` with the `loadSaved` method.
187+
onMounted(loadSaved);
188+
watch(photos, cachePhotos);
189+
190+
return {
191+
photos,
192+
takePhoto
193+
};
194+
};
61195
```
62196

63-
Within the `usePhotoGallery` function, add the `onMounted` function and call `loadSaved`:
197+
After these updates to the `usePhotoGallery` function, your `usePhotoGallery.ts` file should look like this:
64198

65199
```tsx
66-
onMounted(loadSaved);
200+
import { ref, onMounted, watch } from 'vue';
201+
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
202+
import { Filesystem, Directory } from '@capacitor/filesystem';
203+
import { Preferences } from '@capacitor/preferences';
204+
205+
export const usePhotoGallery = () => {
206+
const PHOTO_STORAGE = 'photos';
207+
const photos = ref<UserPhoto[]>([]);
208+
209+
const takePhoto = async () => {
210+
const photo = await Camera.getPhoto({
211+
resultType: CameraResultType.Uri,
212+
source: CameraSource.Camera,
213+
quality: 100,
214+
});
215+
const fileName = Date.now() + '.jpeg';
216+
const savedFileImage = await savePicture(photo, fileName);
217+
218+
photos.value = [savedFileImage, ...photos.value];
219+
};
220+
221+
const convertBlobToBase64 = (blob: Blob) =>
222+
new Promise((resolve, reject) => {
223+
const reader = new FileReader();
224+
reader.onerror = reject;
225+
reader.onload = () => {
226+
resolve(reader.result);
227+
};
228+
reader.readAsDataURL(blob);
229+
});
230+
231+
const cachePhotos = () => {
232+
Preferences.set({
233+
key: PHOTO_STORAGE,
234+
value: JSON.stringify(photos.value),
235+
});
236+
};
237+
238+
const savePicture = async (photo: Photo, fileName: string): Promise<UserPhoto> => {
239+
// Fetch the photo, read as a blob, then convert to base64 format
240+
const response = await fetch(photo.webPath!);
241+
const blob = await response.blob();
242+
const base64Data = (await convertBlobToBase64(blob)) as string;
243+
244+
const savedFile = await Filesystem.writeFile({
245+
path: fileName,
246+
data: base64Data,
247+
directory: Directory.Data,
248+
});
249+
250+
// Use webPath to display the new image instead of base64 since it's
251+
// already loaded into memory
252+
return {
253+
filepath: fileName,
254+
webviewPath: photo.webPath,
255+
};
256+
};
257+
258+
const loadSaved = async () => {
259+
const photoList = await Preferences.get({ key: PHOTO_STORAGE });
260+
const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
261+
262+
for (const photo of photosInPreferences) {
263+
const file = await Filesystem.readFile({
264+
path: photo.filepath,
265+
directory: Directory.Data,
266+
});
267+
photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
268+
}
269+
270+
photos.value = photosInPreferences;
271+
};
272+
273+
onMounted(loadSaved);
274+
watch(photos, cachePhotos);
275+
276+
return {
277+
photos,
278+
takePhoto
279+
};
280+
};
281+
282+
export interface UserPhoto {
283+
filepath: string;
284+
webviewPath?: string;
285+
}
67286
```
68287

288+
:::note
289+
If you're seeing broken image links or missing photos after following these steps, you may need to open your browser's dev tools and clear both [localStorage](https://developer.chrome.com/docs/devtools/storage/localstorage) and [IndexedDB](https://developer.chrome.com/docs/devtools/storage/indexeddb).
290+
291+
In localStorage, look for domain `http://localhost:8100` and key `CapacitorStorage.photos`. In IndexedDB, find a store called "FileStorage". Your photos will have a key like `/DATA/123456789012.jpeg`.
292+
:::
293+
69294
That’s it! We’ve built a complete Photo Gallery feature in our Ionic app that works on the web. Next up, we’ll transform it into a mobile app for iOS and Android!

0 commit comments

Comments
 (0)