ultra_qr_scanner 3.0.3

SDKflutter
Platformandroidios

Ultra-fast, low-latency QR code & Barcode scanner plugin for Flutter with native performance optimization.

Ultra QR Scanner 📱⚡

pub package GitHub Platform

Ultra-fast, low-latency QR code scanner plugin for Flutter with native camera preview and performance optimization.

🎯 Goal: Open scanner → show camera preview → detect QR instantly → return result → done ✅

✨ Features

🚀 Ultra-fast startup - Preload scanner during app initialization for instant access
Native performance - CameraX (Android) + AVCapture (iOS) with platform views
📸 Live camera preview - Real-time camera feed with customizable overlay
🤖 Auto-start scanning - Optional automatic scanning when widget appears
👆 Manual controls - User-controlled start/stop with customizable UI
📱 Simple API - Single scan or continuous stream modes
🔦 Flash control - Toggle flashlight on supported devices
📷 Camera switching - Front/back camera support
🛡️ Production ready - Comprehensive error handling & memory management
🎨 Customizable UI - Custom overlays, buttons, and scanning frames

🚀 Performance Optimizations

FeatureDescriptionBenefit
Native Camera APIsCameraX on Android, AVCaptureSession on iOSMaximum hardware utilization
Platform ViewsNative camera preview renderingSeamless integration with Flutter UI
ML FrameworksMLKit Barcode Scanning (Android), Vision API (iOS)Optimized QR detection
ThreadingBackground processing with main thread UI updatesNon-blocking UI performance
Auto-start ModeBegin scanning immediately when widget appearsZero user interaction needed
Auto-stopImmediate camera shutdown after detectionZero waste of resources
PreloadingInitialize during app startup< 50ms to first scan
Memory ManagementProper cleanup and lifecycle handlingLeak-free operation

📊 Benchmarks

MetricTargetTypical Result
Cold Start Time< 300ms~200ms
Auto-Start Time< 100ms~50ms
QR Detection Speed< 100ms~50ms
Memory Usage< 50MB~35MB
Battery ImpactMinimal2-3% per hour
Frame Rate30 FPSStable 30 FPS

🛠 Installation

Add to your pubspec.yaml:

dependencies:
  ultra_qr_scanner: ^3.0.3
flutter pub get

🏃‍♂️ Quick Start

import 'package:ultra_qr_scanner/ultra_qr_scanner.dart';

// Best practice: Initialize during app startup for faster access
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Request camera permission and prepare scanner
  final hasPermission = await UltraQrScanner.requestPermissions();
  if (hasPermission) {
    await UltraQrScanner.prepareScanner();
  }
  
  runApp(MyApp());
}
import 'package:ultra_qr_scanner/ultra_qr_scanner_widget.dart';

class QuickScannerPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Quick QR Scanner')),
      body: Container(
        width: 300,
        height: 300,
        child: UltraQrScannerWidget(
          onCodeDetected:(code , type) {
            print('QR Code detected: $qrCode');
            Navigator.pop(context, qrCode);
          },
          showFlashToggle: true,       // Show flash button
          autoStop: true,              // Auto-stop after detection
          showStartStopButton: false,  // Hide manual controls
          autoStart: true,             // Start scanning immediately
        ),
      ),
    );
  }
}

3. Manual Scanner (Traditional User-Controlled)

class ManualScannerPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Manual QR Scanner')),
      body: Container(
        width: 300,
        height: 300,
        child: UltraQrScannerWidget(
          onCodeDetected:(code , type) {
            print('QR Code detected: $qrCode');
            Navigator.pop(context, qrCode);
          },
          showFlashToggle: true,       // Show flash button
          autoStop: true,              // Auto-stop after detection
          showStartStopButton: true,   // Show start/stop button
          autoStart: false,            // Wait for user to start
        ),
      ),
    );
  }
}

4. Programmatic Scanning (Alternative)

// Single scan
Future<void> scanQRCode() async {
  try {
    final qrCode = await UltraQrScanner.scanOnce();
    if (qrCode != null) {
      print('Scanned QR Code: $qrCode');
    }
  } catch (e) {
    print('Scan failed: $e');
  }
}

// Continuous scanning stream
StreamSubscription<String>? _scanSubscription;

void startContinuousScanning() {
  _scanSubscription = UltraQrScanner.scanStream().listen(
    (qrCode) {
      print('Detected QR Code: $qrCode');
    },
    onError: (error) {
      print('Scan error: $error');
    },
  );
}

