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:
This article is Part 3 in a 3-Part Series.
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.
-
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>
-
Include
Dynamsoft Barcode Reader
to read QR codes andDynamsoft 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>
-
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()); }
-
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; } };
-
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); } }
-
Start scanning from the camera if the
startScan
URL param istrue
. This is useful to start scanning immediately.function init(){ if (getUrlParam("startScan") === "true") { startScan(); } }
-
Pass messages to React-Native-WebView.
We can use
window.ReactNativeWebView.postMessage
to pass messages from the WebView to the native side.-
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)); } }
-
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
-
Create a new React-Native project with Expo.
npx create-expo-app QRCodeScanner
-
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"] }
-
Install dependencies.
npx expo install react-native-webview expo-camera react-native-safe-area-context
Create a New QR Code Scanner Component
-
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', }, });
-
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"); })(); }, []); }
-
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
-
In
App.js
, useSafeAreaProvider
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> );
-
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