How to Integrate Dynamsoft Barcode Reader into React Native Camera

If you are using React Native, you can quickly create a camera app with React Native Camera. In addition, React Native Camera component supports barcode scanning, face detection, and text recognition. This article shares how to replace the default barcode reading function with Dynamsoft Barcode Reader for Android.

How to Build a Camera App with React Native Camera

Follow the tutorial to create a new React Native project:

npm install -g react-native-cli
react-native init AwesomeProject
cd AwesomeProject

Install React Native Camera module:

npm install react-native-camera –save
react-native link react-native-camera

Read the documentation to get the code snippet of using RNCamera in App.js.

Build and run the Android camera app:

react-native run-android

You may get the error ‘Could not find play-services-basement.aar’:

gradle build error

Referring to StackOverflow, move jcenter() to the last place in repositories.

jcenter position

How to Integrate Dynamsoft Barcode Reader into React Native Camera

According to the sample code shown in the documentation, by default, we can use Google Mobile Vision to scan barcodes on Android:

<RNCamera
            <…>
            onGoogleVisionBarcodesDetected={({ barcodes }) => {
              console.log(barcodes)
            }}
        />

Let’s anatomize the source code and make Dynamsoft Barcode Reader work with the component.

How to return barcode results to JavaScript

Get started with ‘onGoogleVisionBarcodesDetected’.

google mobile vision

This is an event used to pass native barcode results from Java to JavaScript. The following code shows how to pack barcode types and values into an event:

@Override
  public void dispatch(RCTEventEmitter rctEventEmitter) {
    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
  }

private WritableMap serializeEventData() {
    WritableArray barcodesList = Arguments.createArray();

    for (int i = 0; i < mBarcodes.size(); i++) {
      Barcode barcode = mBarcodes.valueAt(i);
      WritableMap serializedBarcode = Arguments.createMap();
      serializedBarcode.putString("data", barcode.displayValue);
      serializedBarcode.putString("type", BarcodeFormatUtils.get(barcode.format));
      barcodesList.pushMap(serializedBarcode);
    }

    WritableMap event = Arguments.createMap();
    event.putString("type", "barcode");
    event.putArray("barcodes", barcodesList);
    event.putInt("target", getViewTag());
    return event;
  }

How to detect barcodes?

When we set ‘onGoogleVisionBarcodesDetected’ to receive the callback, another property ‘googleVisionBarcodeDetectorEnabled’ will be true in RMCamera.js:

