How to capture multiple photo with Ionic and Capacitor ?

Recently, i had to develop a ionic application in which users should be able to take multiple photo with their camera. This will be the purpose of this tutorial.

The source code repository

First le'ts generate a new Ionic with capacitor project. Launch a terminal and enter:

ionic start multiplephoto blank --capacitor --project-id=multiplephoto --package-id=com.ionicanddjangotutorials.multiplephoto

Installing and using Media capture plugin with Ionic and Capacitor

To be able to access the mobile camera and implement our multiple photo functionnality, we will use the Media Capture plugin so let's install it

npm install cordova-plugin-media-capture
npm install @ionic-native/media-capture

Now let's modify the generated home.page.html file to add a button which will open the Camera preview on click

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Multiple photo capture
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <div id="container">
    <strong>Ready to start?</strong>
    <ion-button color="primary" (click)="openCamera()">Take photos</ion-button>
  </div>
</ion-content>

and let's adjust the home.page.scss for a better design

#container {
  display: flex;
  flex-direction: column;
  text-align: center;
  position: absolute;
  left: 0;
  right: 0;
  top: 10%;
}

#container strong {
  font-size: 20px;
  line-height: 26px;
}

#container p {
  font-size: 16px;
  line-height: 22px;
  color: #8c8c8c;
  margin: 0;
}

#container a {
  text-decoration: none;
}

Our screen should look like this:

Ok now we can implement the openCamera() method in the home.page.ts file

import { Component } from '@angular/core';
import { MediaCapture, MediaFile, CaptureError, CaptureImageOptions } from '@ionic-native/media-capture/ngx';


@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {

 
  constructor(private mediaCapture: MediaCapture) { 

  }

  openCamera(){
    let options: CaptureImageOptions = { limit: 3 }
    this.mediaCapture.captureImage(options).then((data: MediaFile[]) => {
      console.log(data)
    },
    (err: CaptureError) => {
      console.error(err)
    });
  }

}

and we need to modify the home.module.ts file to declare the MediaCapture provider plugin:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { HomePage } from './home.page';

import { HomePageRoutingModule } from './home-routing.module';
import { MediaCapture } from '@ionic-native/media-capture/ngx';


@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    HomePageRoutingModule
  ],
  providers:[MediaCapture],
  declarations: [HomePage]
})
export class HomePageModule {}

Ok lot of code but the most important part is the openCamera() method and more specifically the following instructions:

 let options: CaptureImageOptions = { limit: 3 }
    

We indicate that the user would be able to take up to 3 photos. If you don't specify a limit value the default will be only one photo. Once photos taken, we should receive an array with the images information or an error if something bad occured

this.mediaCapture.captureImage(options).then((data: MediaFile[]) => {
      console.log(data)
    },
    (err: CaptureError) => {
      console.error(err)
    });

Testing the photo capture on real device with capacitor

At this stage, we would like to run our app in a real mobile device to have a look of what's happening and if everything is working fine. With capacitor, it is super easy to do so. First we build our app

ionic build

Then since i will test with an android device, i need to add the android platform to capacitor

npx cap add android
npx cap sync

Now i plug in my android device to usb port and launch the command

ionic capacitor run android --livereload --external

This command will open Android Studio and i just need to hit the run button. The Ionic application will be deployed on my android device with live mode, meaning that if i made a change in the code, the application will update automatically and reload to reflect the changes.

Please notice that your android device must be in debug mode if you want to run applications on it from Android Studio

If you are using an iPhone just do the same with npx cap add ios and ionic capacitor run ios --livereload --external

The application running on my android device, i can click the Take photo button and the camera preview appears and let me take one photo. Then the view briefly disappears and appears again and i can take another photo. Same process happens once more. Finally after my 3 photos capture, the camera preview closes and i can see in the logs:

 http://192.168.1.51:8100/home-home-module.js - Line 200 - Msg: [object Object],[object Object],[object Object]

