Build a React Native QR Code Scanner Native UI Component
Note:
This article talks about how to build a QR code scanner native UI component for React Native. If you need to use Dynamsoft Barcode Reader in a React Native project for production, please use the official Dynamsoft Capture Vision SDK.
React Native is an open-source UI software framework made by Facebook. It enables developers to use the React framework to create applications for Android, iOS, macOS, Windows, etc.
The architecture of React Native is illustrated in the following diagram 1:
As you can see, there are four core sections. The React code we write is interpreted by the JavaScript engine. The engine communicates with the native via a bridge. The bridge uses asynchronous JSON messages for communication.
React Native uses the NativeModule and the NativeComponent systems to provide access to native platform APIs and UI widgets. NativeComponent is similar to NativeModule. The major difference is that we can use NativeComponent as a view in our React code2.
In this article, we are going to create a React Native barcode/QR Code scanner Native UI component using Dynamsoft Barcode Reader. Since there is Dynamsoft Camera Enhancer (DCE) which makes the use of the camera on Android and iOS easy, we are going to use DCE with DBR to build the component.
A demo app running on iOS:
Create a QR code Scanner Native UI Component
Environment
- Node
- Android Studio
- Xcode
Init a New Native UI Component Library Project
Run the following command:
npx create-react-native-library react-native-dynamsoft-barcode-scanner
It will ask you to enter descriptions of the project, select which languages to use and specify the type of the library.
Here we choose Java+Swift to create a component project.
√ Which languages do you want to use? » Java & Swift
√ What type of library do you want to develop? » Native view (to use as a component)
There is an example folder in the project. We can use it to test the library.
We can navigate into the project folder and run the following command to start the example.
# bootstrap the example project
yarn
# Android app
yarn example android
# iOS app
yarn example ios
iOS Implementation
Let’s implement the library for iOS first.
Overview of the iOS Library
In the ios
folder, there are three code files:
DynamsoftBarcodeScanner-Bridging-Header.h
DynamsoftBarcodeScannerViewManager.m
DynamsoftBarcodeScannerViewManager.swift
In the swift file, there is a view manager which returns a custom view. The custom view has a property called color
.
@objc(DynamsoftBarcodeScannerViewManager)
class DynamsoftBarcodeScannerViewManager: RCTViewManager {
override func view() -> (DynamsoftBarcodeScannerView) {
return DynamsoftBarcodeScannerView()
}
}
class DynamsoftBarcodeScannerView : UIView {
@objc var color: String = "" {
didSet {
self.backgroundColor = hexStringToUIColor(hexColor: color)
}
}
}
In the .m
file, the color
property is exported.
#import "React/RCTViewManager.h"
@interface RCT_EXTERN_MODULE(DynamsoftBarcodeScannerViewManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(color, NSString)
@end
Let’s open the index.js
file to learn about how the custom view is defined as a component in React.
import {
requireNativeComponent,
UIManager,
Platform,
ViewStyle,
} from 'react-native';
const LINKING_ERROR =
`The package 'react-native-dynamsoft-barcode-scanner' doesn't seem to be linked. Make sure: \n\n` +
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
'- You rebuilt the app after installing the package\n' +
'- You are not using Expo managed workflow\n';
type DynamsoftBarcodeScannerProps = {
color: string;
style: ViewStyle;
};
const ComponentName = 'DynamsoftBarcodeScannerView';
export const DynamsoftBarcodeScannerView =
UIManager.getViewManagerConfig(ComponentName) != null
? requireNativeComponent<DynamsoftBarcodeScannerProps>(ComponentName)
: () => {
throw new Error(LINKING_ERROR);
};
To use the component in the example/src/App.tsx
file:
import { DynamsoftBarcodeScannerView } from 'react-native-dynamsoft-barcode-scanner';
<DynamsoftBarcodeScannerViewManager color="#32a852" style={styles.box} />
Add Dependencies
First, we need to add dependencies: DBR and DCE
-
Open
react-native-dynamsoft-barcode-scanner.podspec
and add the following lines:s.libraries = 'c++' s.dependency 'DynamsoftCameraEnhancer', '= 2.3.21' s.dependency 'DynamsoftBarcodeReader', '= 9.6.40'
-
Update pods
pod install
In addition, licenses are needed to use Dynamsoft Barcode Reader and Dynamsoft Camera Enhancer. You can apply for a trial license here.
Integrate DCE
Let’s open the swift file and do the following:
-
Import DCE
import DynamsoftCameraEnhancer
-
Add properties
var dce:DynamsoftCameraEnhancer! = nil var dceView:DCECameraView! = nil
-
Config DCE. Add the DCE CameraView to the root UIView.
func configurationDCE() { // Initialize a camera view for previewing video. dceView = DCECameraView.init(frame: self.bounds) self.addSubview(dceView) dceView.overlayVisible = true dce = DynamsoftCameraEnhancer.init(view: dceView) }
We also need to add the following to the Info.plist
for camera permission:
<key>NSCameraUsageDescription</key>
<string>For barcode scanning</string>
<key>NSMicrophoneUsageDescription</key>
<string>For barcode scanning</string>
Start the Camera
After DCE is integrated, we can use it to call the camera.
-
Change the view’s name to
Scanner
in theindex.tsx
- export const DynamsoftBarcodeScannerView = + export const Scanner =
-
Add a
isScanning
property to control the scanning statusIn the
index.tsx
:type DynamsoftBarcodeScannerProps = { - color: string; + isScanning: isScanning; style: ViewStyle; };
-
Update the component in the
example/src/App.tsx
fileimport { Scanner } from 'react-native-dynamsoft-barcode-scanner'; <Scanner isScanning={true} style={styles.scanner} />
-
In the
DynamsoftBarcodeScannerViewManager.swift
file, add theisScanning
property@objc var isScanning: Bool = false { didSet { if isScanning { if (dce == nil){ configurationDCE() dce.open() }else{ dce.resume() } }else{ if dce != nil { dce.pause() } } } }
-
In the
DynamsoftBarcodeScannerViewManager.m
file, export theisScanning
propertyRCT_EXPORT_VIEW_PROPERTY(isScanning, BOOL)
-
We can add a button to control the
isScanning
propertyconst [isScanning, setIsScanning] = useState(false); const [btnText, setBtnText] = useState('Start Scan'); const toggleScan = () => { if (isScanning == true){ setIsScanning(false); setBtnText("Start Scan"); }else{ setIsScanning(true); setBtnText("Stop Scan"); } } <View style={{ position: 'absolute', justifyContent: 'center', bottom: "10%" }}> <TouchableOpacity onPress={toggleScan} > <Text style={{ fontSize: 16 }}> {btnText} </Text> </TouchableOpacity> </View>
Okay. We can now use Xcode to open the example project to have a test.
Bind DBR with DCE to Read Barcodes
Now that we can show the camera preview, we can integrate DBR to read barcodes.
-
Import DBR
import DynamsoftBarcodeReader
-
Add properties
var barcodeReader:DynamsoftBarcodeReader! = nil @objc var dbrLicense: String = "LICENSE-KEY"
-
Config DBR
func configurationDBR() { DynamsoftBarcodeReader.initLicense(dbrLicense, verificationDelegate: self) barcodeReader = DynamsoftBarcodeReader.init() }
-
Bind DCE and DBR
func bindDCEtoDBR(){ // Bind Camera Enhancer to the Barcode Reader. // The Barcode Reader will get video frame from the Camera Enhancer barcodeReader.setCameraEnhancer(dce) // Set text result call back to get barcode results. barcodeReader.setDBRTextResultListener(self) // Start the barcode decoding thread. barcodeReader.startScanning() } func textResultCallback(_ frameId: Int, imageData: iImageData, results: [iTextResult]?) { let count = results?.count ?? 0 if count > 0 { print("Found barcodes") } }
We can get the barcodes info in the textResultCallback
.
Send Scanned Event from Native to JavaScript
The RCTViewManager
has a bridge property. We can use it to send scanned barcodes to JavaScript.
Here is the code to use it:
var bridge:RCTBridge! = nil
func textResultCallback(_ frameId: Int, imageData: iImageData, results: [iTextResult]?) {
let count = results?.count ?? 0
let array = NSMutableArray()
for index in 0..<count {
let tr = results![index]
let result = NSMutableDictionary()
result["barcodeText"] = tr.barcodeText
result["barcodeFormat"] = tr.barcodeFormatString
result["barcodeBytesBase64"] = tr.barcodeBytes?.base64EncodedString()
array.add(result)
}
bridge.eventDispatcher().sendDeviceEvent(withName: "onScanned", body: array)
}
We can then receive the event on the JavaScript side and display the barcodes info:
const [barcodesInfo, setBarcodesInfo] = useState('');
const onScanned = (results:Array<ScanResult>) => {
var info = "";
for (var i=0;i<results.length;i++){
let result = results[i];
info = info + result.barcodeFormat + ": " + result.barcodeText + "\n";
}
console.log(info)
}
DeviceEventEmitter.addListener('onScanned',onScanned);
return (
<View style={styles.container}>
<View style={{ position: 'absolute', top: "5%", left: 10, width: "80%" }}>
<Text style={{ fontSize: 14, textShadowRadius: 12, textShadowColor: "black", color: "white" }}> {barcodesInfo} </Text>
</View>
<Scanner
isScanning={isScanning}
style={styles.scanner}
onScanned={onScanned}
/>
</View>
);
We also need to modify the index.tsx
file to update the props of the component and define a ScanResult
interface.
type DynamsoftBarcodeScannerProps = {
isScanning: boolean;
style: ViewStyle;
onScanned?: Event;
};
export interface ScanResult{
barcodeText: string;
barcodeFormat: string;
barcodeBytesBase64: string;
}
All right, the scanner’s basic iOS implementation is done.
Add More Functions
We can add more functions to the library like flashlight control and camera switcher. We can also modify Dynamsoft Barcode Reader’s runtime settings to decode QR codes only.
Let’s take updating the runtime settings for example.
-
Add a new
template
property inindex.tsx
type DynamsoftBarcodeScannerProps = { template?: string; };
-
Add a new
template
property inexample/src/App.tsx
to use a JSON template specifying the QR code barcode formatconst template = "{\"ImageParameter\":{\"BarcodeFormatIds\":[\"BF_QR_CODE\"],\"Description\":\"\",\"Name\":\"Settings\"},\"Version\":\"3.0\"}"; //...... <Scanner isScanning={isScanning} style={styles.scanner} onScanned={onScanned} template={template} />
-
Update the runtime settings in
DynamsoftBarcodeScannerViewManager.swift
@objc var template: String = "" { didSet { updateTemplate() } } func updateTemplate(){ if barcodeReader != nil { if (template != ""){ try? barcodeReader.initRuntimeSettingsWithString(template, conflictMode: EnumConflictMode.overwrite) }else{ try? barcodeReader.resetRuntimeSettings() } } }
Okay. The barcode reader has been set to decode QR codes only.
Android Implementation
The Android implementation is much the same. Here are the things worth mentioning.
-
The Android libraries of DBR and DCE can be imported by adding the following to the
build.gradle
file:rootProject.allprojects { repositories { maven { url "https://download2.dynamsoft.com/maven/aar" } } } dependencies { //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules implementation 'com.dynamsoft:dynamsoftcameraenhancer:2.3.10@aar' implementation 'com.dynamsoft:dynamsoftbarcodereader:9.6.40@aar' }
-
There is no need to manage the camera permission since DCE Android does this for us
-
The view manager can directly return the DCE camera view instead of creating a new view class
@Override @NonNull public DCECameraView createViewInstance(ThemedReactContext reactContext) { context = reactContext; reactContext.addLifecycleEventListener(this); mCameraView = new DCECameraView(reactContext.getBaseContext()); return mCameraView; }
-
We need to manage the life cycle for an Android app
@Override public void onHostResume() { if (mCameraEnhancer!=null){ if (this.isScanning){ try { mCameraEnhancer.open(); reader.startScanning(); } catch (CameraEnhancerException e) { e.printStackTrace(); } } } } @Override public void onHostPause() { if (mCameraEnhancer!=null){ try { reader.stopScanning(); mCameraEnhancer.close(); } catch (CameraEnhancerException e) { e.printStackTrace(); } } } @Override public void onHostDestroy() { if (reader!=null){ reader.destroy(); reader=null; mCameraEnhancer=null; } context.removeLifecycleEventListener(this); }
-
The
RCTDeviceEventEmitter
is used to send events from Native to JavaScriptcontext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onScanned",results);
Source Code
Here is the complete source code:
https://github.com/xulihang/react-native-dynamsoft-barcode-scanner
You can add the library to your React Native project following its instruction.
References
Disclaimer:
The wrappers and sample code on Dynamsoft Codepool are community editions, shared as-is and not fully tested. Dynamsoft is happy to provide technical support for users exploring these solutions but makes no guarantees.