_convertNativeProps(props: PropsType) {
    const newProps = mapValues(props, this._convertProp);

    if (props.onBarCodeRead) {
      newProps.barCodeScannerEnabled = true;
    }

    if (props.onGoogleVisionBarcodesDetected) {
      newProps.googleVisionBarcodeDetectorEnabled = true;
    }

With @ReactProp annotation, the native setter will be called in CameraViewManager.java:

@ReactProp(name = "googleVisionBarcodeDetectorEnabled")
  public void setGoogleVisionBarcodeDetecting(RNCameraView view, boolean googleBarcodeDetectorEnabled) {
    view.setShouldGoogleDetectBarcodes(googleBarcodeDetectorEnabled);
  }

Hereafter, the instance of the barcode reader will be created in RNCameraView.java:

public void setShouldGoogleDetectBarcodes(boolean shouldDetectBarcodes) {
    if (shouldDetectBarcodes && mGoogleBarcodeDetector == null) {
      setupBarcodeDetector();
    }
    this.mShouldGoogleDetectBarcodes = shouldDetectBarcodes;
    setScanning(mShouldDetectFaces || mShouldGoogleDetectBarcodes || mShouldScanBarCodes || mShouldRecognizeText);
  }

When we start the camera preview, the barcode reader will work if it has been initialized in RNCameraView.java:

@Override
      public void onFramePreview(CameraView cameraView, byte[] data, int width, int height, int rotation) {
        boolean willCallGoogleBarcodeTask = mShouldGoogleDetectBarcodes && !googleBarcodeDetectorTaskLock && cameraView instanceof BarcodeDetectorAsyncTaskDelegate;
if (willCallGoogleBarcodeTask) {
          googleBarcodeDetectorTaskLock = true;
          BarcodeDetectorAsyncTaskDelegate delegate = (BarcodeDetectorAsyncTaskDelegate) cameraView;
          new BarcodeDetectorAsyncTask(delegate, mGoogleBarcodeDetector, data, width, height, correctRotation).execute();
        }

The BarcodeDetectorAsyncTask.java extends AsyncTask. The barcode decoding API runs in the background:

@Override
  protected SparseArray<Barcode> doInBackground(Void... ignored) {
    if (isCancelled() || mDelegate == null || mBarcodeDetector == null || !mBarcodeDetector.isOperational()) {
      return null;
    }

    RNFrame frame = RNFrameFactory.buildFrame(mImageData, mWidth, mHeight, mRotation);
    return mBarcodeDetector.detect(frame);
  }

  @Override
  protected void onPostExecute(SparseArray<Barcode> barcodes) {
    super.onPostExecute(barcodes);

    if (barcodes == null) {
      mDelegate.onBarcodeDetectionError(mBarcodeDetector);
    } else {
      if (barcodes.size() > 0) {
        mDelegate.onBarcodesDetected(barcodes, mWidth, mHeight, mRotation);
      }
      mDelegate.onBarcodeDetectingTaskCompleted();
    }
  }

What should we do with Dynamsoft Barcode Reader?

For Dynamsoft Barcode Reader, the detection method and the returned result type are different. Create DynamsoftBarcodeDetectorAsyncTaskDelegate.java:

package org.reactnative.camera.tasks;

import com.dynamsoft.barcode.TextResult;

public interface DynamsoftBarcodeDetectorAsyncTaskDelegate {
    void onDynamsoftBarCodeDetected(TextResult[] barcodes, int width, int height);
    void onDynamsoftBarCodeDetectingTaskCompleted();
}

And DynamsoftBarcodeDetectorAsyncTask.java:

@Override
  protected TextResult[] doInBackground(Void... ignored) {
    if (isCancelled() || mDelegate == null) {
      return null;
    }

    TextResult[] results = null;

    try {
        mBarcodeReader.decodeBuffer(mImageData, mWidth, mHeight, mWidth, EnumImagePixelFormat.IPF_NV21, "");
        results = mBarcodeReader.getAllTextResults();
    } catch (Exception e) {
        e.printStackTrace();
    }

    return results;
  }

  @Override
  protected void onPostExecute(TextResult[] results) {
    super.onPostExecute(results);
    if (results != null) {
      mDelegate.onDynamsoftBarCodeDetected(results, mWidth, mHeight);
    }
    mDelegate.onDynamsoftBarCodeDetectingTaskCompleted();
  }

Define and use the event onDynamsoftVisionBarcodesDetected:

import React, { Component } from 'react';
import {StyleSheet, Text, View, Dimensions } from 'react-native';

import { RNCamera } from 'react-native-camera';

export default class App extends Component {
	constructor() {
		super();
		this.state = {
			barcodeResult: 'No barcode detected.'
		};
	}

	render() {
		return (
			<RNCamera
				ref={(ref) => {
					this.camera = ref;
				}}
				style={styles.preview}
				type={RNCamera.Constants.Type.back}
				flashMode={RNCamera.Constants.FlashMode.on}
				permissionDialogTitle={'Permission to use camera'}
        permissionDialogMessage={'We need your permission to use your camera phone'}
        onDynamsoftVisionBarcodesDetected={({ barcodes }) => {

				  if (barcodes) {
            if (barcodes.length == 0) {
              this.setState({barcodeResult: 'No barcode detected.'})
            }
            else {
              let text = '';
              for (let i in barcodes) {
                let type = barcodes[i]['type'];
                let data = barcodes[i]['data'];
                text += 'Type: ' + type + ', Value: ' + data;
              }
              this.setState({barcodeResult: text})
            }
				  }
				  else {
				    this.setState({barcodeResult: 'No barcode detected.'})
				  }

        }}
			>
				<View style={styles.textBg}>
					<Text style={styles.scanText}>{this.state.barcodeResult}</Text>
				</View>
			</RNCamera>
		);
	}
}

const deviceHeight = Dimensions.get('window').height;
const deviceWidth = Dimensions.get('window').width;
const styles = StyleSheet.create({
	textBg: {
		width: deviceWidth,
		height: 100,
		marginTop: deviceHeight - 100
	},
	preview: {
		flex: 1,
		alignItems: 'center',
		justifyContent: 'space-between'
	},
	scanText: {
		color: 'white',
    textAlign: 'center',
    fontSize: 20
	}
});

How to run the app?

When I tried to install the module from the local path, I got the error:

error: bundling failed: Error: Unable to resolve module `react-native-camera` from `F:\react-native-camera\Example\App.js`: Module `react-native-camera` does not exist in the Haste module map

node npm haste module map

module map

So far, I have not found any workaround for installing Node.js module locally. The only solution is to install react-native-camera to node_modules first:

npm install react-native-camera –save

Then, overwrite the module with my code.

Run the app:

react native camera with Dynamsoft Barcode Reader

Source Code

https://github.com/yushulx/react-native-camera