How to Build a Quasar QR Code Scanner with Capacitor

Quasar is a Vue.js-based framework, which allows web developers to quickly create responsive websites/apps, while Capacitor is a runtime for creating cross-platform iOS, Android, and Progressive Web Apps with JavaScript, HTML, and CSS.

In this article, we are going to use the two to create a QR code scanner using the capacitor plugin of Dynamsoft Barcode Reader. The app can run as Web, Android and iOS apps.

Online demo.

Check out the Ionic Vue article, if you prefer to use the Ionic framework.

Getting started with Dynamsoft Barcode Reader

DOWNLOAD THE SDK WITH A 30-DAY LICENSE
REQUEST A 30-DAY LICENSE

Build a Quasar QR Code Scanner using Capacitor

New Project

Install quasar cli:

npm install -g @quasar/cli

Then, use it to create a new project:

npm init quasar

Because it has to run in Android’s webview, for compatibility concerns, we choose Quasar v1 + Vue.js 2 + Composition API to build this app.

√ What would you like to build? » App with Quasar CLI, let's go!
√ Project folder: ... quasar-qrcode-scanner
√ Pick Quasar version: » Quasar v1 (Vue 2)
√ Pick script type: » Typescript
√ Package name: ... quasar-qrcode-scanner
√ Project product name: (must start with letter if building mobile apps) ... Quasar QR Code Scanner
√ Project description: ... A Quasar Project
√ Author: ...
√ Pick a Vue component style: » Composition API (recommended) (https://github.com/vuejs/composition-api)
√ Pick your CSS preprocessor: » Sass with SCSS syntax
√ Pick a Quasar components & directives import strategy: (can be changed later) » * Auto-import in-use Quasar components & directives
        - also treeshakes Quasar; minimum bundle size
√ Check the features needed for your project: » ESLint
√ Pick an ESLint preset: » Prettier

Install Dependencies

npm install capacitor-plugin-dynamsoft-barcode-reader

Modify the Default Files

  1. Remove the example components under src/components.
  2. Open src/layouts/MainLayout.vue, simplify its template with the following content:

    <template>
      <q-layout view="lHh Lpr lFf">
        <q-header elevated >
          <q-toolbar>
            <q-toolbar-title>
              QR Code Scanner
            </q-toolbar-title>
          </q-toolbar>
        </q-header>
        <q-page-container>
          <router-view />
        </q-page-container>
      </q-layout>
    </template>
    
  3. Open src/pages/Index.vue, add a floating action button to start the scanner page to scan QR codes and a QList to display the results. Users can click the item to copy the result.

    <template>
      <q-page class="row justify-evenly">
        <div class="full">
          <q-list v-if="results.barcodeResults.value.length>0" dense bordered separator padding class="rounded-borders">
            <q-item @click="copy(result.barcodeText)" clickable v-ripple v-for="(result, index) in results.barcodeResults.value" :key="index">
              <q-item-section>
                <q-item-label :lines="1"></q-item-label>
                <q-item-label caption></q-item-label>
              </q-item-section>
            </q-item>
          </q-list>
          <q-page-sticky position="bottom-left" :offset="[18,18]">
            <q-btn @click="goScan" fab icon="camera_alt" color="blue" />
          </q-page-sticky>
        </div>
      </q-page>
    </template>
    

    The goScan and copy functions:

    import { Clipboard } from '@capacitor/clipboard';
    import { Notify } from 'quasar';
    const goScan = async () => {
      await router.push('/scanner');
    }
    
    const copy = async (text:string) => {
      await Clipboard.write({
        string: text
      });
      Notify.create({
        message: 'Copied.'
      });
    }
    
  4. Use vue-router with the composition API to navigate to another page.

    The dependent vue-router of the current version quasar does not have useRoute and useRouter to use in Vue 2.7 with the composition API. We can use the following workaround (GitHub issue).

    1. Create a file under utils/index.js.

      import { getCurrentInstance } from 'vue'
      
      export function useRoute() {
        const { proxy } = getCurrentInstance()
        const route = proxy.$route
        return route
      }
      export function useRouter() {
        const { proxy } = getCurrentInstance()
        const router = proxy.$router
        return router
      }
      
    2. In the index page, use it to navigate to the scanner page.

      import { useRouter } from '../utils/index.js'
      export default defineComponent({
        name: 'PageIndex',
        components: {},
        setup() {
          const router = useRouter();
          const goScan = async () => {
            await router.push('/scanner');
          }
          return {goScan};
        }
      });
      

Screenshot of the index page:

Index

Add a Scanner Page

  1. Create a new file named Scanner.vue under src\pages.
  2. Open src/router/routes.ts to register the route for the scanner page.

    {
      path: '/scanner',
      component: () => import('pages/Scanner.vue')
    },
    
  3. Add a QR Code Scanner component. We’ve defined the component in the previous Ionic Vue article. Although that project is in Vue 3, we can still directly use the component in a Vue 2 project with the composition api enabled. We are going to put the vue file under src/components/QRCodeScanner.vue.

  4. Use the QR Code Scanner component in the scanner page.

    <template>
      <q-layout view="lHh Lpr lFf">
        <QRCodeScanner
          license="DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="
          :torchOn="torchOn"
          :zoomFactor="zoomFactor"
          :runtimeSettings="runtimeSettings"
          @onScanned="onScanned"
          @onPlayed="onPlayed"
        ></QRCodeScanner>
      </q-layout>
    </template>
    <script lang="ts">
       
    export default defineComponent({
      name: 'PageScanner',
      components: { QRCodeScanner },
      setup() {
        const scanned = ref(false);
        const zoomFactor = ref(1.0);
        const torchOn = ref(false);
        const runtimeSettings = ref('');
        const router = useRouter();
         
        const onPlayed = (resolution:string) => {
          console.log(resolution);
        }
        const onScanned = (result:ScanResult) => {
          console.log(result);
        };
    
        onMounted(() => {
          zoomFactor.value = 1.0;
          App.addListener("backButton",() => {
            router.go(-1);
          });
        });
    
        return {onPlayed, onScanned, goBack, zoomFactor, runtimeSettings};
      }});
     </script>
    
  5. Add a float action button for cancelling the scanner and controlling the torch and zoom status.

    Template:

    <q-page-sticky style="z-index:1;" position="bottom-left" :offset="[18,18]">
      <q-fab color="blue" icon="keyboard_arrow_up" direction="up">
        <q-fab-action @click="toggleTorch" color="blue" icon="flashlight_on" />
        <q-fab-action @click="zoomIn" color="blue" icon="add" />
        <q-fab-action @click="zoomOut" color="blue" icon="remove" />
        <q-fab-action @click="goBack" color="blue" icon="arrow_back" />
      </q-fab>
    </q-page-sticky>
    

    Script:

    const zoomFactor = ref(1.0);
    const torchOn = ref(false);
    const toggleTorch = () => {
       torchOn.value = ! torchOn.value;
     };
    
    const zoomIn = () => {
      zoomFactor.value = zoomFactor.value + 0.3;
    };
    
    const zoomOut = () => {
      zoomFactor.value = Math.max(zoomFactor.value - 0.3, 1.0);
    };
    
    const goBack = () => {
      update([]);
      router.go(-1);
    }
    

Screenshot of the scanner page:

Index

Create a Store for Passing the QR Code Results to the Index Page

We can create a store to share data between different pages using Vuex.

  1. Create a new Vuex store named barcodes.

    quasar new store barcodes
    

    It will add the following files to the project:

    .
    └── src/
        └── store/
            ├── index.js         # Vuex Store definition
            └── barcodes         # Module "barcodes"
                ├── index.js     # Gluing the module together
                ├── actions.js   # Module actions
                ├── getters.js   # Module getters
                ├── mutations.js # Module mutations
                └── state.js     # Module state
    
  2. Edit src/store/index.js to add a reference to the new module.

    export default function (/* { ssrContext } */) {
      const Store = new Vuex.Store({
        modules: {
    +     barcodes
        },
    
        strict: process.env.DEBUGGING
      })
    
      return Store
    }
    
  3. Update state.js to define the states and mutations.js to define the setter.

    // src/store/barcodes/mutations.js
    export function update(state, results) {
      state.barcodeResults = results;
    }
    
    // src/store/barcodes/state.js
    // Always use a function to return state if you use SSR
    export default function () {
      return {
        barcodeResults: []
      }
    }
    
  4. In the scanner page, if QR codes are found, update the state and return to the index page.

    import { createNamespacedHelpers } from 'vuex-composition-helpers';
    const { useMutations } = createNamespacedHelpers('barcodes');
    //...
    setup() {
      const { update } = useMutations(['update']);
      const onScanned = (result:ScanResult) => {
        if (result.results.length>0 && scanned.value == false) {
          scanned.value = true;
          update(result.results);
          router.go(-1);
        }
      };
    }
    
  5. In the index page, load the results using useState.

    import { createNamespacedHelpers } from 'vuex-composition-helpers';
    const { useState } = createNamespacedHelpers('barcodes');
    setup() {
      const results = useState(["barcodeResults"]);
    }
    

All right, we’ve now finished writing the page. We can run the following to test the page:

quasar dev

Use Capacitor to Build Android and iOS Apps

We can take a step further to turn the web app into an Android app or an iOS app using Capacitor. The quasar cli has a Capacitor mode but it uses Capacitor v2. We are going to use Capacitor v4 directly.

  1. Drop Capacitor to the project:

    npm install @capacitor/cli @capacitor/core
    npx cap init
    
  2. Then, we can create projects for Android and iOS.

    npm install @capacitor/ios @capacitor/android
    npx cap add ios
    npx cap add android
    
  3. Build the web assets and sync the files to the projects.

    npm run build
    npx cap sync
    
  4. Use the following commands to start the app:

    npx cap run android // run on Android devices
    npx cap run ios // run on iOS devices
    

Extra steps are needed.

  • For the iOS platform:

    Add camera permission in Info.plist.

     <key>NSCameraUsageDescription</key>
     <string>For barcode scanning</string>
    
  • For the Android platform, we may have to handle the back button event using the @capacitor/app plugin:

     import { App } from '@capacitor/app';
     App.addListener("backButton",() => {
       console.log("back pressed");
     });
    
  • For both platforms, enable viewport-fit=cover in the index.template.html so that the page will not be blocked by the status bar.

     - <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover <% } %>">
     + <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, viewport-fit=cover">
    

Source Code

https://github.com/tony-xlh/Quasar-QR-Code-Scanner