Implementing Barcode Scanning via Camera in Mobile Applications
Real-time barcode scanning through video stream — a task where platform APIs differ significantly. iOS provides AVCaptureSession, Android — Camera2 API or CameraX. The goal is the same: capture a frame, pass to decoder, get result with minimal latency.
iOS: AVCaptureSession + AVCaptureMetadataOutput
Classical approach — pipeline via AVCaptureSession:
let session = AVCaptureSession()
guard let device = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: device) else { return }
let metadataOutput = AVCaptureMetadataOutput()
session.addInput(input)
session.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
metadataOutput.metadataObjectTypes = [.ean13, .ean8, .code128, .upce, .qr]
// Preview
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.frame = view.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
DispatchQueue.global(qos: .userInitiated).async {
session.startRunning()
}
session.startRunning() always on background thread — on main thread this blocks UI for 300-600ms at startup.
Delegate AVCaptureMetadataOutputObjectsDelegate receives result:
func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
guard let object = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
let code = object.stringValue else { return }
session.stopRunning()
handleCode(code)
}
Scanning Zone
metadataOutput.rectOfInterest — restricts the area where codes are searched. Coordinates in normalized space (0.0-1.0), with axes inverted compared to UIKit. AVCaptureVideoPreviewLayer.metadataOutputRectConverted(fromLayerRect:) converts from UIKit coordinates to the required format.
Without rectOfInterest on crowded shelves with products, the camera may recognize a neighboring barcode instead of the intended one.
Android: CameraX + ML Kit
CameraX — recommended approach with API 21+:
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
val imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), BarcodeAnalyzer { barcode ->
handleBarcode(barcode)
})
cameraProvider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis)
}, ContextCompat.getMainExecutor(context))
STRATEGY_KEEP_ONLY_LATEST — critically important. Without it, frames accumulate in queue and decoder lags by 1-2 seconds.
BarcodeAnalyzer — implementation of ImageAnalysis.Analyzer, internally ML Kit:
class BarcodeAnalyzer(private val onDetected: (String) -> Unit) : ImageAnalysis.Analyzer {
private val scanner = BarcodeScanning.getClient(
BarcodeScannerOptions.Builder().setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS).build()
)
@androidx.camera.core.ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image ?: run { imageProxy.close(); return }
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
barcodes.firstOrNull()?.rawValue?.let { onDetected(it) }
}
.addOnCompleteListener { imageProxy.close() }
}
}
imageProxy.close() in addOnCompleteListener — mandatory, otherwise CameraX stops delivering new frames.
Camera Permission and Autofocus
On budget Android devices (Realme C-series, Tecno), autofocus can work unstably. CameraControl.startFocusAndMetering() with FocusMeteringAction helps force focus to center every 2 seconds.
On iOS — AVCaptureDevice.focusMode = .continuousAutoFocus and autoFocusRangeRestriction = .near for close-range scanning.
Implementation timeline: 1-3 days. Cost calculated individually.







