How to Start a QR Code Scanner in React Native WebView

React Native is an open-source UI software framework to create apps mainly for Android and iOS. Apart from native components, we can also use React-Native-WebView to utilize web technologies.

In this article, we are going to take about how to start a QR code scanner in React-Native-WebView. We can use getUserMedia to start the camera and use Dynamsoft Barcode Reader to read QR codes from camera frames. An advantage of using WebView is that we can use third-party barcode SDKs in an Expo-managed app to quickly create an MVP.

Demo video of the final result:

Create a Web Page to Scan QR Codes

First, we need to create a web page to scan QR codes which later can be used in the WebView.

  1. Create a new HTML file with the following content.

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta
          name="viewport"
          content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
        />
        <title>QR Code Scanner in React-Native-WebView</title>
        <style>
        </style>
      </head>
      <body>
        <div class="scanner">
        </div>
        <script>
        </script>
      </body>
    </html>
    
  2. Include Dynamsoft Barcode Reader to read QR codes and Dynamsoft Camera Enhancer to access the camera.

    <script src="https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@3.3.4/dist/dce.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.20/dist/dbr.js"></script>
    
  3. Initialize the barcode reader and the camera enhancer. Dynamsoft Barcode Reader requires a license. You can apply for one here.

    init();
    async function init(){
      Dynamsoft.DBR.BarcodeScanner.license = 'DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=='; // one-day trial
      reader = await Dynamsoft.DBR.BarcodeScanner.createInstance();
      enhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance();
      await enhancer.setUIElement(Dynamsoft.DCE.CameraEnhancer.defaultUIElementURL);
      let container = document.getElementsByClassName("scanner")[0];
      container.appendChild(enhancer.getUIElement());
    }
    
  4. Add a button to set an interval to start scanning.

    HTML:

    <div class="controls">
      <button onclick="startScan();">Scan From Camera</button>
    </div>
    

    JavaScript:

    function init(){
      //...
      enhancer.on("played", (playCallbackInfo) => {
        console.log("camera started");
        startProcessingLoop();
      });
      enhancer.on("cameraClose", playCallBackInfo => {
        stopScan();
      });
      //...
    }
       
    function startScan(){
      if (!enhancer || !reader) {
        alert("Please wait for the initialization of Dynamsoft Barcode Reader");
        return;
      }
      document.getElementsByClassName("scanner")[0].classList.add("active");
      enhancer.open(true); //start the camera
    }
         
    function stopScan(){
      stopProcessingLoop();
      enhancer.close(true);
      document.getElementsByClassName("scanner")[0].classList.remove("active");
    }
    function startProcessingLoop(isBarcode){
      stopProcessingLoop();
      interval = setInterval(captureAndDecode,100); // read barcodes
    }
    
    function stopProcessingLoop(){
      if (interval) {
        clearInterval(interval);
        interval = undefined;
      }
      processing = false;
    }
    
    async function captureAndDecode() {
      if (!enhancer || !reader) {
        return
      }
      if (enhancer.isOpen() === false) {
        return;
      }
      if (processing == true) {
        return;
      }
      processing = true; // set processing to true so that the next frame will be skipped if the processing has not completed.
      let frame = enhancer.getFrame();
      if (frame) {  
        let results = await reader.decode(frame);
        console.log(results);
        processing = false;
      }
    };
    
  5. Add a button to read barcodes from static images.

    HTML:

    <div class="controls">
      <input type="file" onchange="readFromImage();" name="file" accept=".jpg,.jpeg,.png,.bmp" id="file" style="display:none">
      <button onclick="document.getElementById('file').click()" >Read From Image</button>
    </div>
    

    JavaScript:

    async function readFromImage(){
      let fileInput = document.getElementById("file");
      if (fileInput.files.length > 0) {
        let file = fileInput.files[0];
        let results = await reader.decode(file);
        console.log(results);
      }
    }
    
  6. Start scanning from the camera if the startScan URL param is true. This is useful to start scanning immediately.

    function init(){
      if (getUrlParam("startScan") === "true") {
        startScan();
      }
    }
    
  7. Pass messages to React-Native-WebView.

    We can use window.ReactNativeWebView.postMessage to pass messages from the WebView to the native side.

    1. Pass the barcode results when at least one barcode is found.

      if (results.length > 0) {
        stopScan();
        if (window.ReactNativeWebView) {
          window.ReactNativeWebView.postMessage(JSON.stringify(results));
        }
      }
      
    2. Pass an empty message if the camera stops.

      enhancer.on("cameraClose", playCallBackInfo => {
        stopScan();
        if (window.ReactNativeWebView) {
          window.ReactNativeWebView.postMessage("");
        }
      });
      

