Camera Scanner Kit

A production-grade, highly optimized Flutter UI toolkit for barcode and QR scanning that solves camera lifecycle bugs and turns raw streams into fully-wired retail and warehouse workflows.

pub package License: MIT


Why Camera Scanner Kit?

While packages like mobile_scanner provide the raw camera stream, integrating them into a real-world app is notoriously error-prone. Developers constantly fight with native camera-lock hangs, "ghost scans" during exit animations, deactivated-widget context crashes, and lack of visual overlays.

camera_scanner_kit wraps the raw scanner in an enterprise-ready shell featuring:

  • 9-in-1 Routing Matrix: Instantly launch single-scan, batch-accumulate, or streaming modes with customized barcode, QR, or manual overlays.
  • Hardware-Safe Tripwires: Failsafe hooks that guarantee the camera sensor is fully detached and released before screen transitions begin, ending "deactivated widget" crashes forever.
  • Built-in POS Mode: A complete Point of Sale scanning interface featuring live quantity increment/decrement controls, ghost success pulses, and a reactive checkout cart summary.
  • Low-Latency Native Feedback: Direct integration with native haptic and audio APIs for ultra-low latency scan confirmation beeps.
  • Collapsible Inline View: An embeddable BarcodeScannerView that slides open/shut like a window blind and automatically sleeps during periods of inactivity.
  • Theme Isolation: Hardened primitives and scoped Material 3 modals ensure the scanner UI remains visually consistent, rendering identically whether the host application uses Material 3 or legacy Material 2 configurations.

Installation

Add camera_scanner_kit to your pubspec.yaml:

dependencies:
  camera_scanner_kit: ^1.2.0

Platform Setup

Android

Add the camera permission to your AndroidManifest.xml (usually under android/app/src/main/AndroidManifest.xml):

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

iOS

Add the camera usage description to your Info.plist (usually under ios/Runner/Info.plist):

<key>NSCameraUsageDescription</key>
<string>This app requires camera access to scan barcodes.</string>

Sound & Haptics

Scan feedback is powered by native_haptics_and_audio. Every scanner — ScannerScreen, BarcodeScannerView and PosBarcodeScannerScreen — initializes the shared audio engine on mount and preloads its beeps, so the first scan of a session has no decode latency. Set enableSoundAndVibration: false to skip this entirely: no engine is started and no audio is loaded.

Configuring the audio engine

The audio engine is a process-wide singleton, and its configuration is honored from the first successful initialize() call only. This package deliberately calls it with defaults so it never silently clamps your app's settings.

If you want non-default behavior, call initialize() yourself in main() before any scanner mounts:

import 'package:native_haptics_and_audio/native_haptics_and_audio.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await NativeHapticsAndAudioRepository.instance.initialize(
    // On iOS, beeps are silenced by the hardware ringer switch by default.
    // POS apps usually want them audible regardless:
    respectSilentSwitch: false,
  );

  runApp(const MyApp());
}

⚠️ iOS ringer switch. Without the call above, respectSilentSwitch defaults to true and the scanner beep will not play when the device's mute switch is on. This is the most common cause of "the scanner stopped beeping" reports on iOS.


Getting Started

1. Single Scan Mode

Open the camera, scan exactly one item, and automatically pop the screen to return the value.

import 'package:camera_scanner_kit/camera_scanner_kit.dart';

Future<void> startSingleScan(BuildContext context) async {
  // scanBarcode is optimized with a wide horizontal 1D cutout
  final String? barcode = await scanBarcode(context);
  
  if (barcode != null) {
    print('Scanned barcode: $barcode');
  }
}

2. POS Mode (With Quantity Controls)

Launch a full retail checkout scanner with quantity adjustment controls (+/-) and a live cart preview sheet.

import 'package:camera_scanner_kit/camera_scanner_kit.dart';

void openCheckout(BuildContext context) {
  showPosBarcodeScanner(
    context,
    onScan: (barcode, quantity) {
      print('Adding $quantity of $barcode to checkout cart');
    },
  );
}

3. Inline Mode (Embeddable Widget)

Embed a scanning window directly inside your existing UI (e.g. form fields or lists). Includes a smooth expand/collapse transition.

import 'package:camera_scanner_kit/camera_scanner_kit.dart';

class MyInlineForm extends StatefulWidget {
  const MyInlineForm({super.key});

  @override
  State<MyInlineForm> createState() => _MyInlineFormState();
}

class _MyInlineFormState extends State<MyInlineForm> {
  final _controller = BarcodeScannerController();

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        BarcodeScannerView(
          controller: _controller,
          onBarcodeScanned: (barcode) {
            print('Inline scan: $barcode');
          },
        ),
        ElevatedButton(
          onPressed: _controller.toggle,
          child: ListenableBuilder(
            listenable: _controller,
            builder: (context, _) {
              return Text(_controller.isCameraActive ? 'Close Scanner' : 'Open Scanner');
            },
          ),
        ),
      ],
    );
  }
}

💡 Programmatic Control (v1.0.2+): In addition to toggle(), you can call _controller.start() and _controller.stop() for explicit, idempotent control. Both are safe to call repeatedly — calling start() on an already-active camera (or stop() on an already-stopped one) is a no-op.

Advanced Routing (GoRouter & Nested Navigators): When using advanced routing packages like GoRouter, or when embedding the scanner inside a tab-based layout (like IndexedStack or BottomNavigationBar), you must be careful not to leave the camera hardware running when the user navigates away from the active tab. Leaving the camera active in the background will drain the user's battery and can cause native hardware crashes if another screen tries to claim the camera sensor. To see a complete, production-ready example of how to orchestrate the BarcodeScannerView with GoRouter and RouteAware mixins, check out our official Route Aware Sandbox on GitHub.


API Reference

Facade Functions (functions.dart)

Function Mode Overlay Shape Return Type
scanBarcode() Single 1D Horizontal Future<String?>
scanQrCode() Single 1:1 Square Future<String?>
scanCustom() Single Custom Rect Future<String?>
scanBarcodeBatch() Batch 1D Horizontal Future<List<String>?>
scanQrCodeBatch() Batch 1:1 Square Future<List<String>?>
scanCustomBatch() Batch Custom Rect Future<List<String>?>
scanBarcodeStream() Stream 1D Horizontal Future<void> (fires onCameraScan)
scanQrCodeStream() Stream 1:1 Square Future<void> (fires onCameraScan)
scanCustomStream() Stream Custom Rect Future<void> (fires onCameraScan)

License

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

Libraries

camera_scanner_kit
A production-ready Flutter UI toolkit for barcode and QR scanning.