/mobile-app-features

How to Add QR Scanner to Your Mobile App

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 free Β consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Add QR Scanner to Your Mobile App

Adding QR Code Scanning to Your Mobile App: The Complete Guide

 

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:

  • Contactless payments grew 300% during the pandemic and haven't slowed down
  • Quick authentication without typing credentials
  • Instant product information access in retail environments
  • Event check-ins without paper tickets

 

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.

 

Architectural Considerations First

 

Before diving into code, let's consider where QR scanning fits in your app's architecture:

 

The Three-Layer Approach

 

  • UI Layer: Camera preview, scan guides, user feedback
  • Processing Layer: QR detection algorithms, image processing
  • Business Logic Layer: What happens after a successful scan

 

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.

 

Cross-Platform Implementation Options

 

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();
  }
}

 

The UX Layer: Beyond Just Scanning

 

Create a Superior Scanning Experience

 

The difference between amateur and professional QR implementations lies in the details:

  • Visual Guidance: Use an overlay with a highlighted scanning area
  • Feedback Mechanisms: Haptic feedback upon successful scan
  • Lighting Detection: Suggest flash in low-light conditions
  • Instructional Elements: Simple animations showing how to position the code

 

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       β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

 

Processing QR Content: The Business Logic

 

Not All QR Codes Are Equal

 

QR codes can contain various types of data:

  • Plain text or numbers
  • URLs (most common)
  • Contact information (vCard)
  • WiFi network credentials
  • Calendar events
  • Proprietary formats (e.g., payment systems)

 

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
}

 

Security Considerations

 

Don't Trust What You Scan

 

QR codes can be vectors for attacks, so protect your users:

  • Sanitize URLs: Check for malicious domains before opening
  • Preview content: Show users what they're about to open
  • Implement digital signatures: For proprietary QR formats
  • Rate limiting: Prevent rapid-fire scanning that could be exploitative

 

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'
    };
  }
}

 

Performance Optimization

 

Keep It Fast and Battery-Friendly

 

QR scanning can be resource-intensive. Optimize with these techniques:

  • Frame rate control: Process every 2-3 frames instead of every frame
  • Resolution scaling: Lower resolution for detection, full resolution for confirmation
  • Intelligent timeout: Stop the scanner after a period of inactivity
  • Light sensor integration: Adjust parameters based on ambient light

 

Testing Your QR Implementation

 

Covering All the Bases

 

A thorough testing strategy includes:

  • Various code densities: From simple URLs to complex data structures
  • Environmental testing: Different lighting conditions, angles, distances
  • Error states: Damaged codes, partial scans, unsupported formats
  • Permissions handling: How your app behaves when camera access is denied

 

Real-World Implementation Strategy

 

The Crawl-Walk-Run Approach

 

I recommend implementing QR scanning in phases:

  • Phase 1 (Crawl): Basic URL scanning with simple UI
  • Phase 2 (Walk): Support for multiple QR types and improved UX
  • Phase 3 (Run): Advanced features like batch scanning, history, custom QR generation

 

This approach lets you validate the feature with users before investing in the full suite of capabilities.

 

Conclusion: Beyond the Basics

 

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.

Ship QR Scanner 10x Faster with RapidDev

Connect with our team to unlock the full potential of code solutions with a no-commitment consultation!

Book a Free Consultation

Top 3 Mobile App QR Scanner Usecases

Explore the top 3 practical QR scanner use cases to enhance your mobile app’s functionality and user experience.

Contactless Information Exchange

 

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.

 

Secure Authentication & Access Control

 

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.

 

Touchless Payments & Transactions

 

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.


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project managementΒ organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive.Β 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

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.Β