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!
Import Platform API
Let’s start with making some small code changes - then our app will “just work” when we deploy it to a device.
Import the IonicPlatform API intousePhotoGallery.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).
AddPlatform to the imports at the top of the file to use theisPlatform method.
import{ ref, watch, onMounted}from'vue';
import{ Camera, CameraResultType, CameraSource}from'@capacitor/camera';
importtype{ Photo}from'@capacitor/camera';
import{ Filesystem, Directory}from'@capacitor/filesystem';
import{ Preferences}from'@capacitor/preferences';
// CHANGE: Add import
import{ isPlatform}from'@ionic/vue';
// ...existing code...
Platform-specific Logic
First, we’ll update the photo saving functionality to support mobile. In thesavePicture() 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 theFilesystem.readFile() method. Otherwise, use the same logic as before when running the app on the web.
UpdatesavePicture() to look like the following:
// CHANGE: Update the `savePicture()` method
const savePicture=async(photo: Photo, fileName:string):Promise<UserPhoto>=>{
let base64Data:string| Blob;
// CHANGE: Add platform check
// "hybrid" will detect mobile - iOS or Android
if(isPlatform('hybrid')){
const readFile=await Filesystem.readFile({
path: photo.path!,
});
base64Data= readFile.data;
}else{
// Fetch the photo, read as a blob, then convert to base64 format
const response=awaitfetch(photo.webPath!);
const blob=await response.blob();
base64Data=(awaitconvertBlobToBase64(blob))asstring;
}
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, setfilepath to the result of thewriteFile() operation -savedFile.uri. When setting thewebviewPath, use the specialCapacitor.convertFileSrc() method (details on the File Protocol). To use this method, we'll need to import Capacitor intousePhotoGallery.ts.
import{ ref, watch, onMounted}from'vue';
import{ Camera, CameraResultType, CameraSource}from'@capacitor/camera';
importtype{ Photo}from'@capacitor/camera';
import{ Filesystem, Directory}from'@capacitor/filesystem';
import{ Preferences}from'@capacitor/preferences';
import{ isPlatform}from'@ionic/vue';
// CHANGE: Add import
import{ Capacitor}from'@capacitor/core';
// ...existing code...
Then updatesavePicture() to look like the following:
// CHANGE: Update `savePicture()` method
const savePicture=async(photo: Photo, fileName:string):Promise<UserPhoto>=>{
let base64Data:string| Blob;
// CHANGE: Add platform check
// "hybrid" will detect mobile - iOS or Android
if(isPlatform('hybrid')){
const readFile=await Filesystem.readFile({
path: photo.path!,
});
base64Data= readFile.data;
}else{
// Fetch the photo, read as a blob, then convert to base64 format
const response=awaitfetch(photo.webPath!);
const blob=await response.blob();
base64Data=(awaitconvertBlobToBase64(blob))asstring;
}
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
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,
};
}
};
Next, add a new bit of logic in theloadSaved() 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 usesIndexedDB under the hood. Update theloadSaved() method:
// CHANGE: Update `loadSaved` method
constloadSaved=async()=>{
const photoList=await Preferences.get({ key:PHOTO_STORAGE});
const photosInPreferences= photoList.value?JSON.parse(photoList.value):[];
// CHANGE: Add platform check
// If running on the web...
if(!isPlatform('hybrid')){
for(const photoof photosInPreferences){
const readFile=await Filesystem.readFile({
path: photo.filepath,
directory: Directory.Data,
});
// Web platform only: Load the photo as base64 data
photo.webviewPath=`data:image/jpeg;base64,${readFile.data}`;
}
}
photos.value= photosInPreferences;
};
Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS.
usePhotoGallery.ts should now look like this:
import{ ref, watch, onMounted}from'vue';
import{ Camera, CameraResultType, CameraSource}from'@capacitor/camera';
importtype{ 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';
exportconstusePhotoGallery=()=>{
const photos=ref<UserPhoto[]>([]);
constPHOTO_STORAGE='photos';
constaddNewToGallery=async()=>{
// Take a photo
const capturedPhoto=await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality:100,
});
const fileName= Date.now()+'.jpeg';
// Save the picture and add it to photo collection
const savedImageFile=awaitsavePicture(capturedPhoto, fileName);
photos.value=[savedImageFile,...photos.value];
};
const savePicture=async(photo: Photo, fileName:string):Promise<UserPhoto>=>{
let base64Data:string| Blob;
// "hybrid" will detect mobile - iOS or Android
if(isPlatform('hybrid')){
const readFile=await Filesystem.readFile({
path: photo.path!,
});
base64Data= readFile.data;
}else{
// Fetch the photo, read as a blob, then convert to base64 format
const response=awaitfetch(photo.webPath!);
const blob=await response.blob();
base64Data=(awaitconvertBlobToBase64(blob))asstring;
}
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
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,
};
}
};
constconvertBlobToBase64=(blob: Blob)=>{
returnnewPromise((resolve, reject)=>{
const reader=newFileReader();
reader.onerror= reject;
reader.onload=()=>{
resolve(reader.result);
};
reader.readAsDataURL(blob);
});
};
constcachePhotos=()=>{
Preferences.set({
key:PHOTO_STORAGE,
value:JSON.stringify(photos.value),
});
};
constloadSaved=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 photoof photosInPreferences){
const readFile=await Filesystem.readFile({
path: photo.filepath,
directory: Directory.Data,
});
// Web platform only: Load the photo as base64 data
photo.webviewPath=`data:image/jpeg;base64,${readFile.data}`;
}
}
photos.value= photosInPreferences;
};
onMounted(loadSaved);
watch(photos, cachePhotos);
return{
addNewToGallery,
photos,
};
};
exportinterfaceUserPhoto{
filepath:string;
webviewPath?:string;
}
Next up, the part you’ve been waiting for - deploying the app to a device.