DEV Community

xulihang
xulihang

Posted on • Edited on • Originally published at dynamsoft.com

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.

Build a Quasar QR Code Scanner using Capacitor

New Project

Install quasar cli:

npm install -g @quasar/cli
Enter fullscreen mode Exit fullscreen mode

Then, use it to create a new project:

npm init quasar
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Install Dependencies

npm install capacitor-plugin-dynamsoft-barcode-reader
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
  1. 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 clikc 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">{{ result.barcodeText }}</q-item-label>
               <q-item-label caption>{{ result.barcodeFormat }}</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>
Enter fullscreen mode Exit fullscreen mode

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.'
     });
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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')
   },
Enter fullscreen mode Exit fullscreen mode
  1. 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.

  2. 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>
Enter fullscreen mode Exit fullscreen mode
  1. 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>
Enter fullscreen mode Exit fullscreen mode

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);
   }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  1. 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
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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: []
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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);
       }
     };
   }
Enter fullscreen mode Exit fullscreen mode
  1. In the index page, load the results using useState.
   import { createNamespacedHelpers } from 'vuex-composition-helpers';
   const { useState } = createNamespacedHelpers('barcodes');
   setup() {
     const results = useState(["barcodeResults"]);
   }
Enter fullscreen mode Exit fullscreen mode

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

quasar dev
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  1. Then, we can create projects for Android and iOS.
   npm install @capacitor/ios @capacitor/android
   npx cap add ios
   npx cap add android
Enter fullscreen mode Exit fullscreen mode
  1. Build the web assets and sync the files to the projects.
   npm run build
   npx cap sync
Enter fullscreen mode Exit fullscreen mode
  1. Use the following commands to start the app:
   npx cap run android // run on Android devices
   npx cap run ios // run on iOS devices
Enter fullscreen mode Exit fullscreen mode

Extra steps are needed.

  • For the iOS platform:

Add camera permission in Info.plist.

   <key>NSCameraUsageDescription</key>
   <string>For barcode scanning</string>
Enter fullscreen mode Exit fullscreen mode
  • 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");
   });
Enter fullscreen mode Exit fullscreen mode
  • 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">
Enter fullscreen mode Exit fullscreen mode

Source Code

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

Top comments (1)

Collapse
 
mitya profile image
Dima

Your next article: Vue Test Utils & Quasar 😉