void stopScanning() {
  _scanSubscription?.cancel();
  UltraQrScanner.stopScanner();
}

🎨 Widget Customization

Scanning Modes

The widget supports two main scanning modes:

🤖 Auto-Start Mode (Instant Scanning)

Perfect for quick scanning scenarios where you want immediate results:

UltraQrScannerWidget(
  onCodeDetected:(code , type) => handleQRCode(qrCode),
  autoStart: true,             // Start scanning immediately
  showStartStopButton: false,  // Hide manual controls
  autoStop: true,              // Stop after first detection
  showFlashToggle: true,       // Optional flash control
)

👆 Manual Mode (User-Controlled)

Traditional scanning with user controls:

UltraQrScannerWidget(
  onCodeDetected:(code , type) => handleQRCode(qrCode),
  autoStart: false,            // Wait for user action
  showStartStopButton: true,   // Show start/stop button
  autoStop: true,              // Stop after first detection
  showFlashToggle: true,       // Optional flash control
)

🔄 Hybrid Mode (Best of Both)

Auto-start with manual controls available:

UltraQrScannerWidget(
  onCodeDetected:(code , type) => handleQRCode(qrCode),
  autoStart: true,             // Start immediately
  showStartStopButton: true,   // But also show controls
  autoStop: false,             // Continuous scanning
  showFlashToggle: true,       // Flash control
)

Widget Configuration Options

ParameterTypeDefaultDescription
onQrDetectedFunction(String)RequiredCallback when QR code is detected
autoStartboolfalseStart scanning automatically when widget appears
showStartStopButtonbooltrueShow/hide the start/stop scan button
autoStopbooltrueStop scanning after first QR code detection
showFlashToggleboolfalseShow/hide flash/torch toggle button
overlayWidget?nullCustom overlay widget (uses default if null)

Custom Overlay Example

