smart_qr_scanner 1.6.0 copy "smart_qr_scanner: ^1.6.0" to clipboard
smart_qr_scanner: ^1.6.0 copied to clipboard

A production-ready Flutter package for real-time QR code and barcode scanning using Google ML Kit with beautiful modern UI, animations, and advanced features.

smart_qr_scanner #

Banner

pub version License: MIT Platform

A production-ready Flutter package for real-time QR code and barcode scanning powered by Google ML Kit. Drop in one widget, get a full-featured scanner with a modern animated UI, built-in QR generator, gallery scan, persistent history, favorites, CSV export, URL handling, haptic feedback, and a clean developer API.


Features #

Scanning

  • Real-time scanning — high-FPS camera stream with ML Kit processing
  • Gallery scan — pick any image from the device gallery and extract barcodes
  • 13 barcode formats — QR Code, Aztec, Codabar, Code 39/93/128, Data Matrix, EAN-8/13, ITF, PDF417, UPC-A/E
  • Pinch-to-zoom — smooth gesture-based zoom, no slider UI clutter
  • Smooth camera switch — front/back toggle with fade-in, no preview crash
  • Instant camera open — pre-warm the controller before navigation so the preview is ready on landing

QR Code Generator

  • Built-in QR generatorQrGeneratorWidget with no external rendering dependency
  • 6 input types — URL, plain text, WiFi credentials, email, phone number, vCard contact
  • pretty_qr_code renderingPrettyQrSquaresSymbol with standard 4-module quiet zone ensures generated codes are reliably scannable by all QR readers
  • Accent-coloured finder patterns — eye regions use your accent color; data modules stay black
  • Save to gallery — captures QR at 3× pixel ratio and saves via the gal package
  • Copy data — one-tap clipboard copy of the encoded string

UI & Animations

  • Modern animated loader — glowing frame, sweep line, pulsing icon, animated dots
  • Animated scan line — neon sweep with glow effect
  • Pixel burst success animation — Paytm-style white blocks burst radially from the scan center; ScanSuccessStyle.pixelBurst
  • Holographic QR overlay — scanned code materialises as a floating hologram with Matrix4 3D tilt, float, entrance succession, and a cyan sweep line; rendered behind the pixel burst
  • Scan UI auto-hide — corner brackets, scan line, and hint text disappear the instant a code is detected during pixel burst mode and restore cleanly after
  • Camera freeze on scan — camera pauses during the success animation and resumes on completion for a polished transition
  • Dark scrim on scan success — semi-transparent overlay ensures white hologram modules are legible against any camera background
  • Elastic check-mark ripple — default success animation (ripple rings + animated check-mark)
  • Glassmorphism controls — flash, flip, gallery buttons with blur backdrop
  • 3 built-in themes — Neon Cyan, Light, Minimal White + fully custom
  • Theme picker — bottom sheet with animated selection rows
  • Granular UI control — show/hide flash, gallery, flip, and menu buttons individually
  • GlobalKey API — expose showThemePicker() from any external button (e.g. AppBar)
  • DuplicateToastOverlay — animated in-app toast for duplicate scan feedback

Data & Persistence

  • Persistent history — scan history saved to SharedPreferences via StorageService; survives app restarts
  • Favorites — bookmark any scan result; persisted across sessions via FavoritesService
  • CSV export — export full scan history as a .csv file and share it via HistoryExporter
  • JSON serializationSmartScanResult.toJson() / fromJson() for custom storage
  • Smart URL handlerSmartUrlHandler launches URLs, email clients, phone dialer, SMS, and geo coordinates

Feedback

  • Haptic patterns per barcode type — distinct vibration patterns for URL, email, phone, WiFi, etc.
  • Duplicate prevention — configurable time-window deduplication

Developer API

  • Stream & Future APIsonScan callback, scanEvents stream, and scanOnce() Future
  • Lifecycle aware — auto-pause on background, resume on foreground
  • Timeout handling — configurable scan deadline with callback
  • Single & continuous modes — stop after first hit or keep scanning
  • Permission handling — built-in request flow with settings deep-link
  • Frame throttling — configurable skip count to save CPU/battery

Supported Formats #

