How to Build a NativeScript Plugin for Barcode Reading
In the previous article, we’ve built a NativeScript plugin for camera preview. In this article, we are going to create a NativeScript plugin for Dynamsoft Barcode Reader which can be used for scanning barcodes or QR codes.
This article is Part 2 in a 2-Part Series.
Build a Barcode Reader NativeScript Plugin
Let’s do this in steps.
New Plugin Project
Create a plugin project using the plugin workspace seed. Here, we can just use the project we’ve created in the previous article.
Then, we can add a new barcode reader package:
npm run add
Follow the prompts to complete the setup. Here, we use nativescript-dynamsoft-barcode-reader
as the package’s name.
Write Definitions
Let’s write the definitions first.
-
Open
index.d.ts
to write function definitions.The plugin will have the following functions to use Dynamsoft Barcode Reader to read barcodes:
import { BarcodeReaderCommon } from './common'; export declare class BarcodeReader extends BarcodeReaderCommon { initLicense(license:string,licenseListener?:LicenseListener):void; initRuntimeSettingsWithString(template:string); decodeFrame(frame:any):TextResult[]; decodeBitmap(bitmap:any):TextResult[]; decodeBase64(base64:string):TextResult[]; decodeFile(file:string):TextResult[]; setCameraEnhancer(dce:any):void; startScanning():void; stopScanning():void; setTextResultListener(listener: TextResultListener); } export * from "./common";
-
Open
common.ts
to define the interfaces shared by both Android and iOS platforms.export interface TextResultListener{ (results:TextResult[]):void; } export interface LicenseListener{ (isSuccess:boolean,error:any):void; } export interface TextResult { barcodeText:string; barcodeFormat:string; x1:number; x2:number; x3:number; x4:number; y1:number; y2:number; y3:number; y4:number; }
Android Implementation
Add the Dependency
Create a include.gradle
file under platforms\android
to add the dependency of Dynamsoft Barcode Reader.
allprojects {
repositories {
maven { url "https://download2.dynamsoft.com/maven/aar" }
}
}
dependencies {
implementation 'com.dynamsoft:dynamsoftbarcodereader:9.6.0@aar'
}
Generate Typings
- Download the aar file of Dynamsoft Barcode Reader from here.
- Use the Android d.ts Generator to generate TypeScript definitions for the aar file. Put the generated
android.d.ts
file undertypings
.
Implement the Barcode Reader in TypeScript
Open index.android.ts
to call the native APIs using TypeScript.
The complete file is like the following:
import { LicenseListener, TextResultListener, BarcodeReaderCommon, TextResult } from './common';
export class BarcodeReader extends BarcodeReaderCommon {
dbr:com.dynamsoft.dbr.BarcodeReader;
constructor(){
super();
this.dbr = new com.dynamsoft.dbr.BarcodeReader();
}
initLicense(license:string,listener?:LicenseListener) {
com.dynamsoft.dbr.BarcodeReader.initLicense(license,new com.dynamsoft.dbr.DBRLicenseVerificationListener({
DBRLicenseVerificationCallback: function(isSuccessful:boolean,exception:java.lang.Exception){
if (listener) {
listener(isSuccessful, exception);
}
}
}));
}
initRuntimeSettingsWithString(template:string) {
this.dbr.initRuntimeSettingsWithString(template,com.dynamsoft.dbr.EnumConflictMode.CM_OVERWRITE);
}
decodeFrame(frame:any):TextResult[] {
let results = this.dbr.decodeBuffer(frame.getImageData(),frame.getWidth(),frame.getHeight(), frame.getStrides()[0], frame.getPixelFormat());
return this.wrapResult(results);
}
decodeBitmap(bitmap:any):TextResult[] {
let results = this.dbr.decodeBufferedImage(bitmap);
return this.wrapResult(results);
}
decodeFile(file:string):TextResult[] {
let results = this.dbr.decodeFile(file)
return this.wrapResult(results);
}
decodeBase64(base64:string):TextResult[] {
let bitmap = this.base642Bitmap(base64);
return this.decodeBitmap(bitmap);
}
wrapResult(results:androidNative.Array<com.dynamsoft.dbr.TextResult>):TextResult[] {
let textResults:TextResult[] = [];
for (let index = 0; index < results.length; index++) {
const result = results[index];
let textResult:TextResult = {
barcodeText:result.barcodeText,
barcodeFormat:result.barcodeFormatString,
x1:result.localizationResult.resultPoints[0].x,
x2:result.localizationResult.resultPoints[1].x,
x3:result.localizationResult.resultPoints[2].x,
x4:result.localizationResult.resultPoints[3].x,
y1:result.localizationResult.resultPoints[0].y,
y2:result.localizationResult.resultPoints[1].y,
y3:result.localizationResult.resultPoints[2].y,
y4:result.localizationResult.resultPoints[3].y
}
textResults.push(textResult);
}
return textResults;
}
base642Bitmap(base64:string):android.graphics.Bitmap {
let decode = android.util.Base64.decode(base64,android.util.Base64.DEFAULT);
return android.graphics.BitmapFactory.decodeByteArray(decode,0,decode.length);
}
setCameraEnhancer(dce:any) {
this.dbr.setCameraEnhancer(dce);
}
startScanning(){
this.dbr.startScanning();
}
stopScanning(){
this.dbr.stopScanning();
}
setTextResultListener(listener: TextResultListener){
let pThis = this;
this.dbr.setTextResultListener(new com.dynamsoft.dbr.TextResultListener({
textResultCallback: function(id: number, imageData: com.dynamsoft.dbr.ImageData, textResults: androidNative.Array<com.dynamsoft.dbr.TextResult>){
listener(pThis.wrapResult(textResults));
}
}));
}
}
iOS Implementation
Add the Dependency
Create a Podfile
file under platforms\ios
to add the Dynamsoft Barcode Reader dependency.
platform :ios, '9.0'
pod 'DynamsoftBarcodeReader','9.6.0'
Generate Typings
Run the following to generate the TypeScript definitions for iOS. Then, copy the objc!DynamsoftBarcodeReader.d.ts
to packages\nativescript-dynamsoft-barcode-reader\typings
.
cd apps/demo
ns typings ios
Implement the Barcode Reader in TypeScript
Open index.ios.ts
to call the native APIs using TypeScript.
The complete file is like the following:
import { LicenseListener, TextResultListener, BarcodeReaderCommon, TextResult } from './common';
@NativeClass()
class LicenseListenerImpl
extends NSObject // native delegates mostly always extend NSObject
implements DBRLicenseVerificationListener {
private callback: (isSuccess: boolean, error: any) => void;
static ObjCProtocols = [DBRLicenseVerificationListener] // define our native protocalls
static new(): LicenseListenerImpl {
return <LicenseListenerImpl>super.new() // calls new() on the NSObject
}
DBRLicenseVerificationCallbackError(isSuccess: boolean, error: NSError): void {
if (this.callback) {
this.callback(isSuccess, error);
}
}
public setCallback(callback: (isSuccess:boolean, error:any) => void): void {
this.callback = callback;
}
}
/**
* "Listener" for text result events
*
* @link https://v7.docs.nativescript.org/core-concepts/ios-runtime/how-to/objc-subclassing#typescript-delegate-example
*/
@NativeClass()
class TextResultListenerImpl
extends NSObject // native delegates mostly always extend NSObject
implements DBRTextResultListener {
private callback: TextResultListener;
private wrapResult: (results:NSArray<iTextResult>)=>TextResult[];
static ObjCProtocols = [DBRTextResultListener] // define our native protocalls
static new(): TextResultListenerImpl {
return <TextResultListenerImpl>super.new() // calls new() on the NSObject
}
textResultCallbackImageDataResults(frameId: number, imageData: iImageData, results: NSArray<iTextResult> | iTextResult[]): void {
if (this.callback) {
// @ts-ignore
this.callback(this.wrapResult(results));
}
}
public setCallback(callback: TextResultListener,wrapResult:(results:NSArray<iTextResult>)=>TextResult[]) {
this.callback = callback;
this.wrapResult = wrapResult;
}
}
export class BarcodeReader extends BarcodeReaderCommon {
dbr:DynamsoftBarcodeReader;
licenseListener:LicenseListenerImpl;
textResultListener:TextResultListenerImpl;
constructor(){
super();
this.dbr = DynamsoftBarcodeReader.alloc().init();
}
initLicense(license:string,listener?:LicenseListener) {
if (!this.licenseListener) {
this.licenseListener = LicenseListenerImpl.new();
}
if (listener) {
this.licenseListener.setCallback(listener);
}
DynamsoftBarcodeReader.initLicenseVerificationDelegate(license,this.licenseListener);
}
initRuntimeSettingsWithString(template:string) {
this.dbr.initRuntimeSettingsWithStringConflictModeError(template,EnumConflictMode.Overwrite);
}
decodeFrame(frame:any):TextResult[] {
let results = this.dbr.decodeBufferWithWidthHeightStrideFormatError(frame.imageData,frame.width,frame.height, frame.stride, frame.pixelFormat);
return this.wrapResult(results);
}
decodeBitmap(bitmap:any):TextResult[] {
let results = this.dbr.decodeImageError(bitmap);
return this.wrapResult(results);
}
decodeFile(file:string):TextResult[] {
let results = this.dbr.decodeFileWithNameError(file)
return this.wrapResult(results);
}
decodeBase64(base64:string):TextResult[] {
let image = this.base642UIImage(base64);
return this.decodeBitmap(image);
}
wrapResult(results:NSArray<iTextResult>):TextResult[] {
let textResults:TextResult[] = [];
if (results) {
for (let index = 0; index < results.count; index++) {
const result = results[index];
let textResult:TextResult = {
barcodeText:result.barcodeText,
barcodeFormat:result.barcodeFormatString,
x1:result.localizationResult.resultPoints[0].x,
x2:result.localizationResult.resultPoints[1].x,
x3:result.localizationResult.resultPoints[2].x,
x4:result.localizationResult.resultPoints[3].x,
y1:result.localizationResult.resultPoints[0].y,
y2:result.localizationResult.resultPoints[1].y,
y3:result.localizationResult.resultPoints[2].y,
y4:result.localizationResult.resultPoints[3].y
}
textResults.push(textResult);
}
}
return textResults;
}
base642UIImage(base64:string):UIImage{
let data = NSData.alloc().initWithBase64EncodedStringOptions(base64,NSDataBase64DecodingOptions.IgnoreUnknownCharacters);
let image = UIImage.alloc().initWithData(data);
return image;
}
setCameraEnhancer(dce:any) {
this.dbr.setCameraEnhancer(dce);
}
startScanning(){
this.dbr.startScanning();
}
stopScanning(){
this.dbr.stopScanning();
}
setTextResultListener(listener: TextResultListener){
if (!this.textResultListener) {
this.textResultListener = TextResultListenerImpl.new();
}
this.textResultListener.setCallback(listener,this.wrapResult);
this.dbr.setDBRTextResultListener(this.textResultListener)
}
}
Update the Demo to Use the Plugin
Let’s update the plain TypeScript demo to use the plugins to create a barcode scanning demo.
-
Open
apps/demo/src/plugin-demos/nativescript-dynamsoft-barcode-reader.xml
to add the camera preview view which is provided by the Dynamsoft Camera Enhancer plugin. Here we use theGridLayout
to make the view contain the whole space and add several buttons to test the functions.<Page xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:dce="nativescript-dynamsoft-camera-enhancer" navigatingTo="navigatingTo" class="page"> <GridLayout rows="*, auto, auto"> <dce:CameraEnhancer loaded="" rowSpan="3" active="" cameraID="" torch=""></dce:CameraEnhancer> <Label rowSpan="3" verticalAlignment="top" textAlignment="center" text="" textWrap="true"/> <StackLayout row="1"> <Button class="btn btn-primary" text="Init License" tap=""></Button> <Button class="btn btn-primary" text="Switch Torch" tap=""></Button> <Button class="btn btn-primary" text="Switch Camera" tap=""></Button> <Button class="btn btn-primary" text="Decode Frame" tap=""></Button> <Button class="btn btn-primary" text="" tap=""></Button> </StackLayout> </GridLayout> </Page>
-
Open
apps/demo/src/plugin-demos/nativescript-dynamsoft-barcode-reader.ts
to add relevant functions.-
Define props used by the views.
isActive: boolean = true; desiredTorchStatus:boolean = false; desiredCamera:string = ""; liveButtonText:string = "Turn on Live Detection"; barcodeText:string = "";
-
Initialize Dynamsoft Barcode Reader in the constructor. We also have to set a license to use it. You can apply for a license here.
dbr:BarcodeReader; constructor(){ super(); this.dbr = new BarcodeReader(); } initLicense(){ const listener:LicenseListener = function (isSuccess:boolean,error:any) { console.log("License initialization result: "+isSuccess); } this.dbr.initLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", listener); //one-day public trial }
-
When the camera view is loaded, get the instance of the camera enhancer and bind it to Dynamsoft Barcode Reader. We can get the barcodes’ text results in the text result listener after we call
startScanning
.dce:CameraEnhancer; barcodes:TextResult[] = []; dceLoaded(args: EventData) { this.dce = <CameraEnhancer>args.object; this.dbr.setCameraEnhancer(this.dce.getCameraEnhancer()); let pThis = this; this.dbr.setTextResultListener(function (textResults:TextResult[]) { pThis.barcodes = textResults; }) }
-
Add a function for toggling live detection of barcodes. The results will be displayed in a label.
interval:any; liveOn:boolean = false; toggleLiveDetection(){ if (this.liveOn === false) { this.liveOn = true; this.set("liveButtonText","Turn off Live Detection"); this.dbr.startScanning(); const rerender = async () => { console.log("rerender"); let barcodes = "Found "+this.barcodes.length+" barcode(s).\n"; this.barcodes.forEach(textResult => { barcodes = barcodes + textResult.barcodeFormat + ": " + textResult.barcodeText + "\n"; }); this.set("barcodeText",barcodes); } console.log("set interval"); this.interval = setInterval(rerender,200); }else{ this.liveOn = false; this.set("liveButtonText","Turn on Live Detection"); this.dbr.stopScanning(); if (this.interval) { console.log("clear interval"); clearInterval(this.interval); } } }
-
Handle the lifecycle events for Android. Close the camera and stop scanning when the app is paused. Start the camera and start scanning when the app is resumed.
constructor(){ super(); this.dbr = new BarcodeReader(); this.registerLifeCycleEvents(); } registerLifeCycleEvents(){ if (global.isAndroid) { let pThis = this; Application.android.on(AndroidApplication.activityPausedEvent, function (args: AndroidActivityBundleEventData) { console.log("paused"); if (pThis.dbr && pThis.liveOn) { console.log("stop scanning"); pThis.dbr.stopScanning(); } if (pThis.dce && pThis.isActive) { console.log("close camera"); pThis.dce.close(); } }); Application.android.on(AndroidApplication.activityResumedEvent, function (args: AndroidActivityBundleEventData) { console.log("resumed"); if (pThis.dce && pThis.isActive === true) { console.log("restart camera"); pThis.dce.open(); } if (pThis.dbr && pThis.liveOn) { console.log("start scanning"); pThis.dbr.startScanning(); } }); } }
-
Add a function for capturing a frame and reading barcodes from it.
async onDecodeFrame(args: EventData){ let frame = this.dce.captureFrame(); console.log(frame); let textResults:TextResult[] = this.dbr.decodeFrame(frame); console.log(textResults); let barcodes = "Found "+textResults.length+" barcode(s).\n"; textResults.forEach(textResult => { barcodes = barcodes + textResult.barcodeFormat + ": " + textResult.barcodeText + "\n"; }); alert(barcodes); }
-
Add functions to control the camera.
onSwitchCamera(args: EventData) { if (this.dce) { if (this.liveOn) { this.toggleLiveDetection(); } if (!this.cameras) { this.cameras = this.dce.getAllCameras(); } const selectedCamera = this.dce.getSelectedCamera(); let nextIndex = this.cameras.indexOf(selectedCamera) + 1; if (nextIndex >= this.cameras.length) { nextIndex = 0; } const nextCamera = this.cameras[nextIndex]; if (nextCamera != selectedCamera) { this.set("desiredCamera",nextCamera); } } } onSwitchTorch(args: EventData) { this.set("desiredTorchStatus",!this.desiredTorchStatus); }
-
All right, we’ve finished writing the plugin and the demo. We can run npm run start
to run the app on Android or iOS devices for a test.
Android Screenshot:
Notes about Decoding without Blocking the UI
In the above example, we use the text result listener to get the barcode results. This will start capturing frames from Dynamsoft Camera Enhancer and then use Dynamsoft Barcode Reader to read barcodes. The decoding runs on a background thread so it won’t block the UI.
If we write the frame capturing and barcode reading in TypeScript, the code will be executed on the main thread on Android, which will block the UI for a while. This will not happen on iOS as the NativeScript iOS runtime can handle such a situation.
Source Code
https://github.com/tony-xlh/nativescript-dynamsoft-capture-vision