mobile_scanner
repository·develop·Indexed 22 days ago
https://github.com/juliansteenbakker/mobile_scannerA high-performance Flutter plugin for barcode and QR code scanning across Android, iOS, macOS, and Web. It provides real-time detection and fine-grained control over camera lenses and scanning parameters via the MobileScanner widget and MobileScannerController. Features include flashlight toggle, zoom control, support for multiple barcode formats, and configurable web detection backends (Native BarcodeDetector, zxing-wasm, and ZXing-js).
What's inside mobile_scanner
- mobile_scanner is a fast and lightweight Flutter plugin designed for scanning barcodes and QR codes using a device's camera. It provides real-time detection, support for multiple barcode formats, and customizable camera/scanner behavior. It is suitable for high-performance scanning applications across mobile and desktop platforms.
Platform support and feature availability
developmobile_scanner supports Android, iOS, macOS, and Web. Note that feature availability varies by platform:
Feature Android iOS macOS Web analyzeImage✔ ✔ ✔ ✘ returnImage✔ ✔ ✔ ✘ scanWindow✔ ✔ ✔ ✘ autoZoom✔ ✘ ✘ ✘ lensType✔ ✔ ✘ ✘ getSupportedLenses(facing:)✔ ✔ ✘ ✘ getBestCloseRangeScanningLens✔ (normal) ✔ (iOS 15+, else normal) ✔ (normal) ✘ (normal) Configure macOS camera permissions
developFor macOS support, you must grant camera permission in Xcode under Signing & Capabilities.Configure Android MLKit Barcode-scanning
developBy default,
mobile_scanneruses the bundled version of MLKit for Android, which is immediately available but increases app size by 3-10 MB.You can switch to the unbundled version to reduce app size by ~600KB. This version is downloaded via Google Play Services upon first use.
To use the unbundled version, add the following line to your
/android/gradle.propertiesfile:dev.steenbakker.mobile_scanner.useUnbundled=trueRun the mobile_scanner example project
developTo run the official demonstration app to see how the plugin works in practice, follow these steps in your terminal:
- Clone the repository.
- Navigate to the example directory.
- Fetch dependencies.
- Run the application.
git clone https://github.com/juliansteenbakker/mobile_scanner.git cd mobile_scanner/example/lib flutter pub get flutter runManage MobileScanner lifecycle with WidgetsBindingObserver
developTo prevent the scanner from running while the app is inactive, manually manage the lifecycle using
WidgetsBindingObserver.Steps:
- Initialize
MobileScannerControllerwithautoStart: false. - Mix in
WidgetsBindingObserverto yourStateclass. - In
didChangeAppLifecycleState:- When
resumed: Listen tocontroller.barcodesand callcontroller.start(). - When
inactive: Cancel the subscription and callcontroller.stop().
- When
- In
initState: Add the observer and start the scanner. - In
dispose: Remove the observer, cancel subscriptions, and callcontroller.dispose().
class MyState extends State<MyStatefulWidget> with WidgetsBindingObserver { final MobileScannerController controller = MobileScannerController(autoStart: false); StreamSubscription<Object?>? _subscription; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _subscription = controller.barcodes.listen(_handleBarcode); unawaited(controller.start()); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (!controller.value.hasCameraPermission) return; switch (state) { case AppLifecycleState.detached: case AppLifecycleState.hidden: case AppLifecycleState.paused: return; case AppLifecycleState.resumed: _subscription = controller.barcodes.listen(_handleBarcode); unawaited(controller.start()); break; case AppLifecycleState.inactive: unawaited(_subscription?.cancel()); _subscription = null; unawaited(controller.stop()); break; } } @override Future<void> dispose() async { WidgetsBinding.instance.removeObserver(this); unawaited(_subscription?.cancel()); _subscription = null; super.dispose(); await controller.dispose(); } }- Initialize
Configure iOS permissions
developTo use the camera and local gallery features on iOS, you must add the following keys to your
Info.plistfile (located at<project root>/ios/Runner/Info.plist):NSCameraUsageDescription: A description of why your app needs camera access.NSPhotoLibraryUsageDescription: A description of why your app needs access to the photo library (required if usingimage_pickerfor local gallery features).
<key>NSCameraUsageDescription</key> <string>This app needs camera access to scan QR codes</string> <key>NSPhotoLibraryUsageDescription</key> <string>This app needs photos access to get QR code from photo library</string>Install mobile_scanner via pub
developAdd
mobile_scannerto yourpubspec.yamldependencies and runflutter pub getto install.dependencies: mobile_scanner: ^<latest_version>flutter pub getCustomize iOS launch screen assets
developTo change the appearance of the launch screen in your iOS application, you can replace the existing image files in the
example/ios/Runner/Assets.xcassets/LaunchImage.imageset/directory with your own assets.Alternatively, you can manage these assets using Xcode:
- Open your Flutter project's iOS workspace using
open ios/Runner.xcworkspace. - In the Xcode Project Navigator, navigate to
Runner/Assets.xcassets. - Drag and drop your desired images into the asset catalog to replace the current launch images.
open ios/Runner.xcworkspace- Open your Flutter project's iOS workspace using
Configure the scanWindow for barcode scanning
developThe
scanWindowproperty allows you to define a specificRectarea within theMobileScannerwidget's layout. The scanner will only detect barcodes that intersect this rectangle.Important Notes:
- Web Support:
scanWindowis not supported on the web because the scanner does not expose barcode size information there. - Coordinate System: The rectangle is relative to the layout size of the
MobileScannerwidget in the widget tree, not the raw camera output size. Thefitproperty (e.g.,BoxFit.cover) affects how this window is mapped to the camera texture. - Performance: If updating the scan window causes performance issues, use
scanWindowUpdateThresholdto prevent frequent updates when layout constraints change slightly.
Example: Centered Scan Window
To create a scan window that is centered and occupies a specific portion of the widget's size:
LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final Size layoutSize = constraints.biggest; final double scanWindowWidth = layoutSize.width / 3; final double scanWindowHeight = layoutSize.height / 2; final Rect scanWindow = Rect.fromCenter( center: layoutSize.center(Offset.zero), width: scanWindowWidth, height: scanWindowHeight, ); return MobileScanner( scanWindow: scanWindow, // ... other properties ); }, );LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final Size layoutSize = constraints.biggest; final double scanWindowWidth = layoutSize.width / 3; final double scanWindowHeight = layoutSize.height / 2; final Rect scanWindow = Rect.fromCenter( center: layoutSize.center(Offset.zero), width: scanWindowWidth, height: scanWindowHeight, ); } );- Web Support:
Understand the MobileScannerState object
developThe
MobileScannerStateclass represents the current status of aMobileScannerController. It provides real-time information about the camera hardware, the scanner's lifecycle, and potential errors. You can use this state to build reactive UIs that respond to camera changes, zoom levels, or permission issues.Key properties include:
isInitialized: Indicates if the scanner has successfully initialized (note: this is distinct from camera permission).isRunning:trueif the camera is currently active.isStarting:trueif the scanner is in the process of starting; use this to prevent duplicate calls toMobileScannerController.start().hasCameraPermission: A helper getter that returnstrueif the scanner is initialized and nopermissionDeniederror is present.cameraDirection: The current facing direction (CameraFacing).torchState: The current state of the flashlight (TorchState).zoomScale: The current zoom level.error: Contains aMobileScannerExceptionif something went wrong.
Find the best lens for close-range scanning
developOn devices with multiple cameras, some lenses are better suited for close-up scanning. You can query for the best lens and then switch to it.
- Use
getBestCloseRangeScanningLens(facing: ...)to find the idealCameraLensType. - Use
getSupportedLenses(facing: ...)to verify the lens is actually available on the device. - Use
switchCamera(SelectCamera(...))to apply the change.
final bestLens = await controller.getBestCloseRangeScanningLens(facing: CameraFacing.back); final supported = await controller.getSupportedLenses(facing: CameraFacing.back); if (bestLens != null && supported.contains(bestLens)) { await controller.switchCamera( SelectCamera(facingDirection: CameraFacing.back, lensType: bestLens), ); }- Use