How to Build a Cross-Platform QR Code Scanner with Capacitor

Capacitor is an open source native runtime created by the Ionic team for building Web Native apps. We can use it to create cross-platform iOS, Android, and Progressive Web Apps with JavaScript, HTML, and CSS.1 As an alternative to Cordova, Capacitor delivers the same cross-platform benefits, but with a more modern approach to app development, taking advantage of the latest Web APIs and native platform capabilities.2

In this article, we are going to talk about how to use the capacitor barcode reader plugin to build a QR code scanner using Dynamsoft Barcode Reader (DBR).

Build a QR Code Scanner using Capacitor

Let’s do this in steps.

Integrate Capacitor with an Existing Web App

Capacitor can be integrated with existing web apps to add native functionality.

Here, we create a JavaScript project with Vite.

npm create vite@latest qrcode-scanner -- --template vanilla

Then, drop Capacitor into the project.

npm install @capacitor/cli @capacitor/core
npx cap init

Then, we can create projects for Android and iOS.

npm install @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android

Use the following commands to update files after the app is changed:

npm run build
npx cap sync

Use the following commands to start the app:

npm run start // run in browsers
npx cap run android // run on Android devices
npx cap run ios // run on iOS devices

Use Plugins to Bring QR Code Scanning Functionality to the App

Plugins in Capacitor enable JavaScript to interface directly with Native APIs.3 A plugin has been created to make it easy to use Dynamsoft Barcode Reader in a Capacitor app to scan QR codes. Since Dynamsoft Barcode Reader has Android, iOS and JavaScript editions, the plugin also supports Android, iOS and Web. We can test the app on the web browsers first and then make modifications to make it compatible with native platforms.

  1. Install plugins to the web app we just create. We are also installing capacitor-plugin-camera to open the camera.

    npm install capacitor-plugin-dynamsoft-barcode-reader capacitor-plugin-camera
    
  2. Open index.html. Create a home container which contains a start scanning button and a result container. Create a controls container which contains the camera element, a button to toggle the torch and a button to close scanning.

    <main>
      <div class="home">
        <h2>Barcode Reader</h2>
        <button id="startScanButton" type="button">Start Live Scan</button>
        <div id="results">
        </div>
      </div>
      <div class="controls" style="display:none;">
        <div class="camera">
          <div class="dce-video-container"></div>
        </div>
        <button id="closeButton" type="button">Close</button>
        <button id="toggleTorchButton" type="button">Toggle Torch</button>
      </div>
    </main>
    
  3. Initialize the plugins when the page is loaded. You may need to apply for a trial license to use Dynamsoft Barcode Reader from here.

    import { DBR } from 'capacitor-plugin-dynamsoft-barcode-reader';
       
    initDBR();
    initCamera();
       
    async function initDBR(){
      await DBR.initLicense({license:"DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="}); //one-day trial
      await DBR.initialize();
    }
       
       
    async function initCamera(){
      if (!Capacitor.isNativePlatform()) {
        await CameraPreview.setElement(document.getElementsByClassName("camera")[0]);
      }
      await CameraPreview.initialize();
      await CameraPreview.requestCameraPermission();
    }
    
  4. Add the onclick event for the start scanning button.

    let startScanButton = document.getElementById("startScanButton");
    startScanButton.addEventListener("click",startScan);
    async function startScan(){
      toggleControlsDisplay(true);
      await CameraPreview.startCamera();
    }
       
    function toggleControlsDisplay(show){
      if (show) {
        document.getElementsByClassName("home")[0].style.display = "none";
        document.getElementsByClassName("controls")[0].style.display = "";
        document.body.style.background = "transparent"; //to avoid the webview blocking the camera preview
      }else {
        document.getElementsByClassName("home")[0].style.display = "";
        document.getElementsByClassName("controls")[0].style.display = "none";
        document.body.style.background = "white";
      }
    }
    
  5. Add an onPlayed plugin handler to receive the event when the camera is opened. Here, we start a loop to read barcodes from the camera after the camera is opened.

    let decoding = false;
    let interval;
       
    let onPlayedListener = await CameraPreview.addListener('onPlayed', (res) => {
      startDecoding();
    });
       
    function startDecoding(){
      stopDecoding();
      intervel = setInterval(captureAndDecode,200);
    }
    
    function stopDecoding(){
      clearInterval(intervel);
    }
       
    async function captureAndDecode(){
      if (decoding === true) {
        return;
      }
      let results = [];
      let dataURL;
      decoding = true;
      try {
        if (Capacitor.isNativePlatform()) {
          await CameraPreview.saveFrame();
          results = (await DBR.decodeBitmap({})).results;
        }else{
          let frame = await CameraPreview.takeSnapshot({quality:50});
          dataURL = "data:image/jpeg;base64,"+frame.base64;
          results = await readDataURL(dataURL);
        }
        if (results.length>0) {
          stopScan();
          displayResults(results);
        }
      } catch (error) {
        console.log(error);
      }
      decoding = false;
    }
       
    async function readDataURL(dataURL){
      let response = await DBR.decode({source:dataURL});
      let results = response.results;
      return results;
    }
    
  6. When the close scan button is clicked or the barcode is scanned, stop scanning and display the barcode results.

    async function stopScan(){
      stopDecoding();
      await CameraPreview.stopCamera();
      toggleControlsDisplay(false);
    }
       
    function displayResults(results){
      let resultsContainer = document.getElementById("results");
      let ol = document.createElement("ol");
      for (let index = 0; index < results.length; index++) {
        const result = results[index];
        let li = document.createElement("li");
        li.innerText = result.barcodeFormat + ": " + result.barcodeText;
        ol.appendChild(li);
      }
      resultsContainer.innerHTML = ol.outerHTML;
    }
    
  7. Add a click event for the toggle torch button.

    let torchStatus = false;
    let toggleTorchButton = document.getElementById("toggleTorchButton");
    toggleTorchButton.addEventListener("click",toggleTorch);
    async function toggleTorch(){
      try {
        let desiredStatus = !torchStatus;
        await CameraPreview.toggleTorch({on:desiredStatus});
        torchStatus = desiredStatus;   
      } catch (error) {
        alert(error);
      }
    }
    

All right, we’ve completed the demo.

Add Camera Permission

We have to configure the camera permission for iOS and Android in order to use the camera.

Add the following to Info.plist for iOS:

<key>NSCameraUsageDescription</key>
<string>For barcode scanning</string>

Add the following to AndroidManifest.xml for Android:

<uses-permission android:name="android.permission.CAMERA" />

Source Code

https://github.com/xulihang/capacitor-qr-code-scanner

References