diff --git a/docs/vue/your-first-app.md b/docs/vue/your-first-app.md
index 1ebeda7d1b4..9eb99df9f32 100644
--- a/docs/vue/your-first-app.md
+++ b/docs/vue/your-first-app.md
@@ -4,10 +4,10 @@ sidebar_label: Build Your First App
---
- Vue Step-by-Step Tutorial: Run Your First Ionic App with Vue
+ Build Your First Ionic Mobile App: Vue Development Tutorial
@@ -30,9 +30,9 @@ We'll create a Photo Gallery app that offers the ability to take photos with you
Highlights include:
-- One Vue-based codebase that runs on the web, iOS, and Android using Ionic Framework [UI components](https://ionicframework.com/docs/components).
+- One Vue-based codebase that runs on the web, iOS, and Android using Ionic Framework [UI components](../components.md).
- Deployed as a native iOS and Android mobile app using [Capacitor](https://capacitorjs.com), Ionic's official native app runtime.
-- Photo Gallery functionality powered by the Capacitor [Camera](https://capacitorjs.com/docs/apis/camera), [Filesystem](https://capacitorjs.com/docs/apis/filesystem), and [Preferences](https://capacitorjs.com/docs/apis/preferences) APIs.
+- Photo Gallery functionality powered by the Capacitor [Camera](../native/camera.md), [Filesystem](../native/filesystem.md), and [Preferences](../native/preferences.md) APIs.
Find the complete app code referenced in this guide [on GitHub](https://github.com/ionic-team/photo-gallery-capacitor-vue).
@@ -43,9 +43,8 @@ Download and install these right away to ensure an optimal Ionic development exp
- **Node.js** for interacting with the Ionic ecosystem. [Download the LTS version here](https://nodejs.org/en/).
- **A code editor** for... writing code! We are fans of [Visual Studio Code](https://code.visualstudio.com/).
- **Command-line interface/terminal (CLI)**:
- - **Windows** users: for the best Ionic experience, we recommend the built-in command line (cmd) or the Powershell
- CLI, running in Administrator mode.
- - **Mac/Linux** users, virtually any terminal will work.
+ - **Windows** users: for the best Ionic experience, we recommend the built-in command line (cmd) or the Powershell CLI, running in Administrator mode.
+ - **Mac/Linux** users: virtually any terminal will work.
## Install Ionic Tooling
@@ -56,7 +55,7 @@ To open a terminal in Visual Studio Code, go to Terminal -> New Terminal.
:::
```shell
-npm install -g @ionic/cli@latest native-run
+npm install -g @ionic/cli native-run cordova-res
```
:::note
@@ -89,7 +88,7 @@ npm install @capacitor/camera @capacitor/preferences @capacitor/filesystem
### PWA Elements
-Some Capacitor plugins, including the Camera API, provide the web-based functionality and UI via the Ionic [PWA Elements library](https://github.com/ionic-team/pwa-elements).
+Some Capacitor plugins, including the [Camera API](../native/camera.md), provide the web-based functionality and UI via the Ionic [PWA Elements library](https://github.com/ionic-team/pwa-elements).
It's a separate dependency, so install it next:
@@ -97,21 +96,37 @@ It's a separate dependency, so install it next:
npm install @ionic/pwa-elements
```
-After installation, open up the project in your code editor of choice.
-
Next, import `@ionic/pwa-elements` by editing `src/main.ts`.
-```tsx
-// Above the createApp() line
+```ts
+import { createApp } from 'vue';
+import App from './App.vue';
+import router from './router';
+
+import { IonicVue } from '@ionic/vue';
+// CHANGE: Add the following import.
import { defineCustomElements } from '@ionic/pwa-elements/loader';
+
+/* Ionic styles are not shown in this example to keep it brief but will be included in the Ionic package downloaded for your app. Do not remove them. */
+
+/* Theme variables */
+import './theme/variables.css';
+
+// CHANGE: Call the element loader before the createApp() call
defineCustomElements(window);
+
+const app = createApp(App).use(IonicVue).use(router);
+
+router.isReady().then(() => {
+ app.mount('#app');
+});
```
That’s it! Now for the fun part - let’s see the app in action.
## Run the App
-Run this command in your shell:
+Run this command next:
```shell
ionic serve
@@ -121,13 +136,13 @@ And voilà! Your Ionic app is now running in a web browser. Most of your app can
## Photo Gallery!!!
-There are three tabs. Click on the Tab2 tab. It’s a blank canvas, aka the perfect spot to transform into a Photo Gallery. The Ionic CLI features Live Reload, so when you make changes and save them, the app is updated immediately!
+There are three tabs. Click on the "Tab2" tab. It’s a blank canvas, aka the perfect spot to transform into a Photo Gallery. The Ionic CLI features Live Reload, so when you make changes and save them, the app is updated immediately!

-Open `/src/views/Tab2.vue`. We see:
+Open `/src/views/Tab2Page.vue`. We see:
-```html
+```vue
@@ -146,30 +161,53 @@ Open `/src/views/Tab2.vue`. We see:
+
+
```
-`ion-header` represents the top navigation and toolbar, with "Tab 2" as the title. Let’s rename it:
+`ion-header` represents the top navigation and toolbar, with "Tab 2" as the title (there are two of them due to iOS [Collapsible Large Title](../api/title.md#collapsible-large-titles) support). Rename both `ion-title` elements to:
-```html
+```vue
Photo Gallery
```
-We put the visual aspects of our app into ``. In this case, it’s where we’ll add a button that opens the device’s camera as well as displays the image captured by the camera. But first, remove the `ExploreContainer` component, beginning with the import statement:
+We put the visual aspects of our app into ``. In this case, it’s where we’ll add a button that opens the device’s camera as well as displays the image captured by the camera. Start by adding a [floating action button](../api/fab.md) (FAB) to the bottom of the page and set the camera image as the icon.
-```tsx
-import ExploreContainer from '@/components/ExploreContainer.vue';
-```
-
-Next, remove the `ExploreContainer` node from the HTML markup in the `template`.
+```vue
+
+
+
+
+ Photo Gallery
+
+
+
+
+
+ Photo Gallery
+
+
-```html
-
-```
+
+
+
+
+
+
-We'll replace it with a [floating action button](https://ionicframework.com/docs/api/fab) (FAB). First, update the imports within the `
```
-Since our pages are generated as [Vue Single File Components](https://vuejs.org/api/sfc-spec.html) using the [`
```
-Within the tab bar (``), change the label to "Photos" and the `ellipse` icon to `images` for the middle tab button:
-
-```html
-
-
- Photos
-
-```
-
-That’s just the start of all the cool things we can do with Ionic. Up next, implementing camera taking functionality on the web, then building for iOS and Android.
+That’s just the start of all the cool things we can do with Ionic. Up next, implement camera taking functionality on the web, then build it for iOS and Android.
diff --git a/docs/vue/your-first-app/2-taking-photos.md b/docs/vue/your-first-app/2-taking-photos.md
index 859c3a4565a..a340deeb7e3 100644
--- a/docs/vue/your-first-app/2-taking-photos.md
+++ b/docs/vue/your-first-app/2-taking-photos.md
@@ -1,34 +1,35 @@
---
+title: Taking Photos with the Camera
sidebar_label: Taking Photos
---
-# Taking Photos with the Camera
+
+ Build Camera API for iOS, Android & Web | Ionic Capacitor Camera
+
+
-Now for the fun part - adding the ability to take photos with the device’s camera using the Capacitor [Camera API](https://capacitorjs.com/docs/apis/camera). We’ll begin with building it for the web, then make some small tweaks to make it work on mobile (iOS and Android).
+Now for the fun part - adding the ability to take photos with the device’s camera using the Capacitor [Camera API](../../native/camera.md). We’ll begin with building it for the web, then make some small tweaks to make it work on mobile (iOS and Android).
-To do so, we will create a standalone composition function paired with Vue's Composition API to manage the photos for the gallery.
+## Photo Gallery Composable
-:::note
-If you are not familiar with Vue's Composition API, [Why Composition API?](https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api) from the official Vue docs is a good resource to start with.
-:::
+We will create a standalone composition method paired with [Vue's Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api) to manage the photos for the gallery.
Create a new file at `src/composables/usePhotoGallery.ts` and open it up.
-We will start by importing the various utilities we will use from Vue core and Capacitor:
+Next, define a new method, `usePhotoGallery()`, that will contain the core logic to take a device photo and save it to the filesystem. Let’s start by opening the device camera:
-```typescript
+```ts
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';
-```
-
-Next, create a function named usePhotoGallery:
-```typescript
export const usePhotoGallery = () => {
- const takePhoto = async () => {
- const photo = await Camera.getPhoto({
+ const addNewToGallery = async () => {
+ const capturedPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
@@ -36,120 +37,235 @@ export const usePhotoGallery = () => {
};
return {
- takePhoto,
+ addNewToGallery,
};
};
```
-Our `usePhotoGallery` function exposes a method called takePhoto, which in turn calls the Capacitor Camera API's `getPhoto` method.
-
Notice the magic here: there's no platform-specific code (web, iOS, or Android)! The Capacitor Camera plugin abstracts that away for us, leaving just one method call - `getPhoto()` - that will open up the device's camera and allow us to take photos.
-The last step we need to take is to use the new function from the Tab2 page. Go back to `Tab2Page.vue` and import it:
-
-```tsx
-import { usePhotoGallery } from '@/composables/usePhotoGallery';
-```
+Next, in `Tab2Page.vue`, import the `usePhotoGallery` method and destructure it to call its `addNewToGallery` method.
+
+```vue
+
+
+
+
+ Photo Gallery
+
+
+
+
+
+ Photo Gallery
+
+
+
+
+
+
+
+
+
+
+
+
-Destructure the `takePhoto` function from `usePhotoGallery` so we can use it in our `template`:
-
-```tsx
```
-Save the file. Start the development server via `ionic serve` if it is not already running. In your browser, on the Photo Gallery tab, click the Camera button. If your computer has a webcam of any sort, a modal window appears. Take a selfie!
+If it's not running already, restart the development server in your browser by running `ionic serve`. On the Photo Gallery tab, click the Camera button. If your computer has a webcam of any sort, a modal window appears. Take a selfie!

_(Your selfie is probably much better than mine)_
-After taking a photo, it disappears right away. We still need to display it within our app and save it for future access.
+After taking a photo, it disappears right away. We need to display it within our app and save it for future access.
## Displaying Photos
-First we will create a new type to define our Photo, which will hold specific metadata. Add the following UserPhoto interface to the `usePhotoGallery.ts` file, somewhere outside of the main function:
+Return to `usePhotoGallery.ts`.
+
+Outside of the `usePhotoGallery` method definition (the very bottom of the file), create a new interface, `UserPhoto`, to hold our photo metadata:
-```tsx
+```ts
+export const usePhotoGallery = () => {
+ // Same old code from before.
+};
+
+// CHANGE: Add the `UserPhoto` interface.
export interface UserPhoto {
filepath: string;
webviewPath?: string;
}
```
-At the top of the `usePhotoGallery` function, define an array so we can store each photo captured with the Camera. Make it a reactive variable using Vue's [ref function](https://v3.vuejs.org/guide/composition-api-introduction.html#reactive-variables-with-ref).
+Above the `addNewToGallery()` method, define an array of `UserPhoto`, which will contain a reference to each photo captured with the Camera. Make it a reactive variable using Vue's [ref method](https://vuejs.org/api/reactivity-core.html#ref).
-```tsx
-const photos = ref([]);
+```ts
+export const usePhotoGallery = () => {
+ // CHANGE: Add the `photos` array.
+ const photos = ref([]);
+
+ // Same old code from before.
+};
```
-When the camera is done taking a picture, the resulting `Photo` returned from Capacitor will be added to the `photos` array. Update the `takePhoto` function, adding this code after the `Camera.getPhoto` line:
+Over in the `addNewToGallery()` method, add the newly captured photo to the beginning of the `photos` array. Then, update the `userPhotoGallery` return statement with the `photos` array.
-```tsx
-const fileName = Date.now() + '.jpeg';
-const savedFileImage = {
- filepath: fileName,
- webviewPath: photo.webPath,
-};
+```ts
+export const usePhotoGallery = () => {
+ const photos = ref([]);
-photos.value = [savedFileImage, ...photos.value];
-```
+ const addNewToGallery = async () => {
+ const capturedPhoto = await Camera.getPhoto({
+ resultType: CameraResultType.Uri,
+ source: CameraSource.Camera,
+ quality: 100,
+ });
-Next, update the return statement to include the photos array:
+ // CHANGE: Create the `fileName` with current timestamp.
+ const fileName = Date.now() + '.jpeg';
+ // CHANGE: Create `savedImageFile` matching `UserPhoto` interface.
+ const savedImageFile = {
+ filepath: fileName,
+ webviewPath: capturedPhoto.webPath,
+ };
-```tsx
-return {
- photos,
- takePhoto,
+ // CHANGE: Update the `photos` array with the new photo.
+ photos.value = [savedImageFile, ...photos.value];
+ };
+
+ return {
+ addNewToGallery,
+ // CHANGE: Update return statement to include `photos` array.
+ photos,
+ };
};
```
-Back in the Tab2 component, update the import statement to include the `UserPhoto` interface:
+`usePhotoGallery.ts` should now look like this:
-```tsx
-import { usePhotoGallery, UserPhoto } from '@/composables/usePhotoGallery';
-```
+```ts
+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';
-Then, get access to the photos array:
+export const usePhotoGallery = () => {
+ const photos = ref([]);
-```tsx
-const { photos, takePhoto } = usePhotoGallery();
+ const addNewToGallery = async () => {
+ const capturedPhoto = await Camera.getPhoto({
+ resultType: CameraResultType.Uri,
+ source: CameraSource.Camera,
+ quality: 100,
+ });
+
+ const fileName = Date.now() + '.jpeg';
+ const savedImageFile = {
+ filepath: fileName,
+ webviewPath: capturedPhoto.webPath,
+ };
+
+ photos.value = [savedImageFile, ...photos.value];
+ };
+
+ return {
+ addNewToGallery,
+ photos,
+ };
+};
+
+export interface UserPhoto {
+ filepath: string;
+ webviewPath?: string;
+}
```
-With the photo(s) stored into the main array we can now display the images on the screen. Add a [Grid component](https://ionicframework.com/docs/api/grid) so that each photo will display nicely as they are added to the gallery, and loop through each photo in the Photos array, adding an Image component (``) for each. Point the `src` (source) to the photo's path:
+Next, switch to `Tab2Page.vue` to display the images. We'll add a [Grid component](../../api/grid.md) to ensure the photos display neatly as they're added to the gallery. Inside the grid, loop through each photo in the `UserPhoto`'s `photos` array. For each item, add an [Image component](../../api/img.md) and set its `src` property to the photo's path.
+
+```vue
+
+
+
+
+ Photo Gallery
+
+
+
+
+
+ Photo Gallery
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-```tsx
-
-
-
-
-
-
-
-
+
```
-Save all files. Within the web browser, click the Camera button and take another photo. This time, the photo is displayed in the Photo Gallery!
+Within the web browser, click the camera button and take another photo. This time, the photo is displayed in the Photo Gallery!
Up next, we’ll add support for saving the photos to the filesystem, so they can be retrieved and displayed in our app at a later time.
diff --git a/docs/vue/your-first-app/3-saving-photos.md b/docs/vue/your-first-app/3-saving-photos.md
index 04f5085738f..8a23ee49f94 100644
--- a/docs/vue/your-first-app/3-saving-photos.md
+++ b/docs/vue/your-first-app/3-saving-photos.md
@@ -1,70 +1,221 @@
---
+title: Saving Photos to the Filesystem
sidebar_label: Saving Photos
---
# 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 lost.
+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 opening the `usePhotoGallery` function (`src/composables/usePhotoGallery.ts`).
+Fortunately, saving them to the filesystem only takes a few steps. Begin by creating a new class method, `savePicture()`, in the `usePhotoGallery` method.
-The Filesystem API requires that files written to disk are passed in as base64 data, so this helper function will be used in a moment to assist with that:
+```ts
+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';
-```tsx
-const convertBlobToBase64 = (blob: Blob) =>
- new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onerror = reject;
- reader.onload = () => {
- resolve(reader.result);
+export const usePhotoGallery = () => {
+ // Same old code from before.
+
+ // CHANGE: Add the `savePicture()` method.
+ const savePicture = async (photo: Photo, fileName: string): Promise => {
+ return {
+ filepath: 'soon...',
+ webviewPath: 'soon...',
};
- reader.readAsDataURL(blob);
- });
+ };
+
+ return {
+ photos,
+ addNewToGallery,
+ };
+};
+
+export interface UserPhoto {
+ filepath: string;
+ webviewPath?: string;
+}
```
-Next, add a function to save the photo to the filesystem. 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.
+We can use this new method immediately in `addNewToGallery()`.
+
+```ts
+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.
-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:
+ // CHANGE: Update the `addNewToGallery()` method.
+ const addNewToGallery = async () => {
+ // Take a photo
+ const capturedPhoto = await Camera.getPhoto({
+ resultType: CameraResultType.Uri,
+ source: CameraSource.Camera,
+ quality: 100,
+ });
-```tsx
-const savePicture = async (photo: Photo, fileName: string): Promise => {
- // 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 fileName = Date.now() + '.jpeg';
+ // CHANGE: Add `savedImageFile()`.
+ // Save the picture and add it to photo collection
+ const savedImageFile = await savePicture(capturedPhoto, fileName);
- const savedFile = await Filesystem.writeFile({
- path: fileName,
- data: base64Data,
- directory: Directory.Data,
- });
+ photos.value = [savedImageFile, ...photos.value];
+ };
+
+ // CHANGE: Add the `savePicture()` method.
+ const savePicture = async (photo: Photo, fileName: string): Promise => {
+ return {
+ filepath: 'soon...',
+ webviewPath: 'soon...',
+ };
+ };
- // Use webPath to display the new image instead of base64 since it's
- // already loaded into memory
return {
- filepath: fileName,
- webviewPath: photo.webPath,
+ photos,
+ addNewToGallery,
};
};
+
+export interface UserPhoto {
+ filepath: string;
+ webviewPath?: string;
+}
```
-Last, update the `takePhoto` function to call `savePicture`. Once the photo has been saved, insert it into the front of reactive `photos` array:
+We'll use the Capacitor [Filesystem API](../../native/filesystem.md) 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.
+
+```ts
+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.
-```tsx
-const takePhoto = async () => {
- const photo = await Camera.getPhoto({
- resultType: CameraResultType.Uri,
- source: CameraSource.Camera,
- quality: 100,
- });
+ // CHANGE: Update the `savePicture()` method.
+ const savePicture = async (photo: Photo, fileName: string): Promise => {
+ // 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 fileName = Date.now() + '.jpeg';
- const savedFileImage = await savePicture(photo, fileName);
+ 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);
+ });
+ };
- photos.value = [savedFileImage, ...photos.value];
+ return {
+ photos,
+ addNewToGallery,
+ };
+};
+
+export interface UserPhoto {
+ filepath: string;
+ webviewPath?: string;
+}
+```
+
+`usePhotoGallery.ts` should now look like this:
+
+```ts
+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([]);
+
+ 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 => {
+ // 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()](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.
+
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.
diff --git a/docs/vue/your-first-app/4-loading-photos.md b/docs/vue/your-first-app/4-loading-photos.md
index 7cd07ef8794..f3aab7b3a67 100644
--- a/docs/vue/your-first-app/4-loading-photos.md
+++ b/docs/vue/your-first-app/4-loading-photos.md
@@ -1,4 +1,5 @@
---
+title: Loading Photos from the Filesystem
sidebar_label: Loading Photos
---
@@ -6,64 +7,310 @@ sidebar_label: Loading Photos
We’ve implemented photo taking and saving to the filesystem. There’s one last piece of functionality missing: the photos are stored in the filesystem, but we need a way to save pointers to each file so that they can be displayed again in the photo gallery.
-Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](https://capacitorjs.com/docs/apis/preferences) to store our array of Photos in a key-value store.
+Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](../../native/preferences.md) to store our array of Photos in a key-value store.
## Preferences API
-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`:
+Open `usePhotoGallery.ts` and begin by defining a constant variable that will act as the key for the store.
-```tsx
-const PHOTO_STORAGE = 'photos';
+```ts
+export const usePhotoGallery = () => {
+ const photos = ref([]);
+
+ // CHANGE: Add a key for photo storage.
+ const PHOTO_STORAGE = 'photos';
+
+ // Same old code from before.
+};
+```
+
+Next, at the end of the `usePhotoGallery` method, add a call to the `cachePhotos` method to save the `photos` array. By adding it here, the `photos` array is stored each time a new photo is taken. This way, it doesn’t matter when the app user closes or switches to a different app - all photo data is saved.
+
+```ts
+export const usePhotoGallery = () => {
+ // Same old code from before.
+
+ // CHANGE: Add `cachePhotos()` method.
+ const cachePhotos = () => {
+ Preferences.set({
+ key: PHOTO_STORAGE,
+ value: JSON.stringify(photos.value),
+ });
+ };
+
+ return {
+ photos,
+ addNewToGallery,
+ };
+};
+```
+
+Next, use the Vue [watch method](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.
+
+Add the call to the `watch()` method above the return statement in `usePhotoGallery`:
+
+```ts
+export const usePhotoGallery = () => {
+ // Same old code from before.
+
+ // CHANGE: Add call to `watch` with `photos` array and `cachePhotos` method.
+ watch(photos, cachePhotos);
+
+ return {
+ photos,
+ addNewToGallery,
+ };
+};
```
-Next, add a `cachePhotos` function that saves the Photos array as JSON to preferences:
+With the photo array data saved, create a new method in the `usePhotoGallery` called `loadSaved()` that can retrieve the photo data. We use the same key to retrieve the `photos` array in JSON format, then parse it into an array.
-```tsx
-const cachePhotos = () => {
- Preferences.set({
- key: PHOTO_STORAGE,
- value: JSON.stringify(photos.value),
- });
+```ts
+export const usePhotoGallery = () => {
+ // Same old code from before.
+
+ // CHANGE: Add `loadSaved()` method.
+ const loadSaved = async () => {
+ const photoList = await Preferences.get({ key: PHOTO_STORAGE });
+ const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
+ };
+
+ watch(photos, cachePhotos);
+
+ return {
+ photos,
+ addNewToGallery,
+ };
};
```
-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.
+On mobile (coming up next!), we can directly set the source of an image tag - `
` - 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, using a new `base64` property on the `Photo` object. This is because the `Filesystem` API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Add the following code to complete the `loadSaved()` method:
+
+```ts
+export const usePhotoGallery = () => {
+ // Same old code from before.
+
+ // CHANGE: Update `loadSaved()` method.
+ const loadSaved = async () => {
+ const photoList = await Preferences.get({ key: PHOTO_STORAGE });
+ const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
-```tsx
-watch(photos, cachePhotos);
+ // CHANGE: Display the photo by reading into base64 format.
+ for (const photo of photosInPreferences) {
+ const readFile = await Filesystem.readFile({
+ path: photo.filepath,
+ directory: Directory.Data,
+ });
+ photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`;
+ }
+
+ photos.value = photosInPreferences;
+ };
+
+ watch(photos, cachePhotos);
+
+ return {
+ photos,
+ addNewToGallery,
+ };
+};
```
-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:
+`usePhotoGallery.ts` should now look like this:
+
+```ts
+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([]);
+
+ const PHOTO_STORAGE = 'photos';
+
+ const addNewToGallery = async () => {
+ // Take a photo
+ 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];
+ };
-```tsx
-const loadSaved = async () => {
- const photoList = await Preferences.get({ key: PHOTO_STORAGE });
- const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
+ const savePicture = async (photo: Photo, fileName: string): Promise => {
+ // 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;
- for (const photo of photosInPreferences) {
- const file = await Filesystem.readFile({
- path: photo.filepath,
+ const savedFile = await Filesystem.writeFile({
+ path: fileName,
+ data: base64Data,
directory: Directory.Data,
});
- photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
- }
- photos.value = photosInPreferences;
+ // 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);
+ });
+ };
+
+ const cachePhotos = () => {
+ Preferences.set({
+ key: PHOTO_STORAGE,
+ value: JSON.stringify(photos.value),
+ });
+ };
+
+ const loadSaved = async () => {
+ const photoList = await Preferences.get({ key: PHOTO_STORAGE });
+ const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
+
+ for (const photo of photosInPreferences) {
+ const file = await Filesystem.readFile({
+ path: photo.filepath,
+ directory: Directory.Data,
+ });
+ photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
+ }
+
+ photos.value = photosInPreferences;
+ };
+
+ watch(photos, cachePhotos);
+
+ return {
+ addNewToGallery,
+ photos,
+ };
};
+
+export interface UserPhoto {
+ filepath: string;
+ webviewPath?: string;
+}
```
-On mobile (coming up next!), we can directly set the source of an image tag - `
` - 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.
+Our `usePhotoGallery` can now load the saved images, but we'll need to update the file to put that new code to work. We'll call `loadSaved` within the [onMounted](https://vuejs.org/api/composition-api-lifecycle.html#onmounted) lifecycle method so that when the user first navigates to the Photo Gallery, all photos are loaded and displayed on the screen.
-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:
+Update `usePhotoGallery.ts` to look like the following:
-```tsx
+```ts
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([]);
+
+ const PHOTO_STORAGE = 'photos';
+
+ 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];
+ };
-Within the `usePhotoGallery` function, add the `onMounted` function and call `loadSaved`:
+ const savePicture = async (photo: Photo, fileName: string): Promise => {
+ // 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;
-```tsx
-onMounted(loadSaved);
+ 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);
+ });
+ };
+
+ const cachePhotos = () => {
+ Preferences.set({
+ key: PHOTO_STORAGE,
+ value: JSON.stringify(photos.value),
+ });
+ };
+
+ const loadSaved = async () => {
+ const photoList = await Preferences.get({ key: PHOTO_STORAGE });
+ const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
+
+ for (const photo of photosInPreferences) {
+ const readFile = await Filesystem.readFile({
+ path: photo.filepath,
+ directory: Directory.Data,
+ });
+ photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`;
+ }
+
+ photos.value = photosInPreferences;
+ };
+
+ // CHANGE: Add call to `onMounted()` with the `loadSaved()` method.
+ onMounted(loadSaved);
+ watch(photos, cachePhotos);
+
+ return {
+ addNewToGallery,
+ photos,
+ };
+};
+
+export interface UserPhoto {
+ filepath: string;
+ webviewPath?: string;
+}
```
+:::note
+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).
+
+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`.
+:::
+
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!
diff --git a/docs/vue/your-first-app/5-adding-mobile.md b/docs/vue/your-first-app/5-adding-mobile.md
index 61a1a51191d..3763af86f1e 100644
--- a/docs/vue/your-first-app/5-adding-mobile.md
+++ b/docs/vue/your-first-app/5-adding-mobile.md
@@ -1,21 +1,82 @@
+---
+title: Adding Mobile
+strip_number_prefixes: false
+---
+
# Adding Mobile
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!
-Let’s start with making some small code changes - then our app will "just work" when we deploy it to a device.
+## Import Platform API
-## Platform-specific Logic
+Let’s start with making some small code changes - then our app will “just work” when we deploy it to a device.
+
+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).
-First, we’ll update the photo saving functionality to support mobile. We'll run slightly different code depending on the platform - mobile or web. Import the `Platform` API from Ionic Vue and `Capacitor` from Capacitor's `core` package:
+Add `Platform` 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.
-```tsx
+```ts
+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';
+// CHANGE: Add imports.
import { isPlatform } from '@ionic/vue';
import { Capacitor } from '@capacitor/core';
+
+// Same old code from before.
+```
+
+## Platform-specific Logic
+
+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.
+
+Update `savePicture` to look like the following:
+
+```ts
+// CHANGE: Update the `savePicture()` method.
+const savePicture = async (photo: Photo, fileName: string): Promise => {
+ let base64Data: string | Blob;
+
+ // CHANGE: Add platform check.
+ // "hybrid" will detect mobile - iOS or Android
+ if (isPlatform('hybrid')) {
+ const file = await Filesystem.readFile({
+ path: photo.path!,
+ });
+ base64Data = file.data;
+ } else {
+ // Fetch the photo, read as a blob, then convert to base64 format
+ const response = await fetch(photo.webPath!);
+ const blob = await response.blob();
+ 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,
+ };
+};
+```
+
+When running on mobile, set `filepath` to the result of the `writeFile()` operation - `savedFile.uri`. When setting the `webviewPath`, use the special `Capacitor.convertFileSrc()` method ([details on the File Protocol](../../core-concepts/webview.md#file-protocol)). To use this method, we'll need to import Capacitor into `usePhotoGallery.ts`.
+
+```ts
+import { Capacitor } from '@capacitor/core';
```
-In the `savePicture` function, 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 `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://capacitorjs.com/docs/basics/utilities#convertfilesrc)). Otherwise, use the same logic as before when running the app on the web.
+Then update `savePicture()` to look like the following:
-```tsx
+```ts
+// CHANGE: Update `savePicture()` method.
const savePicture = async (photo: Photo, fileName: string): Promise => {
let base64Data: string | Blob;
// "hybrid" will detect mobile - iOS or Android
@@ -30,15 +91,16 @@ const savePicture = async (photo: Photo, fileName: string): Promise =
const blob = await response.blob();
base64Data = (await convertBlobToBase64(blob)) as string;
}
+
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data,
});
+ // CHANGE: Add platform check.
if (isPlatform('hybrid')) {
// Display the new image by rewriting the 'file://' path to HTTP
- // Details: https://ionicframework.com/docs/building/webview#file-protocol
return {
filepath: savedFile.uri,
webviewPath: Capacitor.convertFileSrc(savedFile.uri),
@@ -54,9 +116,10 @@ const savePicture = async (photo: Photo, fileName: string): Promise =
};
```
-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:
+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:
-```tsx
+```ts
+// CHANGE: Update the `loadSaved` method.
const loadSaved = async () => {
const photoList = await Preferences.get({ key: PHOTO_STORAGE });
const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
@@ -77,4 +140,124 @@ const loadSaved = async () => {
};
```
-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.
+Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS.
+
+`usePhotoGallery.ts` should now look like this:
+
+```ts
+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';
+import { isPlatform } from '@ionic/vue';
+import { Capacitor } from '@capacitor/core';
+
+export const usePhotoGallery = () => {
+ const photos = ref([]);
+
+ const PHOTO_STORAGE = 'photos';
+
+ 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 => {
+ let base64Data: string | Blob;
+ // "hybrid" will detect mobile - iOS or Android
+ if (isPlatform('hybrid')) {
+ const file = await Filesystem.readFile({
+ path: photo.path!,
+ });
+ base64Data = file.data;
+ } else {
+ // Fetch the photo, read as a blob, then convert to base64 format
+ const response = await fetch(photo.webPath!);
+ const blob = await response.blob();
+ base64Data = (await convertBlobToBase64(blob)) as string;
+ }
+
+ const savedFile = await Filesystem.writeFile({
+ path: fileName,
+ data: base64Data,
+ directory: Directory.Data,
+ });
+
+ if (isPlatform('hybrid')) {
+ // Display the new image by rewriting the 'file://' path to HTTP
+ // Details: https://ionicframework.com/docs/building/webview#file-protocol
+ return {
+ filepath: savedFile.uri,
+ webviewPath: Capacitor.convertFileSrc(savedFile.uri),
+ };
+ } else {
+ // 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);
+ });
+ };
+
+ const cachePhotos = () => {
+ Preferences.set({
+ key: PHOTO_STORAGE,
+ value: JSON.stringify(photos.value),
+ });
+ };
+
+ const loadSaved = async () => {
+ const photoList = await Preferences.get({ key: PHOTO_STORAGE });
+ const photosInPreferences = photoList.value ? JSON.parse(photoList.value) : [];
+
+ // If running on the web...
+ if (!isPlatform('hybrid')) {
+ for (const photo of photosInPreferences) {
+ const file = await Filesystem.readFile({
+ path: photo.filepath,
+ directory: Directory.Data,
+ });
+ // Web platform only: Load the photo as base64 data
+ photo.webviewPath = `data:image/jpeg;base64,${file.data}`;
+ }
+ }
+
+ photos.value = photosInPreferences;
+ };
+
+ onMounted(loadSaved);
+ watch(photos, cachePhotos);
+
+ return {
+ addNewToGallery,
+ photos,
+ };
+};
+
+export interface UserPhoto {
+ filepath: string;
+ webviewPath?: string;
+}
+```
+
+Next up, the part you’ve been waiting for - deploying the app to a device.
diff --git a/docs/vue/your-first-app/6-deploying-mobile.md b/docs/vue/your-first-app/6-deploying-mobile.md
index 7258d32bcfd..e814e27dc72 100644
--- a/docs/vue/your-first-app/6-deploying-mobile.md
+++ b/docs/vue/your-first-app/6-deploying-mobile.md
@@ -4,10 +4,10 @@ sidebar_label: Deploying Mobile
---
- Build and Deploy Vue Apps for iOS and Android with Capacitor
+ Deploying to iOS and Android Apps - Capacitor Setup on Ionic
diff --git a/docs/vue/your-first-app/7-live-reload.md b/docs/vue/your-first-app/7-live-reload.md
index 9c0cb476b80..b75767823fa 100644
--- a/docs/vue/your-first-app/7-live-reload.md
+++ b/docs/vue/your-first-app/7-live-reload.md
@@ -1,36 +1,91 @@
---
+title: Rapid App Development with Live Reload
sidebar_label: Live Reload
---
-# Rapid App Development with Live Reload
+
+
+
So far, we’ve seen how easy it is to develop a cross-platform app that works everywhere. The development experience is pretty quick, but what if I told you there was a way to go faster?
-We can use the Ionic CLI’s [Live Reload functionality](https://ionicframework.com/docs/cli/livereload) to boost our productivity when building Ionic apps. When active, Live Reload will reload the browser and/or WebView when changes in the app are detected.
+We can use the Ionic CLI’s [Live Reload functionality](../../cli/livereload.md) to boost our productivity when building Ionic apps. When active, Live Reload will reload the browser and/or WebView when changes in the app are detected.
## Live Reload
Remember `ionic serve`? That was Live Reload working in the browser, allowing us to iterate quickly.
-We can also use it when developing on iOS and Android devices. This is particularly useful when writing code that interacts with native plugins. Since we need to run native plugin code on a device in order to verify that it works, having a way to quickly write code, build and deploy it, then test it is crucial to keeping up our development speed.
+We can also use it when developing on iOS and Android devices. This is particularly useful when writing code that interacts with native plugins - we must run it on a device to verify that it works. Therefore, being able to quickly write, build, test, and deploy code is crucial to keeping up our development speed.
Let’s use Live Reload to implement photo deletion, the missing piece of our Photo Gallery feature. Select your platform of choice (iOS or Android) and connect a device to your computer. Next, run either command in a terminal, based on your chosen platform:
```shell
-$ ionic cap run ios -l --external
+ionic cap run ios -l --external
-$ ionic cap run android -l --external
+ionic cap run android -l --external
```
The Live Reload server will start up, and the native IDE of choice will open if not opened already. Within the IDE, click the Play button to launch the app onto your device.
## Deleting Photos
-With Live Reload running and the app is open on your device, let’s implement photo deletion functionality. Open `Tab2Page.vue` then import the `actionSheetController`. We'll display an [IonActionSheet](https://ionicframework.com/docs/api/action-sheet) with the option to delete a photo:
+With Live Reload running and the app open on your device, let’s implement photo deletion functionality.
+
+In `usePhotoGallery.ts`, add the `deletePhoto()` method. The selected photo is removed from the `photos` array first. Then, we delete the actual photo file itself using the Filesystem API.
+
+```ts
+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';
+import { isPlatform } from '@ionic/vue';
+import { Capacitor } from '@capacitor/core';
+
+export const usePhotoGallery = () => {
+ // Same old code from before.
+
+ // CHANGE: Add the `deletePhoto()` method.
+ const deletePhoto = async (photo: UserPhoto) => {
+ // Remove this photo from the Photos reference data array
+ photos.value = photos.value.filter((p) => p.filepath !== photo.filepath);
+
+ // delete photo file from filesystem
+ const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1);
+ await Filesystem.deleteFile({
+ path: filename,
+ directory: Directory.Data,
+ });
+ };
+
+ onMounted(loadSaved);
+ watch(photos, cachePhotos);
+
+ // CHANGE: Add `deletePhoto()` to the return statement.
+ return {
+ photos,
+ addNewToGallery,
+ // CHANGE: Export `deletePhoto()` method.
+ deletePhoto,
+ };
+};
+
+export interface UserPhoto {
+ filepath: string;
+ webviewPath?: string;
+}
+```
+
+Next, in `Tab2Page.vue`, implement the `showActionSheet()` method. We're adding two options: "Delete", which calls `usePhotoGallery.deletePicture()`, and "Cancel". The cancel button will automatically closes the action sheet when assigned the "cancel" role.
-```tsx
+```vue
+
+
+
```
-Next, we need to implement the `deletePhoto` method in the `usePhotoGallery` function. Open the file then add:
-
-```tsx
-const deletePhoto = async (photo: UserPhoto) => {
- // Remove this photo from the Photos reference data array
- photos.value = photos.value.filter((p) => p.filepath !== photo.filepath);
-
- // delete photo file from filesystem
- const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1);
- await Filesystem.deleteFile({
- path: filename,
- directory: Directory.Data,
- });
-};
-```
-
-The selected photo is removed from the `photos` array first, then we delete the photo file using the Filesystem API.
-
-Remember that removing the photo from the `photos` array triggers the `cachePhotos` function for us automatically:
-
-```tsx
-const cachePhotos = () => {
- Preferences.set({
- key: PHOTO_STORAGE,
- value: JSON.stringify(photos.value),
- });
-};
-
-watch(photos, cachePhotos);
+Add a click handler to the `` element. When the app user taps on a photo in our gallery, we’ll display an [Action Sheet](../../api/action-sheet.md) dialog with the option to either delete the selected photo or cancel (close) the dialog.
+
+```vue
+
+
+
+
+ Photo Gallery
+
+
+
+
+
+ Photo Gallery
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
```
-Finally, return the `deletePhoto` function:
-
-```tsx
-return {
- photos,
- takePhoto,
- deletePhoto,
-};
-```
+Remember that removing the photo from the `photos` array triggers the `cachePhotos` method for us automatically.
-Save this file, then tap on a photo again and choose the "Delete" option. This time, the photo is deleted! Implemented much faster using Live Reload. 💪
+Tap on a photo again and choose the “Delete” option. The photo is deleted! Implemented much faster using Live Reload. 💪
In the final portion of this tutorial, we’ll walk you through the basics of the Appflow product used to build and deploy your application to users' devices.