Introduction
Building a reliable barcode scanner is a common requirement in Flutter mobile development. Continuous scanning, where the camera keeps detecting barcodes, improves UX but introduces duplicate reads and noisy events. This tutorial shows a practical pattern: stream detection + debounce + deduplication. We use the mobile_scanner package concepts, but the debounce ideas apply to any camera/barcode pipeline.
Setup And Permissions
Add mobile_scanner to pubspec.yaml and request camera permission for Android/iOS. In Flutter, keep camera lifecycle management tied to widget lifecycle to avoid resource leaks. Example dependencies:
mobile_scanner: ^2.0.0 (check latest)
Ask for permission at startup or handle the denied state gracefully in the UI. On Android, add CAMERA permission in AndroidManifest.xml. On iOS, add NSCameraUsageDescription in Info.plist.
Camera Stream And Barcode Detection
mobile_scanner exposes a continuous detection callback (onDetect). Each onDetect can contain one or more barcodes; these callbacks can fire dozens per second while the camera sees a barcode. Forward raw detections into a controlled stream rather than processing them directly in the UI callback—this decouples detection from handling and is better for testing and throttling.
Example widget-level setup that listens for detections:
final MobileScannerController scannerController = MobileScannerController();
MobileScanner(
controller: scannerController,
onDetect: (capture) {
final barcode = capture.barcodes.first.value;
detectionSink.add(barcode);
},
);This pushes barcode strings into detectionSink (a StreamController) where debounce/deduplication logic resides.
Debounce And Continuous Scan Logic
Two objectives for robust continuous scanning:
Debounce bursts of repeated detections (same frame or consecutive frames).
Deduplicate identical values to avoid processing the same code repeatedly while the camera remains on it.
A simple and effective approach uses a short debounce window (e.g., 400ms) plus a recent-history for dedupe (store last scanned and optionally a timestamp). Use Timer-based debounce for minimal dependencies.
String? _lastScanned;
Timer? _debounceTimer;
final Duration debounceDuration = Duration(milliseconds: 400);
void handleRawDetection(String code, void Function(String) onScan) {
_debounceTimer?.cancel();
_debounceTimer = Timer(debounceDuration, () {
if (code != _lastScanned) {
_lastScanned = code;
onScan(code);
}
});
}This function resets the timer on every detection; only when detections stop for debounceDuration will it process the last observed barcode. It also ignores repeats of the most recent successfully processed code. If you prefer to capture the first seen code instead of the last, trigger on the first detection and block further processing until a cooldown expires.
For use-cases requiring rapid repeated legitimate scans (different barcodes back-to-back), maintain a small in-memory MRU list with timestamps and expire entries after a few seconds.
UI And Best Practices
Visual Feedback: Show an overlay or flash when a code is accepted so users know a scan succeeded. This also prevents them from moving off the barcode prematurely.
Cooldown vs Debounce: Debounce waits for a quiet period then handles the last value. Cooldown acts immediately and blocks further scans for a duration. Choose based on UX: cooldown is immediate but may pick the earliest noisy read; debounce picks the stable last read.
Threading and Isolates: Barcode decoding is handled by the plugin natively; avoid heavy work in the onScan handler. If you need to do CPU work (e.g., network calls, DB writes), offload to a background isolate or queue jobs to avoid jank.
Error Handling: Handle null/empty barcode values, permission denials, and camera interruptions (app lifecycle). Pause scanning when the app goes to background or when navigating to a result page.
Performance: Limit frame resolution if CPU/battery is a concern. mobile_scanner lets you configure detection speed vs accuracy tradeoffs.
Testing: Simulate detection events via your StreamController in widget tests to verify debounce/dedup logic deterministically.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
Continuous barcode scanning in Flutter is achievable with a stable camera stream plus simple debounce and deduplication. Use a StreamController to decouple detection from processing, apply a short debounce window to consolidate noisy events, and maintain a recent-history or cooldown to prevent duplicates. These techniques keep your mobile development code responsive, battery-friendly, and user-friendly while avoiding repeated scans and race conditions.