QR Scanner Hybrid
A high-accuracy QR code scanner for Flutter that combines Google ML Kit and ZXing scanning engines for maximum reliability across different lighting conditions and QR code qualities.
Features
- Dual Scanning Engines: Combines Google ML Kit (real-time) and ZXing (enhanced processing) for superior accuracy
- Automatic Fallback: Switches to ZXing enhancement when ML Kit struggles with difficult codes
- Built-in Torch: Toggle flashlight for low-light scanning
- Performance Optimized: Frame throttling and intelligent processing to minimize battery drain
- Image Enhancement: Automatic contrast and grayscale adjustments for challenging QR codes
- Cross-Platform: Works on Android and iOS
- Easy Integration: Single widget drop-in solution
Screenshots
| Scanning | Detected |
|---|---|
![]() |
![]() |
Installation
Add this to your package's pubspec.yaml file:
dependencies:
qr_scanner_hybrid: ^0.1.0
Then run:
flutter pub get
Platform Setup
Android
Add the following permissions to your AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
Update android/app/build.gradle:
android {
compileSdkVersion 34 // or higher
defaultConfig {
minSdkVersion 21 // ML Kit requires API 21+
}
}
iOS
Add the following to your Info.plist:
<key>NSCameraUsageDescription</key>
<string>Camera access is required to scan QR codes</string>
Update minimum iOS version in ios/Podfile:
platform :ios, '12.0' # or higher
Usage
Basic Implementation
import 'package:flutter/material.dart';
import 'package:qr_scanner_hybrid/qr_scanner_hybrid.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const QrScannerHybrid(),
);
}
}
With Callback and Auto-Close
import 'package:flutter/material.dart';
import 'package:qr_scanner_hybrid/qr_scanner_hybrid.dart';
class ScannerPage extends StatelessWidget {
const ScannerPage({super.key});
void _scanQrCode(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QrScannerHybrid(
autoClose: true,
onDetected: (data, engine) {
print('QR Code detected via $engine: $data');
},
),
),
);
if (result != null && result is Map<String, String>) {
// Handle the scanned result
String qrData = result['data']!;
String engine = result['engine']!;
print('Scanned: $qrData');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('QR Scanner')),
body: Center(
child: ElevatedButton(
onPressed: () => _scanQrCode(context),
child: const Text('Scan QR Code'),
),
),
);
}
}
With Custom UI Configuration
QrScannerHybrid(
onDetected: (data, engine) {
print('Detected: $data via $engine');
},
config: const ScannerConfig(
title: 'Scan QR Code',
borderColor: Colors.blue,
borderWidth: 4,
borderRadius: 20,
scanAreaSize: 0.7,
appBarColor: Colors.blue,
overlayColor: Colors.black54,
showStatusText: true,
showTorchToggle: true,
),
)
Minimal Configuration
QrScannerHybrid(
autoClose: true,
config: const ScannerConfig(
showAppBar: false,
showStatusText: false,
scanAreaSize: 0.8,
),
)
How It Works
- Primary Scanning: ML Kit processes camera frames in real-time (~8 fps) for instant detection
- Smart Fallback: After 15 frames without detection, triggers enhanced ZXing scan
- Image Enhancement: Captures photo, applies grayscale + contrast boost using isolate
- ZXing Processing: Enhanced image processed with
tryHardermode for difficult codes - Result Display: Shows detected data with scan engine information
Performance
- Frame Rate: ~8 FPS processing rate (120ms throttle)
- Memory: Efficient YUV420 to NV21 conversion
- Battery: Minimal drain through frame throttling
- Isolate Processing: Image enhancement runs on separate thread
API Reference
QrScannerHybrid
Main widget that provides QR scanning functionality.
Constructor Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
onDetected |
OnQrCodeDetected? |
null |
Callback when QR code is detected. Receives (String data, String engine) |
onClose |
VoidCallback? |
null |
Callback when scanner is closed |
config |
ScannerConfig |
ScannerConfig() |
UI configuration options |
autoClose |
bool |
false |
Automatically close scanner after detection |
OnQrCodeDetected Callback
typedef OnQrCodeDetected = void Function(String data, String engine);
data: The scanned QR code contentengine: The scanning engine that detected it ("ML Kit Live"or"ZXing Enhanced")
ScannerConfig
UI configuration class for customizing the scanner appearance.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
borderColor |
Color |
Colors.green |
Color of the scan area border |
borderWidth |
double |
3.0 |
Width of the scan area border |
borderRadius |
double |
16.0 |
Border radius of the scan area |
scanAreaSize |
double |
0.7 |
Size of scan area as fraction of screen width (0.0-1.0) |
overlayColor |
Color |
Colors.black54 |
Background overlay color |
showStatusText |
bool |
true |
Show scanning status text |
title |
String? |
null |
AppBar title (defaults to "QR Scanner") |
showAppBar |
bool |
true |
Show the AppBar |
appBarColor |
Color? |
null |
AppBar background color (defaults to black) |
showTorchToggle |
bool |
true |
Show torch/flashlight toggle button |
Example Configuration
const ScannerConfig(
title: 'My Custom Scanner',
borderColor: Colors.purple,
borderWidth: 4,
borderRadius: 20,
scanAreaSize: 0.65,
appBarColor: Colors.purple,
overlayColor: Colors.black45,
showStatusText: true,
showTorchToggle: true,
)
Return Value
When the scanner is closed or auto-closed, it returns:
Map<String, String> {
'data': 'scanned_qr_code_content',
'engine': 'ML Kit Live' // or 'ZXing Enhanced'
}
Access via Navigator.pop() result:
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => QrScannerHybrid()),
);
if (result != null && result is Map<String, String>) {
String data = result['data']!;
String engine = result['engine']!;
}
Troubleshooting
Camera Permission Denied
Ensure you've added platform-specific permissions (see Platform Setup).
ML Kit Not Working on Android
Verify minSdkVersion is 21 or higher in android/app/build.gradle.
Black Screen on iOS
Check that NSCameraUsageDescription is added to Info.plist.
QR Code Not Detected
- Ensure adequate lighting or use the built-in torch
- Hold device steady and keep QR code within the green frame
- Try different distances from the QR code
- The scanner will automatically try enhanced mode after a few seconds
Examples
Check the example directory for a complete working application.
Roadmap
xCustomizable callbacks for detection eventsxConfigurable scan area and overlayxCustom UI theming optionsSupport for multiple barcode formats (beyond QR codes)Batch scanning modeGallery image scanningScan history managementQR code generation
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Credits
This package uses:
- google_mlkit_barcode_scanning for ML Kit integration
- flutter_zxing for ZXing scanning
- camera for camera control
- image for image processing
Support
For issues and feature requests, please visit the issue tracker.
Libraries
- optimized_dual_scanner
- qr_scanner_hybrid
- High-accuracy QR code scanner library using dual scanning engines.

