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.
Check out the Ionic Vue article, if you prefer to use the Ionic framework.
Getting started with Dynamsoft Barcode Reader
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@1.5.0
Modify the Default Files
- Remove the example components under
src/components
. -
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>
-
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
andcopy
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.' }); }
-
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
anduseRouter
to use in Vue 2.7 with the composition API. We can use the following workaround (GitHub issue).-
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 }
-
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:
Add a Scanner Page
- Create a new file named
Scanner.vue
undersrc\pages
. -
Open
src/router/routes.ts
to register the route for the scanner page.{ path: '/scanner', component: () => import('pages/Scanner.vue') },
-
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
. -
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>
-
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:
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.
-
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
-
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 }
-
Update
state.js
to define the states andmutations.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: [] } }
-
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); } }; }
-
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.
-
Drop Capacitor to 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
-
Build the web assets and sync the files to the projects.
npm run build npx cap sync
-
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 theindex.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">