Format Constant
QR Code BarcodeFormat.qrCode
Aztec BarcodeFormat.aztec
Codabar BarcodeFormat.codabar
Code 39 BarcodeFormat.code39
Code 93 BarcodeFormat.code93
Code 128 BarcodeFormat.code128
Data Matrix BarcodeFormat.dataMatrix
EAN-8 BarcodeFormat.ean8
EAN-13 BarcodeFormat.ean13
ITF BarcodeFormat.itf
PDF417 BarcodeFormat.pdf417
UPC-A BarcodeFormat.upca
UPC-E BarcodeFormat.upce

Installation #

dependencies:
  smart_qr_scanner: ^1.6.0
flutter pub get

Platform Setup #

Android #

android/app/build.gradle

android {
    defaultConfig {
        minSdkVersion 21   // ML Kit requires 21+
    }
}

android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <!-- Gallery save: needed on Android 9 and below only -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="29"
        tools:replace="android:maxSdkVersion" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

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

    <application ...>
        <!-- inside <application> -->
        <meta-data
            android:name="com.google.mlkit.vision.DEPENDENCIES"
            android:value="barcode_ui" />
    </application>
</manifest>

iOS #

ios/Runner/Info.plist

<!-- Camera (required) -->
<key>NSCameraUsageDescription</key>
<string>Camera access is required to scan QR codes and barcodes.</string>

<!-- Gallery scan (required) -->
<key>NSPhotoLibraryUsageDescription</key>
<string>Required to pick images from your gallery for QR code scanning.</string>

<!-- Save generated QR to gallery -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Required to save generated QR codes to your photo library.</string>

<!-- Required by the camera plugin -->
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is not used but required by the camera plugin.</string>

ios/Podfile

platform :ios, '16.0'   # google_mlkit_barcode_scanning 0.13+ requires iOS 16+

Quick Start #

Drop-in widget (simplest) #

import 'package:smart_qr_scanner/smart_qr_scanner.dart';

class ScanPage extends StatefulWidget {
  const ScanPage({super.key});
  @override
  State<ScanPage> createState() => _ScanPageState();
}

class _ScanPageState extends State<ScanPage> {
  late final SmartQrScannerController _controller;

  @override
  void initState() {
    super.initState();
    _controller = SmartQrScannerController(
      config: const ScannerConfig(
        scanMode: ScanMode.single,
        enableVibration: true,
      ),
    );
    _controller.onScan = (result) {
      print('Scanned: ${result.rawValue} (${result.formatName})');
    };
    _controller.initialize();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => SmartScannerWidget(
        controller: _controller,
        theme: ScannerTheme.light,
        hintText: 'Point at a QR code or barcode',
      );
}

Pre-initialize the controller before pushing the scanner route so the camera warms up during the navigation transition and is ready the moment the screen lands:

void openScanner(BuildContext context) {
  final controller = SmartQrScannerController(
    config: const ScannerConfig(scanMode: ScanMode.single, enableVibration: true),
  );
  controller.initialize(); // fire immediately — overlaps with route animation

  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (_) => ScannerPage(controller: controller),
    ),
  );
}

// In ScannerPage.initState():
// _controller = widget.controller;          // use pre-warmed controller
// _controller.onScan    = _onScan;          // wire callbacks
// _controller.onTimeout = _onTimeout;
// _controller.onError   = _onError;
// // do NOT call initialize() again

Future-based single scan #

final result = await _controller.scanOnce();
print(result.rawValue);
// Opens the device photo picker, scans the selected image,
// and fires onScan (or onError if no code is found).
await _controller.scanFromGallery();

QR Generator #

Drop QrGeneratorWidget anywhere to add a full-featured QR code creator:

QrGeneratorWidget(
  accentColor: const Color(0xFF00BCD4), // finder pattern & button color
  onGenerated: (data) => print('Generated: $data'),
)

Supported input types (selectable via chips inside the widget):

