Learn how to easily add a QR scanner to your mobile app with our step-by-step guide for seamless integration and enhanced user experience.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Why QR Scanning Makes Sense in 2024
QR codes have evolved from marketing gimmicks to essential tools for modern apps. Today, they bridge physical and digital experiences in ways users now expect:
Adding QR functionality isn't just trendyβit removes friction at critical user moments. Let's walk through how to implement it properly across platforms.
Before diving into code, let's consider where QR scanning fits in your app's architecture:
The Three-Layer Approach
This separation matters because QR scanning tends to grow more complex over time. You might start with simple URL detection but later need to handle proprietary formats, offline scanning, or security verification.
Option 1: Native Implementation
For iOS, Apple's AVFoundation provides built-in QR scanning:
// AVFoundation handles camera access and QR detection
import AVFoundation
class QRScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var captureSession: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!
override func viewDidLoad() {
super.viewDidLoad()
// Setup begins here
captureSession = AVCaptureSession()
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
let videoInput: AVCaptureDeviceInput
// Safe device access pattern
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
return
}
if (captureSession.canAddInput(videoInput)) {
captureSession.addInput(videoInput)
} else {
// Handle the error appropriately
return
}
// Configure metadata output for QR codes
let metadataOutput = AVCaptureMetadataOutput()
if (captureSession.canAddOutput(metadataOutput)) {
captureSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
metadataOutput.metadataObjectTypes = [.qr] // We're only interested in QR codes
} else {
// Handle the error appropriately
return
}
// Preview setup
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
// Start capturing
captureSession.startRunning()
}
// Delegate method that handles the scanned codes
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
captureSession.stopRunning()
if let metadataObject = metadataObjects.first {
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
guard let stringValue = readableObject.stringValue else { return }
// Here's where you process the QR content
handleQRCode(stringValue)
}
}
func handleQRCode(_ code: String) {
// Your business logic here
print("QR code detected: \(code)")
}
}
For Android, we use the CameraX API with ML Kit:
// Using CameraX and ML Kit for modern, reliable scanning
class QRScannerActivity : AppCompatActivity() {
private lateinit var cameraExecutor: ExecutorService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_qr_scanner)
// Request camera permissions
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
cameraExecutor = Executors.newSingleThreadExecutor()
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// Preview use case
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewFinder.surfaceProvider)
}
// Image analysis use case
val imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(cameraExecutor, QRCodeAnalyzer { qrCode ->
// Process detected QR code on main thread
runOnUiThread {
handleQRCode(qrCode)
}
})
}
// Select back camera as default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageAnalyzer)
} catch(e: Exception) {
Log.e(TAG, "Use case binding failed", e)
}
}, ContextCompat.getMainExecutor(this))
}
private fun handleQRCode(code: String) {
// Your business logic here
}
// QR code analyzer class
private class QRCodeAnalyzer(private val onQRCodeDetected: (String) -> Unit) : ImageAnalysis.Analyzer {
private val scanner = BarcodeScanning.getClient()
@androidx.camera.core.ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(image)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
if (barcode.valueType == Barcode.TYPE_QR_CODE) {
barcode.rawValue?.let { onQRCodeDetected(it) }
}
}
}
.addOnCompleteListener {
imageProxy.close()
}
}
}
}
}
Option 2: Cross-Platform Libraries
If you're using React Native, here's how to implement QR scanning with react-native-camera:
import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text, Alert } from 'react-native';
import { RNCamera } from 'react-native-camera';
const QRScanner = ({ onCodeScanned }) => {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
// Permission handling logic would go here
const handleBarCodeScanned = ({ type, data }) => {
// Prevent multiple rapid scans
if (scanned) return;
setScanned(true);
// Only process QR codes
if (type === RNCamera.Constants.BarCodeType.qr) {
// Pass the data up to parent component
onCodeScanned(data);
// Reset after 2 seconds to allow scanning again
setTimeout(() => {
setScanned(false);
}, 2000);
}
};
return (
<View style={styles.container}>
{hasPermission === null ? (
<Text>Requesting camera permission...</Text>
) : hasPermission === false ? (
<Text>No access to camera</Text>
) : (
<RNCamera
style={styles.camera}
type={RNCamera.Constants.Type.back}
captureAudio={false}
barCodeTypes={[RNCamera.Constants.BarCodeType.qr]}
onBarCodeRead={handleBarCodeScanned}
androidCameraPermissionOptions={{
title: 'Permission to use camera',
message: 'We need your permission to use your camera',
buttonPositive: 'Ok',
buttonNegative: 'Cancel',
}}
>
<View style={styles.overlay}>
<View style={styles.scanArea} />
</View>
</RNCamera>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
camera: {
flex: 1,
},
overlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center',
alignItems: 'center',
},
scanArea: {
width: 200,
height: 200,
borderWidth: 2,
borderColor: '#fff',
backgroundColor: 'transparent',
},
});
export default QRScanner;
For Flutter, here's a clean implementation using the qr_code_scanner package:
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'dart:io';
class QRScannerView extends StatefulWidget {
final Function(String) onCodeScanned;
const QRScannerView({Key? key, required this.onCodeScanned}) : super(key: key);
@override
State<QRScannerView> createState() => _QRScannerViewState();
}
class _QRScannerViewState extends State<QRScannerView> {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
QRViewController? controller;
bool isScanned = false;
@override
void reassemble() {
super.reassemble();
// Need to pause camera on hot reload for iOS
if (Platform.isAndroid) {
controller?.pauseCamera();
} else if (Platform.isIOS) {
controller?.resumeCamera();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.blue,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: MediaQuery.of(context).size.width * 0.8,
),
),
Positioned(
bottom: 20,
left: 0,
right: 0,
child: Center(
child: Text(
'Align QR code within the frame',
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
],
),
);
}
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
if (!isScanned && scanData.code != null) {
setState(() {
isScanned = true;
});
// Pass scanned code to parent
widget.onCodeScanned(scanData.code!);
// Reset after handling
Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
isScanned = false;
});
}
});
}
});
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
}
Create a Superior Scanning Experience
The difference between amateur and professional QR implementations lies in the details:
Here's a diagram of how a complete QR scanner component fits together:
βββββββββββββββββββββββββββββββββββββββ
β QR Scanner View β
βββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββββββββββββββ β
β β Camera Preview Layer β β
β β β β
β β βββββββββββββββββββββ β β
β β β Scanning Target β β β
β β β Frame β β β
β β βββββββββββββββββββββ β β
β β β β
β βββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββ β
β β User Instruction β β
β β "Position QR within frame" β β
β βββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββ β
β β Torch/Flash Control β β
β βββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββ β
β β Cancel/Close Button β β
β βββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββ
Not All QR Codes Are Equal
QR codes can contain various types of data:
Here's a strategy pattern for handling different QR formats:
// QR content handler with strategy pattern
class QRContentHandler {
constructor() {
this.handlers = {
'URL': this.handleURL,
'CONTACT': this.handleContact,
'WIFI': this.handleWifi,
'TEXT': this.handleText,
// Add more handlers as needed
};
}
process(qrContent) {
// Detect content type
const contentType = this.detectContentType(qrContent);
// Find and execute the appropriate handler
if (this.handlers[contentType]) {
return this.handlers[contentType](qrContent);
}
// Default handler for unknown types
return this.handleUnknown(qrContent);
}
detectContentType(content) {
// URL detection
if (content.match(/^https?:\/\//i)) {
return 'URL';
}
// vCard detection
if (content.startsWith('BEGIN:VCARD')) {
return 'CONTACT';
}
// WiFi detection
if (content.startsWith('WIFI:')) {
return 'WIFI';
}
// Default to text
return 'TEXT';
}
handleURL(content) {
// URL validation and sanitization
// Then open in browser or in-app webview
return {
type: 'URL',
url: content,
action: 'OPEN_BROWSER'
};
}
// Other handler methods follow the same pattern
}
Don't Trust What You Scan
QR codes can be vectors for attacks, so protect your users:
Here's a simple URL validator example:
function validateQRUrl(url) {
// Basic structure validation
if (!url.match(/^https?:\/\//i)) {
return {
valid: false,
reason: 'Not a valid URL protocol'
};
}
// Check against known malicious domain list
// This would connect to your security service
const maliciousDomains = ['evil-domain.com', 'phishing-site.net'];
try {
const urlObj = new URL(url);
const domain = urlObj.hostname;
if (maliciousDomains.includes(domain)) {
return {
valid: false,
reason: 'Potentially unsafe website'
};
}
// Add more checks as needed
return {
valid: true,
url: url
};
} catch (e) {
return {
valid: false,
reason: 'Malformed URL'
};
}
}
Keep It Fast and Battery-Friendly
QR scanning can be resource-intensive. Optimize with these techniques:
Covering All the Bases
A thorough testing strategy includes:
The Crawl-Walk-Run Approach
I recommend implementing QR scanning in phases:
This approach lets you validate the feature with users before investing in the full suite of capabilities.
QR scanning seems simple on the surface, but doing it well requires attention to detail across multiple dimensions. The difference between a frustrating scanner that users abandon and one they rely on daily often comes down to these details.
Remember that QR codes are ultimately about removing friction. If your implementation adds more steps or confusion than the alternative, users will revert to manual methods. Focus on making the scan-to-action pipeline as seamless as possible.
As mobile technology evolves, we're seeing more integration between QR scanning and augmented reality, computer vision, and machine learning. Building your QR feature with a modular architecture now will make it easier to extend these capabilities in the future.
Explore the top 3 practical QR scanner use cases to enhance your mobile appβs functionality and user experience.
QR codes enable frictionless sharing of business information, event details, or digital assets without the awkwardness of manual data entry. Users simply scan to instantly capture structured data, making networking events smoother and reducing human error in information transfer. This creates a streamlined bridge between physical and digital environments without requiring NFC hardware.
QR codes provide a portable, dynamic authentication method that enhances security while simplifying user experience. They can generate time-sensitive access tokens, enable two-factor authentication, or validate tickets/passes with minimal user friction. Unlike static credentials, these can be refreshed periodically, making them significantly harder to counterfeit while maintaining ease of use.
QR-based payment systems create a hardware-independent alternative to NFC that works on virtually any smartphone with a camera. This dramatically lowers the barrier to mobile payments, especially in emerging markets where specialized POS hardware is less common. The solution scales from street vendors to enterprise retail, with transaction data easily integrated into loyalty programs and analytics platforms.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. Weβll discuss your project and provide a custom quote at no cost.Β