UltraQrScannerWidget(
  onCodeDetected:(code , type) (qrCode) => handleQRCode(qrCode),
  autoStart: true,
  showStartStopButton: false,
  overlay: Stack(
    children: [
      // Semi-transparent background
      Container(color: Colors.black54),
      
      // Custom scanning frame
      Center(
        child: Container(
          width: 250,
          height: 250,
          decoration: BoxDecoration(
            border: Border.all(color: Colors.green, width: 4),
            borderRadius: BorderRadius.circular(20),
          ),
          child: Stack(
            children: [
              // Corner decorations
              ...buildCornerDecorations(),
              
              // Center dot
              Center(
                child: Container(
                  width: 8,
                  height: 8,
                  decoration: BoxDecoration(
                    color: Colors.green,
                    shape: BoxShape.circle,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
      
      // Custom instructions
      Positioned(
        top: 60,
        left: 0,
        right: 0,
        child: Column(
          children: [
            Icon(Icons.qr_code_scanner, color: Colors.white, size: 48),
            SizedBox(height: 16),
            Text(
              'Scan QR Code',
              textAlign: TextAlign.center,
              style: TextStyle(
                color: Colors.white,
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(height: 8),
            Text(
              'Position the QR code within the frame',
              textAlign: TextAlign.center,
              style: TextStyle(
                color: Colors.white70,
                fontSize: 16,
              ),
            ),
          ],
        ),
      ),
    ],
  ),
)

Usage Scenarios

🚀 Instant QR Scanner (No UI Friction)

// Perfect for: Payment apps, quick actions, URL scanning
UltraQrScannerWidget(
  onCodeDetected:(code , type) => processPayment(code),
  autoStart: true,
  showStartStopButton: false,
  autoStop: true,
)

📷 Traditional Scanner (User Control)

// Perfect for: Document scanning, batch operations, careful scanning
UltraQrScannerWidget(
  onCodeDetected:(code , type) => addToList(code),
  autoStart: false,
  showStartStopButton: true,
  autoStop: false, // Continuous scanning
)
// Perfect for: Professional apps, feature-rich scanning
UltraQrScannerWidget(
  oonCodeDetected:(code , type) => handleCode(code),
  autoStart: true,
  showStartStopButton: true,
  showFlashToggle: true,
  autoStop: true,
)

🔧 Advanced Features

Flash Control

// Toggle flashlight
await UltraQrScanner.toggleFlash(true);  // Turn on
await UltraQrScanner.toggleFlash(false); // Turn off

Camera Switching

// Switch between front and back camera
await UltraQrScanner.switchCamera('front'); // Use front camera
await UltraQrScanner.switchCamera('back');  // Use back camera

Scanner Lifecycle Management

// Check if scanner is ready
if (UltraQrScanner.isPrepared) {
  // Ready to scan
} else {
  // Need to prepare first
  await UltraQrScanner.prepareScanner();
}

// Manually stop scanner
await UltraQrScanner.stopScanner();

🔒 Permissions Setup

Android

Add to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />

Add to android/app/build.gradle:

dependencies {
    implementation 'androidx.camera:camera-camera2:1.2.3'
    implementation 'androidx.camera:camera-lifecycle:1.2.3'
    implementation 'androidx.camera:camera-view:1.2.3'
    implementation 'com.google.mlkit:barcode-scanning:17.2.0'
}

iOS

Add to ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera access is required to scan QR codes</string>

Ensure minimum iOS version 11.0+ in ios/Podfile:

platform :ios, '11.0'

📱 Platform Support

PlatformCamera APIML FrameworkPreviewMin VersionStatus
AndroidCameraX + PreviewViewMLKit Barcode✅ NativeAPI 21 (Android 5.0)✅ Fully Supported
iOSAVCapture + PreviewLayerVision Framework✅ NativeiOS 11.0+✅ Fully Supported

🎯 Complete Example App

import 'package:flutter/material.dart';
import 'package:ultra_qr_scanner/ultra_qr_scanner.dart';
import 'package:ultra_qr_scanner/ultra_qr_scanner_widget.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Ultra QR Scanner Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String? lastQRCode;
  bool useAutoMode = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Ultra QR Scanner'),
        centerTitle: true,
        actions: [
          IconButton(
            icon: Icon(useAutoMode ? Icons.auto_awesome : Icons.touch_app),
            onPressed: () {
              setState(() {
                useAutoMode = !useAutoMode;
              });
            },
            tooltip: useAutoMode ? 'Switch to Manual' : 'Switch to Auto',
          ),
        ],
      ),
      body: Center(
        child: Padding(
          padding: EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // Mode indicator
              Container(
                padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                decoration: BoxDecoration(
                  color: useAutoMode ? Colors.green.shade100 : Colors.blue.shade100,
                  borderRadius: BorderRadius.circular(20),
                  border: Border.all(
                    color: useAutoMode ? Colors.green : Colors.blue,
                    width: 2,
                  ),
                ),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Icon(
                      useAutoMode ? Icons.flash_auto : Icons.touch_app,
                      color: useAutoMode ? Colors.green.shade700 : Colors.blue.shade700,
                      size: 20,
                    ),
                    SizedBox(width: 8),
                    Text(
                      useAutoMode ? '🚀 AUTO MODE' : '👆 MANUAL MODE',
                      style: TextStyle(
                        fontSize: 14,
                        fontWeight: FontWeight.bold,
                        color: useAutoMode ? Colors.green.shade700 : Colors.blue.shade700,
                      ),
                    ),
                  ],
                ),
              ),
              
              SizedBox(height: 10),
              
              Text(
                useAutoMode 
                    ? 'Scanning starts automatically when camera opens'
                    : 'Tap the Start Scan button to begin scanning',
                style: TextStyle(fontSize: 16),
                textAlign: TextAlign.center,
              ),
              
              SizedBox(height: 20),
              
              // Scanner Widget
              Container(
                width: 300,
                height: 300,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.3),
                      blurRadius: 10,
                      offset: Offset(0, 4),
                    ),
                  ],
                ),
                child: UltraQrScannerWidget(
                  onCodeDetected:(code , type) {
                    setState(() {
                      lastQRCode = code;
                    });
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('Scanned: $code'),
                        backgroundColor: Colors.green,
                        behavior: SnackBarBehavior.floating,
                        duration: Duration(seconds: 2),
                      ),
                    );
                  },
                  showFlashToggle: true,
                  autoStop: true,
                  // Dynamic mode switching
                  autoStart: useAutoMode,
                  showStartStopButton: !useAutoMode,
                ),
              ),
              
              SizedBox(height: 20),
              
              // Results Display
              Container(
                padding: EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.grey.shade100,
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.grey.shade300),
                ),
                child: Column(
                  children: [
                    Text(
                      'Last Scanned Code:',
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                        color: Colors.grey.shade700,
                      ),
                    ),
                    SizedBox(height: 8),
                    Text(
                      lastQRCode ?? 'No QR code scanned yet',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.w500,
                      ),
                      textAlign: TextAlign.center,
                    ),
                  ],
                ),
              ),
              
              SizedBox(height: 20),
              
              // Mode comparison
              Container(
                padding: EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.blue.shade50,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '💡 Scanning Modes:',
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                    ),
                    SizedBox(height: 8),
                    Text('🚀 Auto Mode: Instant scanning, no buttons needed'),
                    Text('👆 Manual Mode: User-controlled start/stop'),
                    Text('⚡ Both modes: Auto-stop after detection'),
                    Text('🔦 Flash control available in both modes'),
                    Text('📷 Camera switching available in both modes'),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