Type Encoded format
URL Raw URL (auto-prefixes https://)
Text Plain string
WiFi WIFI:T:WPA;S:ssid;P:pass;;
Email mailto:address
Phone tel:number
Contact vCard 3.0 (BEGIN:VCARD…END:VCARD)

The rendered QR code uses PrettyQrSquaresSymbol with PrettyQrQuietZone.standard — standard sharp square modules with the required 4-module quiet zone — ensuring reliable scanning by all standard QR readers. Users can copy the data or save the QR image to the gallery.

Use QrView standalone #

// Renders a scannable QR code for any string
QrView(
  data: 'https://flutter.dev',
  size: 250,
  eyeColor: Colors.teal,   // finder pattern accent
  dataColor: Colors.black, // data module color
  background: Colors.white,
)

URL Handler #

SmartUrlHandler inspects a SmartScanResult and launches the appropriate system handler:

// Check if the result has a launchable action
if (SmartUrlHandler.canHandle(result)) {
  // Returns a label like 'Open URL', 'Send Email', 'Call', 'Send SMS', 'Open Map'
  final label = SmartUrlHandler.actionLabel(result);
  await SmartUrlHandler.launch(result);
}
Barcode type Action
URL Opens in system browser
Email Opens email composer
Phone Opens phone dialer
SMS Opens SMS composer
Geo coordinates Opens maps app
Raw http/https Opens in system browser

Favorites #

Persist favorite scan results across sessions:

final favService = FavoritesService();

// Check
final isFav = await favService.isFavorite(result.rawValue);

// Toggle (adds if not favorite, removes if favorite)
await favService.toggle(result.rawValue);

// Load all saved favorites
final favorites = await favService.loadAll();

FavoriteButton widget #

Drop-in animated bookmark button with a scale animation on toggle:

// In a list tile or AppBar action:
FavoriteButton.forValue(
  result.rawValue,
  activeColor: Colors.amber,
)

Scan History #

History is automatically persisted to SharedPreferences and restored on the next app launch.

// Access via the controller
final history = controller.history; // List<SmartScanResult>

// Clear
controller.clearHistory();

Export as CSV #

await HistoryExporter.exportCsv(controller.history);
// Generates a .csv file saved to the temporary directory; returns true on success

CSV columns: Timestamp, Format, Type, Raw Value, Display Value, Confidence


JSON Serialization #

SmartScanResult supports full round-trip JSON serialization for custom storage or API integration:

// Serialize
final json = result.toJson(); // Map<String, dynamic>

// Deserialize
final restored = SmartScanResult.fromJson(json);

All fields are preserved, including optional Rect, List<Offset>, and enum values.


Duplicate Toast #

Show an in-app toast when a duplicate barcode is scanned:

// Wrap your scanner screen in DuplicateToastOverlay:
DuplicateToastOverlay(
  child: SmartScannerWidget(controller: _controller, ...),
)

// Trigger from anywhere in the subtree:
DuplicateToastOverlay.show(context, message: 'Already scanned!');

The toast fades in and auto-dismisses after 2 seconds.


Configuration #

SmartQrScannerController(
  config: ScannerConfig(
    // Scan behaviour
    scanMode: ScanMode.single,           // or ScanMode.continuous
    cameraFacing: CameraFacing.back,     // or CameraFacing.front

    // Barcode formats (empty list = all 13 formats)
    formats: [BarcodeFormat.qrCode, BarcodeFormat.ean13],

    // Scan area (fraction of screen width/height)
    scanAreaWidthFactor: 0.75,
    scanAreaHeightFactor: 0.35,

    // Camera
    enableFlash: false,
    enableAutoFocus: true,

    // Feedback
    enableVibration: true,

    // Duplicate prevention
    preventDuplicates: true,
    duplicatePreventionWindow: Duration(seconds: 2),

    // Performance
    framesToSkip: 0,           // 0 = every frame; 2 = every 3rd frame

    // Timeout (null = no timeout)
    scanTimeout: Duration(seconds: 30),

    // History
    enableScanHistory: true,
    maxHistoryItems: 50,

    // Analytics
    onAnalyticsEvent: (value, format) => myAnalytics.track(value, format),
  ),
);

Controller API #

// Lifecycle
await controller.initialize();
await controller.dispose();

// Playback
controller.pause();
controller.resume();

// Camera
await controller.toggleFlash();
await controller.switchCamera();
await controller.setZoom(1.5);    // pinch-to-zoom also handled by widget

// Gallery
await controller.scanFromGallery();

// State
controller.isInitialized    // bool
controller.isPaused         // bool
controller.isFlashOn        // bool
controller.isSwitching      // bool — true while swapping cameras
controller.permissionStatus // CameraPermissionStatus
controller.history          // List<SmartScanResult>
controller.errorMessage     // String?
controller.minZoom          // double
controller.maxZoom          // double
controller.currentZoom      // double

// Callbacks
controller.onScan    = (SmartScanResult result) { ... };
controller.onRawScan = (List<SmartScanResult> all) { ... };
controller.onTimeout = () { ... };
controller.onError   = (String message) { ... };

// Streams
controller.scanEvents;     // Stream<SmartScanResult> — deduplicated
controller.rawScanEvents;  // Stream<List<SmartScanResult>> — every ML Kit batch

// Future API
final result = await controller.scanOnce();

// History
controller.clearHistory();

Widget Parameters #

SmartScannerWidget(
  controller: controller,

  // Theme (see Themes section below)
  theme: ScannerTheme.light,

  // Hint text shown below the scan area
  hintText: 'Align code within the frame',

  // Show/hide entire bottom control bar
  showControls: true,

  // Show/hide hint text
  showHint: true,

  // Show/hide individual bottom buttons
  showFlash: true,    // flash torch toggle
  showGallery: true,  // gallery image picker
  showFlip: true,     // front/back camera switch

  // Show/hide the built-in 3-dots theme picker button.
  // Set to false when you provide your own button via GlobalKey (see below).
  showMenu: true,

  // Called when user picks a theme from the bottom sheet
  onThemeChanged: (ScannerTheme t) => setState(() => _theme = t),

  // Success animation style
  successStyle: ScanSuccessStyle.pixelBurst, // or ScanSuccessStyle.ripple

  // Widget rendered BEHIND the success animation (e.g. holographic QR overlay).
  // Receives the scan result; removed at the same time as the animation.
  successOverlayBuilder: (result) => HolographicQrOverlay(rawValue: result.rawValue),

  // Called after the success animation completes — use this for navigation
  // instead of controller.onScan so the animation is not cut short.
  onScanAnimationComplete: (result) => Navigator.pushReplacement(
    context,
    MaterialPageRoute(builder: (_) => ResultScreen(result: result)),
  ),

  // Optional logo widget centered inside the pixel burst
  successLogo: Image.asset('assets/logo.png', width: 90, height: 90),
  successLogoSize: 100,

  // Override the default loading / error screens
  loadingWidget: MyLoadingScreen(),
  errorWidget:   MyErrorScreen(),
);

Holographic QR Overlay (example app widget) #

HolographicQrOverlay is a ready-to-use widget that renders the scanned QR code as a floating hologram with a cinematic entrance:

successOverlayBuilder: (result) => HolographicQrOverlay(
  rawValue: result.rawValue,
  // onDismiss: null  →  SmartScannerWidget controls removal
  // onDismiss: () {} →  widget self-dismisses after autoDismissAfter
),
Property Default Description
rawValue required QR data to encode
onDismiss null Removal callback; null = parent controls lifecycle
autoDismissAfter 2400 ms Auto-dismiss delay when onDismiss is set

Entrance sequence (900 ms, TweenSequence):

Phase Scale Opacity Duration
Burst 0.05×1.18× 01 60 %
Overshoot 1.18×0.94× 1 20 %
Settle 0.94×1.0× 1 20 %

Continuous animations after entrance:

  • 3D tiltMatrix4 perspective, rotateX ±0.30 rad + rotateY ±0.22 rad, 2200 ms loop
  • Float — ±6 px vertical, 2800 ms sine
  • Cyan sweep line — isolated CustomPainter, repaints only the scan line (1600 ms repeat)

Themes #

Built-in presets #

ScannerTheme.neon     // Cyan glow, dark overlay (default)
ScannerTheme.light    // White corners, sky-blue scan line, soft overlay
ScannerTheme.minimal  // Pure white corners, minimal overlay

Custom theme #

ScannerTheme(
  overlayColor:              Color(0xAA000000),
  borderColor:               Color(0xFF00E5FF),
  borderRadius:              16.0,
  borderStrokeWidth:         3.5,
  cornerLength:              28.0,
  scanLineColor:             Color(0xFF00E5FF),
  scanLineHeight:            2.5,
  scanLineAnimationDuration: Duration(milliseconds: 1800),
  glassTintColor:            Color(0x22FFFFFF),
  glassBlurSigma:            12.0,
  buttonColor:               Color(0x44FFFFFF),
  buttonIconColor:           Colors.white,
  successColor:              Color(0xFF00E676),
  hintTextStyle:             TextStyle(color: Colors.white, fontSize: 14),
)

// Copy an existing preset and override only what you need:
ScannerTheme.neon.copyWith(borderColor: Colors.orange)

Theme picker in a custom AppBar #

final _scannerKey = GlobalKey<SmartScannerWidgetState>();

AppBar(
  actions: [
    IconButton(
      icon: const Icon(Icons.more_vert_rounded, color: Colors.white),
      onPressed: () => _scannerKey.currentState?.showThemePicker(),
    ),
  ],
)

SmartScannerWidget(
  key: _scannerKey,
  controller: _controller,
  showMenu: false,
  onThemeChanged: (t) => setState(() => _theme = t),
)

Scan Result #

SmartScanResult {
  String   rawValue;        // Raw barcode string
  String?  displayValue;    // Human-friendly value (formatted URL, phone, etc.)
  BarcodeFormat format;     // e.g. BarcodeFormat.qrCode
  BarcodeType   type;       // e.g. BarcodeType.url
  DateTime      timestamp;
  ScanResultType resultType; // success | duplicate | error | timeout
  Rect?          boundingBox;
  List<Offset>?  cornerPoints;
  double?        confidence;      // 0.0 – 1.0
  Map<String, dynamic> metadata;  // structured data: email, phone, url, wifi …

  // Helpers
  String formatName; // 'QR Code', 'EAN-13', …
  String typeName;   // 'URL', 'Email', …
  bool   isSuccess;

  // Serialization
  Map<String, dynamic> toJson();
  factory SmartScanResult.fromJson(Map<String, dynamic> json);
}

Performance Tips #

Goal Config / Approach
Instant camera open Pre-create controller and call initialize() before Navigator.push
Reduce CPU on slow devices framesToSkip: 2
Faster cold start, lighter memory enableScanHistory: false
Only need QR codes formats: [BarcodeFormat.qrCode]
Prevent re-scan noise duplicatePreventionWindow: Duration(seconds: 3)
Stop after first scan scanMode: ScanMode.single
Smooth pixel burst saveLayer is not used — all blocks drawn directly via canvas.drawRect; single Paint object reused across all 260 blocks per frame
Smooth hologram Sweep line isolated in its own AnimatedBuilder; card tree never rebuilt by tilt/float ticks; RepaintBoundary wraps both burst and hologram

Troubleshooting #

Scanner shows "iOS Simulator" screen instead of camera
iOS Simulator has no physical camera hardware. availableCameras() returns an empty list, so the package shows a friendly informational screen instead of a red error. Run the app on a real iPhone or iPad to use live scanning. Gallery scan (scanFromGallery()) and QR generation still work in the Simulator.

Camera permission denied on iOS
Add NSCameraUsageDescription to ios/Runner/Info.plist.

Gallery picker crashes on iOS
Add NSPhotoLibraryUsageDescription to ios/Runner/Info.plist.

Cannot save QR to gallery on iOS
Add NSPhotoLibraryAddUsageDescription to ios/Runner/Info.plist.

ML Kit models not downloading on Android
Add <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="barcode_ui"/> inside <application> in AndroidManifest.xml.

Manifest merger conflict for WRITE_EXTERNAL_STORAGE
Add xmlns:tools to the root <manifest> tag and tools:replace="android:maxSdkVersion" to the <uses-permission> element. See the Platform Setup section above.

Black camera preview on open
Pre-warm the controller before Navigator.push (see Instant camera open above). Always call controller.dispose() inside your widget's dispose().

Generated QR code not scannable
QrView uses PrettyQrSquaresSymbol with PrettyQrQuietZone.standard — standard square modules and a 4-module quiet zone — which is reliably read by all QR apps. Ensure the widget is rendered against a white background so the quiet zone has sufficient contrast.

buildPreview() on disposed CameraController crash
Handled automatically via the isSwitching flag — the widget renders a black placeholder while the old controller is disposed.

Slow detection
Increase framesToSkip or restrict formats to only what you need.

No result from gallery image
The image must contain a clear, unobstructed barcode. Blurry, rotated, or very small codes may not be detected. onError is called with a descriptive message if nothing is found.


License #

MIT License — Copyright (c) 2026 Sanjay Sharma
2
likes
160
points
392
downloads

Documentation

API reference

Publisher

verified publishersanjaysharma.info

Weekly Downloads

A production-ready Flutter package for real-time QR code and barcode scanning using Google ML Kit with beautiful modern UI, animations, and advanced features.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

camera, flutter, gal, google_mlkit_barcode_scanning, image_picker, path_provider, permission_handler, pretty_qr_code, shared_preferences, url_launcher, vibration

More

Packages that depend on smart_qr_scanner