Here is a link to the page: https://tony-xlh.github.io/Vanilla-JS-Barcode-Reader-Demos/React-Native-Webview/

Create a New React-Native Project to Use the QR Code Scanner in WebView

Create and Setup the Project

  1. Create a new React-Native project with Expo.

    npx create-expo-app QRCodeScanner
    
  2. Add camera permissions in app.json.

    "ios": {
      "infoPlist": {
        "NSCameraUsageDescription": "This app uses the camera to scan barcodes."
      }
    },
    "android": {
      "permissions": ["android.permission.CAMERA","android.permission.INTERNET"]
    }
    
  3. Install dependencies.

    npx expo install react-native-webview expo-camera react-native-safe-area-context
    

Create a New QR Code Scanner Component

  1. Create a new file named QRCodeScanner.js with the following content.

    import { StyleSheet, View } from 'react-native';
    
    export default function QRCodeScanner(props) {
      return <View />;
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: '#fff',
      },
    });
    
  2. Request camera permission when the component is mounted with expo-camera.

    export default function QRCodeScanner(props) {
      const [hasPermission, setHasPermission] = useState(null);
      useEffect(() => {
        (async () => {
          const { status } = await Camera.requestCameraPermissionsAsync();
          setHasPermission(status === "granted");
        })();
      }, []);
    }
    
  3. If the camera permission is granted, render the WebView with its URI set to the web page we just made.

    if (hasPermission) {
      return (
        <WebView
          style={styles.container}
          allowsInlineMediaPlayback={true}
          mediaPlaybackRequiresUserAction={false}
          onMessage={(event) => {
            if (!event.nativeEvent.data) {
              if (props.onClosed) {
                props.onClosed();
              }
            }else{
              if (props.onScanned) {
                const results = JSON.parse(event.nativeEvent.data)
                props.onScanned(results);
              }
            }
          }}
          source={{ uri: 'https://tony-xlh.github.io/Vanilla-JS-Barcode-Reader-Demos/React-Native-Webview/?startScan=true' }}
        />
      );
    }
    

Use the QR Code Scanner Component

  1. In App.js, use SafeAreaProvider as the root component to avoid the app being blocked by the status bar.

    return (
      <SafeAreaProvider>
        <SafeAreaView style={styles.container}>
          <StatusBar style="auto"/>
        </SafeAreaView>
      </SafeAreaProvider>
    );
    
  2. Add the QRCodeScanner component. It is rendered when the start scanning button is pressed.

    export default function App() {
      const [scanning,setScanning] = useState(false);
      const showResults = (results) => {
        let title = "Found " + results.length + ((results.length>1)?" results":" result")
        let message = "";
        for (let index = 0; index < results.length; index++) {
          const result = results[index];
          message = message + result.barcodeFormatString + ": " + result.barcodeText + "\n";
        }
    
        Alert.alert(title,message);
        setScanning(false);
      }
    
      return (
        <SafeAreaProvider>
          <SafeAreaView style={styles.container}>
            {scanning &&
              <QRCodeScanner
                onScanned={(results)=>showResults(results)}
                onClosed={()=>setScanning(false)}
              ></QRCodeScanner>
            }
            {!scanning &&
              <View style={{alignItems:'center'}}>
                <Text style={styles.title}>
                    Dynamsoft Barcode Reader Demo
                  </Text>
                <Button title='Start QR Code Scanner' onPress={() => setScanning(true)}></Button>
              </View>
            }
            <StatusBar style="auto"/>
          </SafeAreaView>
        </SafeAreaProvider>
      );
    }
    

Source Code

You can check out the source code of the demo to have a try.

https://github.com/tony-xlh/QR-Code-Scanner-React-Native-WebView