How to Configure a Mobile Scanner Flutter App for Continuous QR Code Scanning
Set allowDuplicates: true on the MobileScanner widget and avoid calling controller.stop() in the onDetect callback to keep the camera active after each QR code detection.
The mobile_scanner package is the most widely used library for adding QR code scanning capabilities to Flutter applications. While the mobile scanner Flutter plugin runs continuously by default, it automatically suppresses duplicate detections of the same barcode to prevent redundant callbacks. To achieve true continuous scanning—where the scanner fires repeatedly even when the same QR code remains in view—you must explicitly disable this deduplication filter.
Understanding the Default Scanning Behavior
By default, mobile_scanner implements a duplicate detection filter at the native level (Android ML Kit / iOS AVFoundation). When the camera detects a QR code, the plugin caches that barcode's value and prevents subsequent identical detections from triggering the Dart onDetect callback until the camera view changes or a different barcode appears.
This behavior optimizes battery life and reduces unnecessary UI updates for single-scan use cases. However, for applications requiring rapid, repeated scans—such as inventory management, attendance tracking, or high-frequency validation systems—this default filtering creates a bottleneck that stops the workflow after the first detection.
Enabling Continuous Scanning with allowDuplicates
The MobileScanner widget exposes an allowDuplicates boolean property that controls whether the plugin forwards every detection to your Dart code. Setting this property to true disables the native deduplication logic, ensuring onDetect fires for every frame containing a readable QR code.
The Role of MobileScannerController
While the MobileScanner widget handles the UI preview, the MobileScannerController class manages the underlying camera session and platform channel communication. The controller initializes the native scanner, manages torch state, and transmits the allowDuplicates flag to the platform-specific implementation via Flutter's method channels.
Crucially, you must avoid invoking controller.stop() inside your detection callback. Calling stop() terminates the camera session, requiring a manual controller.start() to resume scanning—a pattern that breaks continuous operation.
Complete Implementation Example
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class ContinuousQrScanner extends StatefulWidget {
const ContinuousQrScanner({Key? key}) : super(key: key);
@override
State<ContinuousQrScanner> createState() => _ContinuousQrScannerState();
}
class _ContinuousQrScannerState extends State<ContinuousQrScanner> {
// Controller manages camera session and platform communication
final MobileScannerController _controller = MobileScannerController();
// Counter demonstrates continuous firing behavior
int _scanCount = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Continuous QR Scanner')),
body: Stack(
children: [
// MobileScanner widget with allowDuplicates enabled
MobileScanner(
controller: _controller,
allowDuplicates: true, // Critical: disables native deduplication
onDetect: (barcode, args) {
// This callback fires for every detection frame
setState(() => _scanCount++);
debugPrint('Scan #$_scanCount: ${barcode.rawValue}');
// Do NOT call _controller.stop() here
},
),
// UI overlay showing detection count
Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Detected $_scanCount times',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
shadows: [Shadow(blurRadius: 4, color: Colors.black)],
),
),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => _controller.toggleTorch(),
child: const Icon(Icons.flash_on),
),
);
}
@override
void dispose() {
// Clean up controller to release camera resources
_controller.dispose();
super.dispose();
}
}
How Continuous Scanning Works Under the Hood
The mobile scanner Flutter plugin relies on Flutter's platform view and method channel infrastructure to bridge Dart code with native camera and ML Kit implementations.
Platform View Integration
The MobileScanner widget creates a platform view that embeds the native camera preview into the Flutter widget tree. According to the Flutter framework source, this mechanism is illustrated in packages/flutter/lib/src/widgets/placeholder.dart, which demonstrates how platform views are embedded within the widget hierarchy. The scanner widget uses this infrastructure to render the live camera feed while maintaining gesture and layout compatibility with other Flutter widgets.
Method Channel Communication
The MobileScannerController communicates configuration parameters—including the allowDuplicates flag—to the native side via Flutter's method channels. The implementation pattern follows the architecture defined in packages/flutter/lib/src/services/platform_channel.dart, where asynchronous messages are sent to platform-specific code. When you set allowDuplicates: true, the controller transmits this boolean to the native Android (ML Kit) and iOS (AVFoundation) implementations, instructing them to bypass the duplicate detection cache.
Native Plugin Integration
The actual barcode detection occurs in native code integrated through Flutter's plugin system. The build configuration for such integrations is exemplified in packages/flutter_tools/gradle/flutter.gradle, which demonstrates how external plugins hook into the Flutter build system. The mobile_scanner plugin uses this infrastructure to link ML Kit on Android and AVFoundation on iOS, providing the computer vision capabilities that detect QR codes in each camera frame.
Best Practices for Continuous Scanning
When enabling continuous scanning in production applications, consider these performance and user experience optimizations:
-
Implement client-side throttling: Even with
allowDuplicates: true, rapid successive detections can overwhelm your UI or backend. Use aTimerordebouncemechanism to limit processing to one detection every 500-1000ms when the same barcode remains in view. -
Manage camera lifecycle explicitly: Always dispose of the
MobileScannerControllerin your widget'sdispose()method to prevent memory leaks and ensure the camera hardware is released for other applications. -
Handle permissions gracefully: Continuous scanning requires sustained camera access. Request
CAMERApermissions before initializing the scanner and provide clear UI feedback if permissions are denied. -
Optimize for specific barcode formats: If your use case only requires QR codes, configure the
formatsparameter to[BarcodeFormat.qrCode]to reduce CPU usage by ignoring other barcode symbologies.
Summary
- The
mobile_scannerFlutter plugin filters duplicate barcode detections by default to prevent redundant callbacks. - Set
allowDuplicates: trueon theMobileScannerwidget to disable this filtering and enable continuous scanning. - Avoid calling
controller.stop()inside theonDetectcallback to keep the camera session active. - The plugin uses Flutter's platform view system (referenced in
packages/flutter/lib/src/widgets/placeholder.dart) and method channels (packages/flutter/lib/src/services/platform_channel.dart) to bridge Dart code with native ML Kit and AVFoundation implementations. - Implement client-side throttling when processing high-frequency detections to maintain UI responsiveness.
Frequently Asked Questions
Why does my Flutter QR scanner stop after the first detection?
By default, the mobile_scanner plugin implements duplicate detection logic that prevents the same barcode from triggering your onDetect callback multiple times. This optimizes battery life for single-scan use cases. To change this behavior, set allowDuplicates: true on your MobileScanner widget.
How do I prevent duplicate QR code entries while using continuous scanning?
When allowDuplicates: true, implement your own deduplication logic in Dart. Maintain a Set<String> of recently scanned codes and clear it periodically, or use a Timer to enforce a cooldown period (e.g., 2 seconds) before accepting the same barcode again. This gives you fine-grained control over what constitutes a "duplicate" in your business logic.
Does enabling allowDuplicates affect battery life or performance?
Yes, continuous scanning increases CPU usage because every camera frame containing a valid barcode triggers a Dart callback. The native detection still runs at the camera frame rate, but the bridge between native and Dart code executes more frequently. To mitigate this, throttle your callback processing and consider reducing the camera resolution or limiting detection to specific barcode formats using the formats parameter.
Can I pause and resume scanning manually while keeping the camera preview active?
Yes, use the MobileScannerController to control the scanning state independently of the camera preview. Call controller.stop() to pause detection (the preview may freeze or show the last frame depending on platform), and controller.start() to resume. However, if you want the preview to remain live while ignoring detections temporarily, keep the controller running but set a boolean flag in your Dart code to ignore onDetect callbacks until you're ready to process them again.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →