Showing
1 changed file
with
78 additions
and
172 deletions
| @@ -7,17 +7,15 @@ | @@ -7,17 +7,15 @@ | ||
| 7 | 7 | ||
| 8 | A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS. | 8 | A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS. |
| 9 | 9 | ||
| 10 | - | ||
| 11 | ## Features Supported | 10 | ## Features Supported |
| 12 | 11 | ||
| 13 | See the example app for detailed implementation information. | 12 | See the example app for detailed implementation information. |
| 14 | 13 | ||
| 15 | -| Features | Android | iOS | macOS | Web | | ||
| 16 | -|------------------------|--------------------|--------------------|-------|-----| | ||
| 17 | -| analyzeImage (Gallery) | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | | ||
| 18 | -| returnImage | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | | ||
| 19 | -| scanWindow | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | | ||
| 20 | -| barcodeOverlay | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | | 14 | +| Features | Android | iOS | macOS | Web | |
| 15 | +|------------------------|--------------------|--------------------|----------------------|-----| | ||
| 16 | +| analyzeImage (Gallery) | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | | ||
| 17 | +| returnImage | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | | ||
| 18 | +| scanWindow | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | | ||
| 21 | 19 | ||
| 22 | ## Platform Support | 20 | ## Platform Support |
| 23 | 21 | ||
| @@ -26,6 +24,7 @@ See the example app for detailed implementation information. | @@ -26,6 +24,7 @@ See the example app for detailed implementation information. | ||
| 26 | | ✔ | ✔ | ✔ | ✔ | :x: | :x: | | 24 | | ✔ | ✔ | ✔ | ✔ | :x: | :x: | |
| 27 | 25 | ||
| 28 | ## Platform specific setup | 26 | ## Platform specific setup |
| 27 | + | ||
| 29 | ### Android | 28 | ### Android |
| 30 | This package uses by default the **bundled version** of MLKit Barcode-scanning for Android. This version is immediately available to the device. But it will increase the size of the app by approximately 3 to 10 MB. | 29 | This package uses by default the **bundled version** of MLKit Barcode-scanning for Android. This version is immediately available to the device. But it will increase the size of the app by approximately 3 to 10 MB. |
| 31 | 30 | ||
| @@ -61,194 +60,101 @@ Ensure that you granted camera permission in XCode -> Signing & Capabilities: | @@ -61,194 +60,101 @@ Ensure that you granted camera permission in XCode -> Signing & Capabilities: | ||
| 61 | <img width="696" alt="Screenshot of XCode where Camera is checked" src="https://user-images.githubusercontent.com/24459435/193464115-d76f81d0-6355-4cb2-8bee-538e413a3ad0.png"> | 60 | <img width="696" alt="Screenshot of XCode where Camera is checked" src="https://user-images.githubusercontent.com/24459435/193464115-d76f81d0-6355-4cb2-8bee-538e413a3ad0.png"> |
| 62 | 61 | ||
| 63 | ## Web | 62 | ## Web |
| 64 | -This package uses ZXing on web to read barcodes so it needs to be included in `index.html` as script. | 63 | + |
| 64 | +Include the `ZXing` library in the `<head>` of your `index.html` as a script. | ||
| 65 | + | ||
| 65 | ```html | 66 | ```html |
| 66 | -<script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script> | 67 | +<head> |
| 68 | + <!-- other things in the tag --> | ||
| 69 | + | ||
| 70 | + <script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script> | ||
| 71 | +</head> | ||
| 67 | ``` | 72 | ``` |
| 68 | 73 | ||
| 69 | ## Usage | 74 | ## Usage |
| 70 | 75 | ||
| 71 | -Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller. | 76 | +Import the package with `package:mobile_scanner/mobile_scanner.dart`. |
| 72 | 77 | ||
| 73 | -If you don't provide a controller, you can't control functions like the torch(flash) or switching camera. | ||
| 74 | - | ||
| 75 | -If you don't set `detectionSpeed` to `DetectionSpeed.noDuplicates`, you can get multiple scans in a very short time, causing things like pop() to fire lots of times. | ||
| 76 | - | ||
| 77 | -Example without controller: | 78 | +Create a new `MobileScannerController` controller, using the required options. |
| 79 | +Provide a `StreamSubscription` for the barcode events. | ||
| 78 | 80 | ||
| 79 | ```dart | 81 | ```dart |
| 80 | -import 'package:mobile_scanner/mobile_scanner.dart'; | 82 | +final MobileScannerController controller = MobileScannerController( |
| 83 | + // required options for the scanner | ||
| 84 | +); | ||
| 81 | 85 | ||
| 82 | - @override | ||
| 83 | - Widget build(BuildContext context) { | ||
| 84 | - return Scaffold( | ||
| 85 | - appBar: AppBar(title: const Text('Mobile Scanner')), | ||
| 86 | - body: MobileScanner( | ||
| 87 | - // fit: BoxFit.contain, | ||
| 88 | - onDetect: (capture) { | ||
| 89 | - final List<Barcode> barcodes = capture.barcodes; | ||
| 90 | - final Uint8List? image = capture.image; | ||
| 91 | - for (final barcode in barcodes) { | ||
| 92 | - debugPrint('Barcode found! ${barcode.rawValue}'); | ||
| 93 | - } | ||
| 94 | - }, | ||
| 95 | - ), | ||
| 96 | - ); | ||
| 97 | - } | 86 | +StreamSubscription<Object?>? _subscription; |
| 98 | ``` | 87 | ``` |
| 99 | 88 | ||
| 100 | -Example with controller and initial values: | 89 | +Ensure that your `State` class mixes in `WidgetsBindingObserver`, to handle lifecyle changes: |
| 101 | 90 | ||
| 102 | ```dart | 91 | ```dart |
| 103 | -import 'package:mobile_scanner/mobile_scanner.dart'; | 92 | +class MyState extends State<MyStatefulWidget> with WidgetsBindingObserver { |
| 93 | + // ... | ||
| 104 | 94 | ||
| 105 | @override | 95 | @override |
| 106 | - Widget build(BuildContext context) { | ||
| 107 | - return Scaffold( | ||
| 108 | - appBar: AppBar(title: const Text('Mobile Scanner')), | ||
| 109 | - body: MobileScanner( | ||
| 110 | - // fit: BoxFit.contain, | ||
| 111 | - controller: MobileScannerController( | ||
| 112 | - detectionSpeed: DetectionSpeed.normal, | ||
| 113 | - facing: CameraFacing.front, | ||
| 114 | - torchEnabled: true, | ||
| 115 | - ), | ||
| 116 | - onDetect: (capture) { | ||
| 117 | - final List<Barcode> barcodes = capture.barcodes; | ||
| 118 | - final Uint8List? image = capture.image; | ||
| 119 | - for (final barcode in barcodes) { | ||
| 120 | - debugPrint('Barcode found! ${barcode.rawValue}'); | ||
| 121 | - } | ||
| 122 | - }, | ||
| 123 | - ), | ||
| 124 | - ); | 96 | + void didChangeAppLifecycleState(AppLifecycleState state) { |
| 97 | + super.didChangeAppLifecycleState(state); | ||
| 98 | + | ||
| 99 | + switch (state) { | ||
| 100 | + case AppLifecycleState.detached: | ||
| 101 | + case AppLifecycleState.hidden: | ||
| 102 | + case AppLifecycleState.paused: | ||
| 103 | + return; | ||
| 104 | + case AppLifecycleState.resumed: | ||
| 105 | + // Restart the scanner when the app is resumed. | ||
| 106 | + // Don't forget to resume listening to the barcode events. | ||
| 107 | + _subscription = controller.barcodes.listen(_handleBarcode); | ||
| 108 | + | ||
| 109 | + unawaited(controller.start()); | ||
| 110 | + case AppLifecycleState.inactive: | ||
| 111 | + // Stop the scanner when the app is paused. | ||
| 112 | + // Also stop the barcode events subscription. | ||
| 113 | + unawaited(_subscription?.cancel()); | ||
| 114 | + _subscription = null; | ||
| 115 | + unawaited(controller.stop()); | ||
| 116 | + } | ||
| 125 | } | 117 | } |
| 118 | + | ||
| 119 | + // ... | ||
| 120 | +} | ||
| 126 | ``` | 121 | ``` |
| 127 | 122 | ||
| 128 | -Example with controller and torch & camera controls: | 123 | +Then, start the scanner in `void initState()`: |
| 129 | 124 | ||
| 130 | ```dart | 125 | ```dart |
| 131 | -import 'package:mobile_scanner/mobile_scanner.dart'; | ||
| 132 | - | ||
| 133 | - MobileScannerController cameraController = MobileScannerController(); | ||
| 134 | - | ||
| 135 | - @override | ||
| 136 | - Widget build(BuildContext context) { | ||
| 137 | - return Scaffold( | ||
| 138 | - appBar: AppBar( | ||
| 139 | - title: const Text('Mobile Scanner'), | ||
| 140 | - actions: [ | ||
| 141 | - IconButton( | ||
| 142 | - color: Colors.white, | ||
| 143 | - icon: ValueListenableBuilder( | ||
| 144 | - valueListenable: cameraController.torchState, | ||
| 145 | - builder: (context, state, child) { | ||
| 146 | - switch (state as TorchState) { | ||
| 147 | - case TorchState.off: | ||
| 148 | - return const Icon(Icons.flash_off, color: Colors.grey); | ||
| 149 | - case TorchState.on: | ||
| 150 | - return const Icon(Icons.flash_on, color: Colors.yellow); | ||
| 151 | - } | ||
| 152 | - }, | ||
| 153 | - ), | ||
| 154 | - iconSize: 32.0, | ||
| 155 | - onPressed: () => cameraController.toggleTorch(), | ||
| 156 | - ), | ||
| 157 | - IconButton( | ||
| 158 | - color: Colors.white, | ||
| 159 | - icon: ValueListenableBuilder( | ||
| 160 | - valueListenable: cameraController.cameraFacingState, | ||
| 161 | - builder: (context, state, child) { | ||
| 162 | - switch (state as CameraFacing) { | ||
| 163 | - case CameraFacing.front: | ||
| 164 | - return const Icon(Icons.camera_front); | ||
| 165 | - case CameraFacing.back: | ||
| 166 | - return const Icon(Icons.camera_rear); | ||
| 167 | - } | ||
| 168 | - }, | ||
| 169 | - ), | ||
| 170 | - iconSize: 32.0, | ||
| 171 | - onPressed: () => cameraController.switchCamera(), | ||
| 172 | - ), | ||
| 173 | - ], | ||
| 174 | - ), | ||
| 175 | - body: MobileScanner( | ||
| 176 | - // fit: BoxFit.contain, | ||
| 177 | - controller: cameraController, | ||
| 178 | - onDetect: (capture) { | ||
| 179 | - final List<Barcode> barcodes = capture.barcodes; | ||
| 180 | - final Uint8List? image = capture.image; | ||
| 181 | - for (final barcode in barcodes) { | ||
| 182 | - debugPrint('Barcode found! ${barcode.rawValue}'); | ||
| 183 | - } | ||
| 184 | - }, | ||
| 185 | - ), | ||
| 186 | - ); | ||
| 187 | - } | 126 | +@override |
| 127 | +void initState() { | ||
| 128 | + super.initState(); | ||
| 129 | + // Start listening to lifecycle changes. | ||
| 130 | + WidgetsBinding.instance.addObserver(this); | ||
| 131 | + | ||
| 132 | + // Start listening to the barcode events. | ||
| 133 | + _subscription = controller.barcodes.listen(_handleBarcode); | ||
| 134 | + | ||
| 135 | + // Finally, start the scanner itself. | ||
| 136 | + unawaited(controller.start()); | ||
| 137 | +} | ||
| 188 | ``` | 138 | ``` |
| 189 | 139 | ||
| 190 | -Example with controller and returning images | 140 | +Finally, dispose of the the `MobileScannerController` when you are done with it. |
| 191 | 141 | ||
| 192 | ```dart | 142 | ```dart |
| 193 | -import 'package:mobile_scanner/mobile_scanner.dart'; | ||
| 194 | - | ||
| 195 | - @override | ||
| 196 | - Widget build(BuildContext context) { | ||
| 197 | - return Scaffold( | ||
| 198 | - appBar: AppBar(title: const Text('Mobile Scanner')), | ||
| 199 | - body: MobileScanner( | ||
| 200 | - fit: BoxFit.contain, | ||
| 201 | - controller: MobileScannerController( | ||
| 202 | - // facing: CameraFacing.back, | ||
| 203 | - // torchEnabled: false, | ||
| 204 | - returnImage: true, | ||
| 205 | - ), | ||
| 206 | - onDetect: (capture) { | ||
| 207 | - final List<Barcode> barcodes = capture.barcodes; | ||
| 208 | - final Uint8List? image = capture.image; | ||
| 209 | - for (final barcode in barcodes) { | ||
| 210 | - debugPrint('Barcode found! ${barcode.rawValue}'); | ||
| 211 | - } | ||
| 212 | - if (image != null) { | ||
| 213 | - showDialog( | ||
| 214 | - context: context, | ||
| 215 | - builder: (context) => | ||
| 216 | - Image(image: MemoryImage(image)), | ||
| 217 | - ); | ||
| 218 | - Future.delayed(const Duration(seconds: 5), () { | ||
| 219 | - Navigator.pop(context); | ||
| 220 | - }); | ||
| 221 | - } | ||
| 222 | - }, | ||
| 223 | - ), | ||
| 224 | - ); | ||
| 225 | - } | 143 | +@override |
| 144 | +Future<void> dispose() async { | ||
| 145 | + // Stop listening to lifecycle changes. | ||
| 146 | + WidgetsBinding.instance.removeObserver(this); | ||
| 147 | + // Stop listening to the barcode events. | ||
| 148 | + unawaited(_subscription?.cancel()); | ||
| 149 | + _subscription = null; | ||
| 150 | + // Dispose the widget itself. | ||
| 151 | + super.dispose(); | ||
| 152 | + // Finally, dispose of the controller. | ||
| 153 | + await controller.dispose(); | ||
| 154 | +} | ||
| 226 | ``` | 155 | ``` |
| 227 | 156 | ||
| 228 | -### BarcodeCapture | ||
| 229 | - | ||
| 230 | -The onDetect function returns a BarcodeCapture objects which contains the following items. | ||
| 231 | - | ||
| 232 | -| Property name | Type | Description | | ||
| 233 | -|---------------|---------------|-----------------------------------| | ||
| 234 | -| barcodes | List<Barcode> | A list with scanned barcodes. | | ||
| 235 | -| image | Uint8List? | If enabled, an image of the scan. | | ||
| 236 | - | ||
| 237 | -You can use the following properties of the Barcode object. | ||
| 238 | - | ||
| 239 | -| Property name | Type | Description | | ||
| 240 | -|---------------|----------------|-------------------------------------| | ||
| 241 | -| format | BarcodeFormat | | | ||
| 242 | -| rawBytes | Uint8List? | binary scan result | | ||
| 243 | -| rawValue | String? | Value if barcode is in UTF-8 format | | ||
| 244 | -| displayValue | String? | | | ||
| 245 | -| type | BarcodeType | | | ||
| 246 | -| calendarEvent | CalendarEvent? | | | ||
| 247 | -| contactInfo | ContactInfo? | | | ||
| 248 | -| driverLicense | DriverLicense? | | | ||
| 249 | -| email | Email? | | | ||
| 250 | -| geoPoint | GeoPoint? | | | ||
| 251 | -| phone | Phone? | | | ||
| 252 | -| sms | SMS? | | | ||
| 253 | -| url | UrlBookmark? | | | ||
| 254 | -| wifi | WiFi? | WiFi Access-Point details | | 157 | +To display the camera preview, pass the controller to a `MobileScanner` widget. |
| 158 | + | ||
| 159 | +See the examples for runnable examples of various usages, | ||
| 160 | +such as the basic usage, applying a scan window, or retrieving images from the barcodes. |
-
Please register or login to post a comment