I have 3 objects in my array list which is a good news since i took 3 photos. For better debugging just change the console.log output to show json data

console.log(JSON.stringify(data))
[
{"name":"1597995590266.jpg",
"localURL":"cdvfile://localhost/sdcard/Pictures/1597995590266.jpg",
"type":"image/jpeg","lastModified":null,"lastModifiedDate":1597995598000,"size":5735308,"start":0,"end":0,
"fullPath":"file:///storage/emulated/0/Pictures/1597995590266.jpg"},{"name":"1597995598705.jpg",
"localURL":"cdvfile://localhost/sdcard/Pictures/1597995598705.jpg",
"type":"image/jpeg","lastModified":null,"lastModifiedDate":1597995603000,"size":5915682,"start":0,"end":0,
"fullPath":"file:///storage/emulated/0/Pictures/1597995598705.jpg"},{"name":"1597995603724.jpg","localURL":"cdvfile://localhost/sdcard/Pictures/1597995603724.jpg","type":"image/jpeg","lastModified":null,"lastModifiedDate":1597995609000,"size":5962056,"start":0,"end":0,"fullPath":"file:///storage/emulated/0/Pictures/1597995603724.jpg"}]

As expected the array contains the information about the 3 photos taken and where the files are stored.

The plugin behaviour/user experience is not so great because user needs to take 3 photos (not 2 or one) to exit the camera mode (or close the camera application).

Now if you want to read the files, i will suggest to use the Capacitor Filesystem plugin and more particulary the readFile methods

Then with the files, you can display them or send them to a server like we learned in this tutorial.

Read and display images taken with the media capture plugin into our Ionic application

Let's modify our html page to display pictures in a grid:

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Multiple photo capture
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <div id="container">
    <strong>Ready to start?</strong>
    <ion-button color="primary" (click)="openCamera()">Take photos</ion-button>
  
  <div class="section-body">
    <ion-row>
      <ion-col size="4" *ngFor="let item of pictures">
        <div class="image-card">
          <img [src]="item" alt="">
        </div>
      </ion-col>
    </ion-row>
  </div>
  
</div>
</ion-content>

Now we modify our code to use the Capacitor filesystem plugin , browse each photo of our array and add them to a the new array pictures which is displayed in our html page

import { Component, NgZone } from '@angular/core';
import { MediaCapture, MediaFile, CaptureError, CaptureImageOptions } from '@ionic-native/media-capture/ngx';
import { Plugins, FilesystemDirectory, FilesystemEncoding } from '@capacitor/core';
const { Filesystem } = Plugins;

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {

  pictures=[]
  constructor(private mediaCapture: MediaCapture) { 

  }

  openCamera(){
    let options: CaptureImageOptions = { limit: 1 }
    this.mediaCapture.captureImage(options).then(async (data: MediaFile[]) => {
      console.log(JSON.stringify(data))
      for (let photo of data){
        let fullPath = photo.fullPath
        try{
          let photoBinary = await Filesystem.readFile({path:fullPath})
          if (photoBinary.data){
            let fullData = "data:" + photo.type + ";base64," + photoBinary.data
            this.pictures.push(fullData)
          }
        
        }
        catch(error){
          console.log(error)
        }
       
      }
    },
    (err: CaptureError) => {
      console.error(err)
    });
  }

}

Capacitor readFile returns the binary data in a based64 encoded so we will use this value with the type value of the photo (image/jpeg) to be able to display it correctly in our img tag:

let photoBinary = await Filesystem.readFile({path:fullPath})
          if (photoBinary.data){
            let fullData = "data:" + photo.type + ";base64," + photoBinary.data
            this.pictures.push(fullData)
          }

Please notice that with Android 10 a change occurs and we need to add: android:requestLegacyExternalStorage="true" into our AndroidManifest.xml file otherwise the application will crash

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:requestLegacyExternalStorage="true"
        android:theme="@style/AppTheme">

Christophe Surbier