🔍 Error Handling

try {
  await UltraQrScanner.prepareScanner();
  final result = await UltraQrScanner.scanOnce();
  // Handle success
} on UltraQrScannerException catch (e) {
  // Handle scanner-specific errors
  switch (e.code) {
    case 'PERMISSION_DENIED':
      // Show permission dialog
      break;
    case 'NO_CAMERA':
      // Handle no camera available
      break;
    case 'NOT_PREPARED':
      // Scanner needs initialization
      break;
    default:
      print('Scanner error: ${e.message}');
  }
} catch (e) {
  // Handle other errors
  print('General error: $e');
}

🚀 Performance Tips

1. Choose the Right Mode for Your Use Case

// ✅ GOOD: Auto-mode for quick actions (payments, URLs)
UltraQrScannerWidget(
  autoStart: true,
  showStartStopButton: false,
  autoStop: true,
)

// ✅ GOOD: Manual mode for careful scanning (documents, batch)
UltraQrScannerWidget(
  autoStart: false, 
  showStartStopButton: true,
  autoStop: false,
)
// ✅ GOOD: Initialize during app startup for faster access
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  if (await UltraQrScanner.requestPermissions()) {
    await UltraQrScanner.prepareScanner();
  }
  runApp(MyApp());
}

3. Use Widget for Better Performance

// ✅ RECOMMENDED: Use UltraQrScannerWidget
UltraQrScannerWidget(
  onQrDetected: (code) => handleCode(code),
  autoStart: true,
)

// ❌ ALTERNATIVE: Programmatic scanning (requires more setup)
final result = await UltraQrScanner.scanOnce();

🐛 Troubleshooting

Common Issues

1. Camera Permission Denied

  • Ensure permissions are added to AndroidManifest.xml (Android) and Info.plist (iOS)
  • Request permissions before using scanner
  • Test on physical device (camera not available in simulators)

2. Black Screen in Auto Mode

  • Updated in v2.1.0 with better initialization timing
  • Ensure proper widget constraints (width/height)
  • Check console for platform view creation logs

3. QR Codes Not Detected

  • Ensure good lighting conditions
  • Check if QR code is clearly visible and not damaged
  • Verify QR code format is supported
  • Try manual mode if auto-mode has issues

4. Controls Not Responding

  • Verify widget configuration parameters
  • Check if scanner is properly initialized
  • Ensure proper error handling in callbacks

Platform-Specific Issues

Android:

  • Minimum SDK version 21+ required
  • Add camera dependencies to build.gradle
  • Test on multiple device orientations

iOS:

  • Minimum iOS 11.0+ required
  • Test on physical device only (simulator has no camera)
  • Ensure proper Info.plist configuration

📈 What's New

[3.0.3] - Latest

  • 🤖 Auto-start scanning mode - Begin scanning immediately when widget appears
  • 🤖 Support Google 16KB Page size - Support Google 16KB Page size
  • 👆 Enhanced manual controls - Better user-controlled scanning experience
  • 🎛️ Flexible UI options - Show/hide start/stop button independently
  • ⏱️ Improved initialization - Better timing for auto-start mode
  • 🔧 Better error handling - More robust initialization and state management
  • 📱 Enhanced examples - Complete auto/manual mode demonstrations

[2.0.0] - Previous Major Release

  • 🎉 Live camera preview with native platform views
  • 📸 Camera switching between front/back cameras
  • 🔦 Flash/torch control for better scanning in low light
  • 🎨 Customizable overlays and scanning frames
  • 🧹 Improved memory management and lifecycle handling
  • 🚀 Better performance with native camera integration

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

git clone https://github.com/shariaralphabyte/ultra_qr_scanner.git
cd ultra_qr_scanner
flutter pub get
cd example
flutter run

Running Tests

flutter test                    # Unit tests
flutter drive --target=test_driver/app.dart  # Integration tests

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

📞 Support


Made with ❤️ for the Flutter community
If this package helped you, please consider giving it a ⭐ on GitHub and a 👍 on pub.dev