zp_qr_scanner

pub package License: MIT

A high-performance Flutter QR scanner plugin powered by native camera textures on iOS (AVFoundation) and Android (CameraX + bundled ML Kit), streamed into Flutter via TextureRegistry.

✨ Features

  • 📱 iOS & Android: AVFoundation on iOS, CameraX + bundled ML Kit on Android (no Google Play Services required).
  • ⚡ High Performance: Native camera texture streaming directly into Flutter via Texture(textureId: ...), avoiding expensive platform view and image conversion overhead.
  • 🎯 Smooth Real-time Tracking: Automatically detects and tracks QR corners with smooth bounding box and scanning animations. The preview freezes on the exact frame the QR was detected in, so the corners always line up.
  • 🔦 Torch & Focus Controls: Built-in support for toggling flashlight and manual tap-to-focus.
  • 🔍 Zoom: Pinch-to-zoom in QrScannerView, or set a zoom level such as 1.0, 2.0, 3.0 from code.
  • 📳 Haptic Feedback: Built-in haptic feedback on successful scans.
  • 📱 Low Memory & Battery Efficient: Clean lifecycle management with automatic resume and pause.

🚀 Getting Started

1. Add dependency

Add zp_qr_scanner to your pubspec.yaml:

dependencies:
  zp_qr_scanner: ^1.1.3

Or install it via CLI:

flutter pub add zp_qr_scanner

2. Platform Setup

iOS Setup

Add the camera permission description to your ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>We need access to your camera to scan QR codes and barcodes.</string>

Note: Requires iOS 15.5 or higher.

Android Setup

No extra setup is needed: the plugin declares the CAMERA permission and requests it at runtime when start() is called.

  • minSdkVersion 21 or higher.
  • Your activity must be a FlutterActivity or FlutterFragmentActivity (the default for Flutter apps).
  • The QR model is bundled (ML Kit), so scanning works without Google Play Services; it adds roughly 2–3 MB per ABI to the APK.

App size impact

Measured with ./.build/analyze-size.sh (Flutter 3.29.0, release build, empty app vs. app using QrScannerView):

Platform Added size
Android arm64-v8a ~3.9 MB
Android armeabi-v7a ~3.6 MB
iOS arm64 ~0.2 MB

On Android most of it is the bundled ML Kit barcode model; iOS uses the system AVFoundation framework.


📖 Usage

Basic Scanner View

Use QrScannerView to display a full-featured scanner screen:

import 'package:flutter/material.dart';
import 'package:zp_qr_scanner/zp_qr_scanner.dart';

class ScanPage extends StatelessWidget {
  const ScanPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: QrScannerView(
        onScanned: (String value) async {
          // Handle scan result
          await showDialog(
            context: context,
            builder: (ctx) => AlertDialog(
              title: const Text('Scanned Result'),
              content: Text(value),
              actions: [
                TextButton(
                  onPressed: () => Navigator.of(ctx).pop(),
                  child: const Text('OK'),
                ),
              ],
            ),
          );
          // Once the Future finishes, scanner resumes automatically!
        },
      ),
    );
  }
}

Scan frame position

The idle scan frame is centered by default. Use scanBoxAlignment to move it and scanBoxOffset to fine-tune it in logical pixels:

QrScannerView(
  onScanned: _handleScanned,
  scanBoxAlignment: const Alignment(0, -0.3), // a bit above center
  scanBoxOffset: const Offset(0, 16),         // then 16px down
)

The whole camera frame is still scanned, so a QR code outside the frame is detected too.

Zoom

Pinch with two fingers to zoom in and out (set enablePinchZoom: false to disable it). To offer fixed zoom levels, call ZpQrScanner.setZoom and listen to ZpQrScanner.zoom:

// e.g. from a "2x" button
await ZpQrScanner.setZoom(2.0);

ValueListenableBuilder<double>(
  valueListenable: ZpQrScanner.zoom,
  builder: (_, zoom, __) => Text('${zoom.toStringAsFixed(1)}x'),
)

1.0 is the main (wide) lens. The level is clamped to ZpQrScanner.minZoom..ZpQrScanner.maxZoom (available after the camera starts): minZoom is below 1.0 on devices with an ultra-wide lens, and maxZoom is capped at 10.0. On iOS devices with a single wide lens the camera may start slightly zoomed in (up to 2.0) to keep small QR codes in focus.


Low-level API (ZpQrScanner)

If you want custom UI or manual camera control:

// Start camera session and get texture metadata
final QrTexture texture = await ZpQrScanner.start();
print('Texture ID: ${texture.id}, Size: ${texture.size}');

// Listen to scanned results stream
final subscription = ZpQrScanner.results.listen((QrScanResult result) {
  print('Scanned value: ${result.value}');
  print('Corners: ${result.corners}');
});

// Toggle torch / flashlight
await ZpQrScanner.setTorch(true);

// Focus at a specific point (normalized 0.0 - 1.0)
await ZpQrScanner.focus(const Offset(0.5, 0.5));

// Zoom (clamped to ZpQrScanner.minZoom..ZpQrScanner.maxZoom)
final double applied = await ZpQrScanner.setZoom(2.0);

// Pause and resume
await ZpQrScanner.stop();
await ZpQrScanner.resume();

🛠️ Example

Check out the complete sample project in the example/ directory.


📄 License

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