Julian Steenbakker
Committed by GitHub

Merge branch 'master' into dependabot/gradle/android/org.jetbrains.kotlin-kotlin-stdlib-jdk7-1.7.22

  1 +## 3.0.0-beta.3
  2 +Deprecated:
  3 +* The `onStart` method has been renamed to `onScannerStarted`.
  4 +* The `onPermissionSet` argument of the `MobileScannerController` is now deprecated.
  5 +
  6 +Breaking changes:
  7 +* `MobileScannerException` now uses an `errorCode` instead of a `message`.
  8 +* `MobileScannerException` now contains additional details from the original error.
  9 +* Refactored `MobileScannerController.start()` to throw `MobileScannerException`s
  10 + with consistent error codes, rather than string messages.
  11 + To handle permission errors, consider catching the result of `MobileScannerController.start()`.
  12 +* The `autoResume` attribute has been removed from the `MobileScanner` widget.
  13 + The controller already automatically resumes, so it had no effect.
  14 +* Removed `MobileScannerCallback` and `MobileScannerArgumentsCallback` typedef.
  15 +
  16 +Improvements:
  17 +* Toggling the device torch now does nothing if the device has no torch, rather than throwing an error.
  18 +* Removed `called stop while already stopped` messages.
  19 +
  20 +Features:
  21 +* Added a new `placeholderBuilder` function to the `MobileScanner` widget to customize the preview placeholder.
  22 +* Added `autoStart` parameter to MobileScannerController(). If set to false, controller won't start automatically.
  23 +* Added `hasTorch` function on MobileScannerController(). After starting the controller, you can check if the device has a torch.
  24 +
  25 +Fixes:
  26 +* Fixes the missing gradle setup for the Android project, which prevented gradle sync from working.
  27 +* Fixes `MobileScannerController.stop()` throwing when already stopped.
  28 +* Fixes `MobileScannerController.toggleTorch()` throwing if the device has no torch.
  29 + Now it does nothing if the torch is not available.
  30 +* Fixes a memory leak where the `MobileScanner` would keep listening to the barcode events.
  31 +* Fixes the `MobileScanner` preview depending on all attributes of `MediaQueryData`.
  32 + Now it only depends on its layout constraints.
  33 +* Fixed a potential crash when the scanner is restarted due to the app being resumed.
  34 +
1 ## 3.0.0-beta.2 35 ## 3.0.0-beta.2
2 Breaking changes: 36 Breaking changes:
3 * The arguments parameter of onDetect is removed. The data is now returned by the onStart callback 37 * The arguments parameter of onDetect is removed. The data is now returned by the onStart callback
@@ -10,6 +10,7 @@ buildscript { @@ -10,6 +10,7 @@ buildscript {
10 10
11 dependencies { 11 dependencies {
12 classpath 'com.android.tools.build:gradle:7.3.1' 12 classpath 'com.android.tools.build:gradle:7.3.1'
  13 + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 } 14 }
14 } 15 }
15 16
  1 +#Fri Jun 23 08:50:38 CEST 2017
  2 +distributionBase=GRADLE_USER_HOME
  3 +distributionPath=wrapper/dists
  4 +zipStoreBase=GRADLE_USER_HOME
  5 +zipStorePath=wrapper/dists
  6 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
@@ -17,10 +17,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanning @@ -17,10 +17,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanning
17 import com.google.mlkit.vision.common.InputImage 17 import com.google.mlkit.vision.common.InputImage
18 import dev.steenbakker.mobile_scanner.objects.DetectionSpeed 18 import dev.steenbakker.mobile_scanner.objects.DetectionSpeed
19 import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters 19 import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters
  20 +import io.flutter.plugin.common.MethodChannel
20 import io.flutter.plugin.common.PluginRegistry 21 import io.flutter.plugin.common.PluginRegistry
21 import io.flutter.view.TextureRegistry 22 import io.flutter.view.TextureRegistry
22 -  
23 -typealias PermissionCallback = (permissionGranted: Boolean) -> Unit  
24 typealias MobileScannerCallback = (barcodes: List<Map<String, Any?>>, image: ByteArray?) -> Unit 23 typealias MobileScannerCallback = (barcodes: List<Map<String, Any?>>, image: ByteArray?) -> Unit
25 typealias AnalyzerCallback = (barcodes: List<Map<String, Any?>>?) -> Unit 24 typealias AnalyzerCallback = (barcodes: List<Map<String, Any?>>?) -> Unit
26 typealias MobileScannerErrorCallback = (error: String) -> Unit 25 typealias MobileScannerErrorCallback = (error: String) -> Unit
@@ -49,10 +48,9 @@ class MobileScanner( @@ -49,10 +48,9 @@ class MobileScanner(
49 private const val REQUEST_CODE = 0x0786 48 private const val REQUEST_CODE = 0x0786
50 } 49 }
51 50
52 - private var listener: PluginRegistry.RequestPermissionsResultListener? = null  
53 -  
54 private var cameraProvider: ProcessCameraProvider? = null 51 private var cameraProvider: ProcessCameraProvider? = null
55 private var camera: Camera? = null 52 private var camera: Camera? = null
  53 + private var pendingPermissionResult: MethodChannel.Result? = null
56 private var preview: Preview? = null 54 private var preview: Preview? = null
57 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null 55 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
58 56
@@ -86,17 +84,12 @@ class MobileScanner( @@ -86,17 +84,12 @@ class MobileScanner(
86 /** 84 /**
87 * Request camera permissions. 85 * Request camera permissions.
88 */ 86 */
89 - fun requestPermission(permissionCallback: PermissionCallback) {  
90 - listener  
91 - ?: PluginRegistry.RequestPermissionsResultListener { requestCode, _, grantResults ->  
92 - if (requestCode != REQUEST_CODE) {  
93 - false  
94 - } else {  
95 - val authorized = grantResults[0] == PackageManager.PERMISSION_GRANTED  
96 - permissionCallback(authorized)  
97 - true  
98 - } 87 + fun requestPermission(result: MethodChannel.Result) {
  88 + if(pendingPermissionResult != null) {
  89 + return
99 } 90 }
  91 +
  92 + pendingPermissionResult = result
100 val permissions = arrayOf(Manifest.permission.CAMERA) 93 val permissions = arrayOf(Manifest.permission.CAMERA)
101 ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE) 94 ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE)
102 } 95 }
@@ -109,7 +102,14 @@ class MobileScanner( @@ -109,7 +102,14 @@ class MobileScanner(
109 permissions: Array<out String>, 102 permissions: Array<out String>,
110 grantResults: IntArray 103 grantResults: IntArray
111 ): Boolean { 104 ): Boolean {
112 - return listener?.onRequestPermissionsResult(requestCode, permissions, grantResults) ?: false 105 + if (requestCode != REQUEST_CODE) {
  106 + return false
  107 + }
  108 +
  109 + pendingPermissionResult?.success(grantResults[0] == PackageManager.PERMISSION_GRANTED)
  110 + pendingPermissionResult = null
  111 +
  112 + return true
113 } 113 }
114 114
115 /** 115 /**
@@ -23,14 +23,8 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa @@ -23,14 +23,8 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa
23 23
24 private lateinit var barcodeHandler: BarcodeHandler 24 private lateinit var barcodeHandler: BarcodeHandler
25 25
26 - private var permissionResult: MethodChannel.Result? = null  
27 private var analyzerResult: MethodChannel.Result? = null 26 private var analyzerResult: MethodChannel.Result? = null
28 27
29 - private val permissionCallback: PermissionCallback = {hasPermission: Boolean ->  
30 - permissionResult?.success(hasPermission)  
31 - permissionResult = null  
32 - }  
33 -  
34 private val callback: MobileScannerCallback = { barcodes: List<Map<String, Any?>>, image: ByteArray? -> 28 private val callback: MobileScannerCallback = { barcodes: List<Map<String, Any?>>, image: ByteArray? ->
35 if (image != null) { 29 if (image != null) {
36 barcodeHandler.publishEvent(mapOf( 30 barcodeHandler.publishEvent(mapOf(
@@ -78,7 +72,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa @@ -78,7 +72,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa
78 } 72 }
79 when (call.method) { 73 when (call.method) {
80 "state" -> result.success(handler!!.hasCameraPermission()) 74 "state" -> result.success(handler!!.hasCameraPermission())
81 - "request" -> requestPermission(result) 75 + "request" -> handler!!.requestPermission(result)
82 "start" -> start(call, result) 76 "start" -> start(call, result)
83 "torch" -> toggleTorch(call, result) 77 "torch" -> toggleTorch(call, result)
84 "stop" -> stop(result) 78 "stop" -> stop(result)
@@ -124,11 +118,6 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa @@ -124,11 +118,6 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa
124 onDetachedFromActivity() 118 onDetachedFromActivity()
125 } 119 }
126 120
127 - private fun requestPermission(result: MethodChannel.Result) {  
128 - permissionResult = result  
129 - handler!!.requestPermission(permissionCallback)  
130 - }  
131 -  
132 @ExperimentalGetImage 121 @ExperimentalGetImage
133 private fun start(call: MethodCall, result: MethodChannel.Result) { 122 private fun start(call: MethodCall, result: MethodChannel.Result) {
134 val torch: Boolean = call.argument<Boolean>("torch") ?: false 123 val torch: Boolean = call.argument<Boolean>("torch") ?: false
@@ -208,7 +197,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa @@ -208,7 +197,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa
208 handler!!.stop() 197 handler!!.stop()
209 result.success(null) 198 result.success(null)
210 } catch (e: AlreadyStopped) { 199 } catch (e: AlreadyStopped) {
211 - result.error("MobileScanner", "Called stop() while already stopped!", null) 200 + result.success(null)
212 } 201 }
213 } 202 }
214 203
@@ -15,13 +15,10 @@ class _BarcodeListScannerWithControllerState @@ -15,13 +15,10 @@ class _BarcodeListScannerWithControllerState
15 with SingleTickerProviderStateMixin { 15 with SingleTickerProviderStateMixin {
16 BarcodeCapture? barcodeCapture; 16 BarcodeCapture? barcodeCapture;
17 17
18 - MobileScannerController controller = MobileScannerController( 18 + final MobileScannerController controller = MobileScannerController(
19 torchEnabled: true, 19 torchEnabled: true,
20 // formats: [BarcodeFormat.qrCode] 20 // formats: [BarcodeFormat.qrCode]
21 // facing: CameraFacing.front, 21 // facing: CameraFacing.front,
22 - onPermissionSet: (hasPermission) {  
23 - // Do something with permission callback  
24 - },  
25 // detectionSpeed: DetectionSpeed.normal 22 // detectionSpeed: DetectionSpeed.normal
26 // detectionTimeoutMs: 1000, 23 // detectionTimeoutMs: 1000,
27 // returnImage: false, 24 // returnImage: false,
@@ -29,6 +26,31 @@ class _BarcodeListScannerWithControllerState @@ -29,6 +26,31 @@ class _BarcodeListScannerWithControllerState
29 26
30 bool isStarted = true; 27 bool isStarted = true;
31 28
  29 + void _startOrStop() {
  30 + if (isStarted) {
  31 + controller.stop();
  32 + } else {
  33 + controller.start().catchError((error) {
  34 + final exception = error as MobileScannerException;
  35 +
  36 + switch (exception.errorCode) {
  37 + case MobileScannerErrorCode.controllerUninitialized:
  38 + break; // This error code is not used by `start()`.
  39 + case MobileScannerErrorCode.genericError:
  40 + debugPrint('Scanner failed to start');
  41 + break;
  42 + case MobileScannerErrorCode.permissionDenied:
  43 + debugPrint('Camera permission denied');
  44 + break;
  45 + }
  46 + });
  47 + }
  48 +
  49 + setState(() {
  50 + isStarted = !isStarted;
  51 + });
  52 + }
  53 +
32 @override 54 @override
33 Widget build(BuildContext context) { 55 Widget build(BuildContext context) {
34 return Scaffold( 56 return Scaffold(
@@ -40,17 +62,13 @@ class _BarcodeListScannerWithControllerState @@ -40,17 +62,13 @@ class _BarcodeListScannerWithControllerState
40 MobileScanner( 62 MobileScanner(
41 controller: controller, 63 controller: controller,
42 fit: BoxFit.contain, 64 fit: BoxFit.contain,
43 - // controller: MobileScannerController(  
44 - // torchEnabled: true,  
45 - // facing: CameraFacing.front,  
46 - // ),  
47 onDetect: (barcodeCapture) { 65 onDetect: (barcodeCapture) {
48 setState(() { 66 setState(() {
49 this.barcodeCapture = barcodeCapture; 67 this.barcodeCapture = barcodeCapture;
50 }); 68 });
51 }, 69 },
52 - onStart: (arguments) {  
53 - // Do something with start arguments 70 + onScannerStarted: (arguments) {
  71 + // Do something with arguments.
54 }, 72 },
55 ), 73 ),
56 Align( 74 Align(
@@ -96,10 +114,7 @@ class _BarcodeListScannerWithControllerState @@ -96,10 +114,7 @@ class _BarcodeListScannerWithControllerState
96 ? const Icon(Icons.stop) 114 ? const Icon(Icons.stop)
97 : const Icon(Icons.play_arrow), 115 : const Icon(Icons.play_arrow),
98 iconSize: 32.0, 116 iconSize: 32.0,
99 - onPressed: () => setState(() {  
100 - isStarted ? controller.stop() : controller.start();  
101 - isStarted = !isStarted;  
102 - }), 117 + onPressed: _startOrStop,
103 ), 118 ),
104 Center( 119 Center(
105 child: SizedBox( 120 child: SizedBox(
@@ -177,4 +192,10 @@ class _BarcodeListScannerWithControllerState @@ -177,4 +192,10 @@ class _BarcodeListScannerWithControllerState
177 ), 192 ),
178 ); 193 );
179 } 194 }
  195 +
  196 + @override
  197 + void dispose() {
  198 + controller.dispose();
  199 + super.dispose();
  200 + }
180 } 201 }
@@ -15,13 +15,10 @@ class _BarcodeScannerWithControllerState @@ -15,13 +15,10 @@ class _BarcodeScannerWithControllerState
15 with SingleTickerProviderStateMixin { 15 with SingleTickerProviderStateMixin {
16 BarcodeCapture? barcode; 16 BarcodeCapture? barcode;
17 17
18 - MobileScannerController controller = MobileScannerController( 18 + final MobileScannerController controller = MobileScannerController(
19 torchEnabled: true, 19 torchEnabled: true,
20 // formats: [BarcodeFormat.qrCode] 20 // formats: [BarcodeFormat.qrCode]
21 // facing: CameraFacing.front, 21 // facing: CameraFacing.front,
22 - onPermissionSet: (hasPermission) {  
23 - // Do something with permission callback  
24 - },  
25 // detectionSpeed: DetectionSpeed.normal 22 // detectionSpeed: DetectionSpeed.normal
26 // detectionTimeoutMs: 1000, 23 // detectionTimeoutMs: 1000,
27 // returnImage: false, 24 // returnImage: false,
@@ -29,6 +26,31 @@ class _BarcodeScannerWithControllerState @@ -29,6 +26,31 @@ class _BarcodeScannerWithControllerState
29 26
30 bool isStarted = true; 27 bool isStarted = true;
31 28
  29 + void _startOrStop() {
  30 + if (isStarted) {
  31 + controller.stop();
  32 + } else {
  33 + controller.start().catchError((error) {
  34 + final exception = error as MobileScannerException;
  35 +
  36 + switch (exception.errorCode) {
  37 + case MobileScannerErrorCode.controllerUninitialized:
  38 + break; // This error code is not used by `start()`.
  39 + case MobileScannerErrorCode.genericError:
  40 + debugPrint('Scanner failed to start');
  41 + break;
  42 + case MobileScannerErrorCode.permissionDenied:
  43 + debugPrint('Camera permission denied');
  44 + break;
  45 + }
  46 + });
  47 + }
  48 +
  49 + setState(() {
  50 + isStarted = !isStarted;
  51 + });
  52 + }
  53 +
32 @override 54 @override
33 Widget build(BuildContext context) { 55 Widget build(BuildContext context) {
34 return Scaffold( 56 return Scaffold(
@@ -40,10 +62,6 @@ class _BarcodeScannerWithControllerState @@ -40,10 +62,6 @@ class _BarcodeScannerWithControllerState
40 MobileScanner( 62 MobileScanner(
41 controller: controller, 63 controller: controller,
42 fit: BoxFit.contain, 64 fit: BoxFit.contain,
43 - // controller: MobileScannerController(  
44 - // torchEnabled: true,  
45 - // facing: CameraFacing.front,  
46 - // ),  
47 onDetect: (barcode) { 65 onDetect: (barcode) {
48 setState(() { 66 setState(() {
49 this.barcode = barcode; 67 this.barcode = barcode;
@@ -93,10 +111,7 @@ class _BarcodeScannerWithControllerState @@ -93,10 +111,7 @@ class _BarcodeScannerWithControllerState
93 ? const Icon(Icons.stop) 111 ? const Icon(Icons.stop)
94 : const Icon(Icons.play_arrow), 112 : const Icon(Icons.play_arrow),
95 iconSize: 32.0, 113 iconSize: 32.0,
96 - onPressed: () => setState(() {  
97 - isStarted ? controller.stop() : controller.start();  
98 - isStarted = !isStarted;  
99 - }), 114 + onPressed: _startOrStop,
100 ), 115 ),
101 Center( 116 Center(
102 child: SizedBox( 117 child: SizedBox(
@@ -175,4 +190,10 @@ class _BarcodeScannerWithControllerState @@ -175,4 +190,10 @@ class _BarcodeScannerWithControllerState
175 ), 190 ),
176 ); 191 );
177 } 192 }
  193 +
  194 + @override
  195 + void dispose() {
  196 + controller.dispose();
  197 + super.dispose();
  198 + }
178 } 199 }
@@ -17,13 +17,10 @@ class _BarcodeScannerReturningImageState @@ -17,13 +17,10 @@ class _BarcodeScannerReturningImageState
17 BarcodeCapture? barcode; 17 BarcodeCapture? barcode;
18 MobileScannerArguments? arguments; 18 MobileScannerArguments? arguments;
19 19
20 - MobileScannerController controller = MobileScannerController( 20 + final MobileScannerController controller = MobileScannerController(
21 torchEnabled: true, 21 torchEnabled: true,
22 // formats: [BarcodeFormat.qrCode] 22 // formats: [BarcodeFormat.qrCode]
23 // facing: CameraFacing.front, 23 // facing: CameraFacing.front,
24 - onPermissionSet: (hasPermission) {  
25 - // Do something with permission callback  
26 - },  
27 // detectionSpeed: DetectionSpeed.normal 24 // detectionSpeed: DetectionSpeed.normal
28 // detectionTimeoutMs: 1000, 25 // detectionTimeoutMs: 1000,
29 returnImage: true, 26 returnImage: true,
@@ -31,6 +28,31 @@ class _BarcodeScannerReturningImageState @@ -31,6 +28,31 @@ class _BarcodeScannerReturningImageState
31 28
32 bool isStarted = true; 29 bool isStarted = true;
33 30
  31 + void _startOrStop() {
  32 + if (isStarted) {
  33 + controller.stop();
  34 + } else {
  35 + controller.start().catchError((error) {
  36 + final exception = error as MobileScannerException;
  37 +
  38 + switch (exception.errorCode) {
  39 + case MobileScannerErrorCode.controllerUninitialized:
  40 + break; // This error code is not used by `start()`.
  41 + case MobileScannerErrorCode.genericError:
  42 + debugPrint('Scanner failed to start');
  43 + break;
  44 + case MobileScannerErrorCode.permissionDenied:
  45 + debugPrint('Camera permission denied');
  46 + break;
  47 + }
  48 + });
  49 + }
  50 +
  51 + setState(() {
  52 + isStarted = !isStarted;
  53 + });
  54 + }
  55 +
34 @override 56 @override
35 Widget build(BuildContext context) { 57 Widget build(BuildContext context) {
36 return Scaffold( 58 return Scaffold(
@@ -62,10 +84,6 @@ class _BarcodeScannerReturningImageState @@ -62,10 +84,6 @@ class _BarcodeScannerReturningImageState
62 MobileScanner( 84 MobileScanner(
63 controller: controller, 85 controller: controller,
64 fit: BoxFit.contain, 86 fit: BoxFit.contain,
65 - // controller: MobileScannerController(  
66 - // torchEnabled: true,  
67 - // facing: CameraFacing.front,  
68 - // ),  
69 onDetect: (barcode) { 87 onDetect: (barcode) {
70 setState(() { 88 setState(() {
71 this.barcode = barcode; 89 this.barcode = barcode;
@@ -115,12 +133,7 @@ class _BarcodeScannerReturningImageState @@ -115,12 +133,7 @@ class _BarcodeScannerReturningImageState
115 ? const Icon(Icons.stop) 133 ? const Icon(Icons.stop)
116 : const Icon(Icons.play_arrow), 134 : const Icon(Icons.play_arrow),
117 iconSize: 32.0, 135 iconSize: 32.0,
118 - onPressed: () => setState(() {  
119 - isStarted  
120 - ? controller.stop()  
121 - : controller.start();  
122 - isStarted = !isStarted;  
123 - }), 136 + onPressed: _startOrStop,
124 ), 137 ),
125 Center( 138 Center(
126 child: SizedBox( 139 child: SizedBox(
@@ -171,4 +184,10 @@ class _BarcodeScannerReturningImageState @@ -171,4 +184,10 @@ class _BarcodeScannerReturningImageState
171 ), 184 ),
172 ); 185 );
173 } 186 }
  187 +
  188 + @override
  189 + void dispose() {
  190 + controller.dispose();
  191 + super.dispose();
  192 + }
174 } 193 }
@@ -107,11 +107,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -107,11 +107,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
107 private func stop(_ result: @escaping FlutterResult) { 107 private func stop(_ result: @escaping FlutterResult) {
108 do { 108 do {
109 try mobileScanner.stop() 109 try mobileScanner.stop()
110 - } catch {  
111 - result(FlutterError(code: "MobileScanner",  
112 - message: "Called stop() while already stopped!",  
113 - details: nil))  
114 - } 110 + } catch {}
115 result(nil) 111 result(nil)
116 } 112 }
117 113
@@ -2,11 +2,13 @@ library mobile_scanner; @@ -2,11 +2,13 @@ library mobile_scanner;
2 2
3 export 'src/enums/camera_facing.dart'; 3 export 'src/enums/camera_facing.dart';
4 export 'src/enums/detection_speed.dart'; 4 export 'src/enums/detection_speed.dart';
  5 +export 'src/enums/mobile_scanner_error_code.dart';
5 export 'src/enums/mobile_scanner_state.dart'; 6 export 'src/enums/mobile_scanner_state.dart';
6 export 'src/enums/ratio.dart'; 7 export 'src/enums/ratio.dart';
7 export 'src/enums/torch_state.dart'; 8 export 'src/enums/torch_state.dart';
8 export 'src/mobile_scanner.dart'; 9 export 'src/mobile_scanner.dart';
9 export 'src/mobile_scanner_controller.dart'; 10 export 'src/mobile_scanner_controller.dart';
  11 +export 'src/mobile_scanner_exception.dart';
10 export 'src/objects/barcode.dart'; 12 export 'src/objects/barcode.dart';
11 export 'src/objects/barcode_capture.dart'; 13 export 'src/objects/barcode_capture.dart';
12 export 'src/objects/mobile_scanner_arguments.dart'; 14 export 'src/objects/mobile_scanner_arguments.dart';
  1 +/// This enum defines the different error codes for the mobile scanner.
  2 +enum MobileScannerErrorCode {
  3 + /// The controller was used
  4 + /// while it was not yet initialized using [MobileScannerController.start].
  5 + controllerUninitialized,
  6 +
  7 + /// A generic error occurred.
  8 + ///
  9 + /// This error code is used for all errors that do not have a specific error code.
  10 + genericError,
  11 +
  12 + /// The permission to use the camera was denied.
  13 + permissionDenied,
  14 +}
1 /// The authorization state of the scanner. 1 /// The authorization state of the scanner.
2 enum MobileScannerState { 2 enum MobileScannerState {
3 - /// The scanner has yet to request weather it is [authorized] or [denied] 3 + /// The scanner has not yet requested the required permissions.
4 undetermined, 4 undetermined,
5 5
6 /// The scanner has the required permissions. 6 /// The scanner has the required permissions.
  1 +import 'dart:async';
  2 +
1 import 'package:flutter/foundation.dart'; 3 import 'package:flutter/foundation.dart';
2 import 'package:flutter/material.dart'; 4 import 'package:flutter/material.dart';
3 import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; 5 import 'package:mobile_scanner/src/mobile_scanner_controller.dart';
4 import 'package:mobile_scanner/src/objects/barcode_capture.dart'; 6 import 'package:mobile_scanner/src/objects/barcode_capture.dart';
5 import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart'; 7 import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart';
6 8
7 -typedef MobileScannerCallback = void Function(BarcodeCapture barcodes);  
8 -typedef MobileScannerArgumentsCallback = void Function(  
9 - MobileScannerArguments? arguments,  
10 -);  
11 -  
12 -/// A widget showing a live camera preview. 9 +/// The [MobileScanner] widget displays a live camera preview.
13 class MobileScanner extends StatefulWidget { 10 class MobileScanner extends StatefulWidget {
14 - /// The controller of the camera. 11 + /// The controller that manages the barcode scanner.
  12 + ///
  13 + /// If this is null, the scanner will manage its own controller.
15 final MobileScannerController? controller; 14 final MobileScannerController? controller;
16 15
17 - /// Calls the provided [onPermissionSet] callback when the permission is set.  
18 - // @Deprecated('Use the [onPermissionSet] paremeter in the [MobileScannerController] instead.')  
19 - // ignore: deprecated_consistency  
20 - final Function(bool permissionGranted)? onPermissionSet;  
21 -  
22 - /// Function that gets called when a Barcode is detected. 16 + /// The [BoxFit] for the camera preview.
23 /// 17 ///
24 - /// [barcode] The barcode object with all information about the scanned code.  
25 - /// [startInternalArguments] Information about the state of the MobileScanner widget  
26 - final MobileScannerCallback onDetect; 18 + /// Defaults to [BoxFit.cover].
  19 + final BoxFit fit;
27 20
28 - /// Function that gets called when the scanner is started.  
29 - ///  
30 - /// [arguments] The start arguments of the scanner. This contains the size of  
31 - /// the scanner which can be used to draw a box over the scanner.  
32 - final MobileScannerArgumentsCallback? onStart; 21 + /// The function that signals when new codes were detected by the [controller].
  22 + final void Function(BarcodeCapture barcodes) onDetect;
33 23
34 - /// Handles how the widget should fit the screen.  
35 - final BoxFit fit; 24 + /// The function that signals when the barcode scanner is started.
  25 + @Deprecated('Use onScannerStarted() instead.')
  26 + final void Function(MobileScannerArguments? arguments)? onStart;
  27 +
  28 + /// The function that signals when the barcode scanner is started.
  29 + final void Function(MobileScannerArguments? arguments)? onScannerStarted;
36 30
37 - /// Whether to automatically resume the camera when the application is resumed  
38 - final bool autoResume; 31 + /// The function that builds a placeholder widget when the scanner
  32 + /// is not yet displaying its camera preview.
  33 + ///
  34 + /// If this is null, a black [ColoredBox] is used as placeholder.
  35 + final Widget Function(BuildContext, Widget?)? placeholderBuilder;
39 36
40 - /// Create a [MobileScanner] with a [controller], the [controller] must has been initialized. 37 + /// Create a new [MobileScanner] using the provided [controller]
  38 + /// and [onBarcodeDetected] callback.
41 const MobileScanner({ 39 const MobileScanner({
42 - super.key,  
43 - required this.onDetect,  
44 - this.onStart,  
45 this.controller, 40 this.controller,
46 - this.autoResume = true,  
47 this.fit = BoxFit.cover, 41 this.fit = BoxFit.cover,
48 - @Deprecated('Use the [onPermissionSet] paremeter in the [MobileScannerController] instead.')  
49 - this.onPermissionSet, 42 + required this.onDetect,
  43 + @Deprecated('Use onScannerStarted() instead.') this.onStart,
  44 + this.onScannerStarted,
  45 + this.placeholderBuilder,
  46 + super.key,
50 }); 47 });
51 48
52 @override 49 @override
@@ -55,65 +52,86 @@ class MobileScanner extends StatefulWidget { @@ -55,65 +52,86 @@ class MobileScanner extends StatefulWidget {
55 52
56 class _MobileScannerState extends State<MobileScanner> 53 class _MobileScannerState extends State<MobileScanner>
57 with WidgetsBindingObserver { 54 with WidgetsBindingObserver {
58 - late MobileScannerController controller; 55 + /// The subscription that listens to barcode detection.
  56 + StreamSubscription<BarcodeCapture>? _barcodesSubscription;
  57 +
  58 + /// The internally managed controller.
  59 + late MobileScannerController _controller;
  60 +
  61 + /// Whether the controller should resume
  62 + /// when the application comes back to the foreground.
  63 + bool _resumeFromBackground = false;
  64 +
  65 + /// Start the given [scanner].
  66 + void _startScanner(MobileScannerController scanner) {
  67 + if (!_controller.autoStart) {
  68 + debugPrint(
  69 + 'mobile_scanner: not starting automatically because autoStart is set to false in the controller.',
  70 + );
  71 + return;
  72 + }
  73 + scanner.start().then((arguments) {
  74 + // ignore: deprecated_member_use_from_same_package
  75 + widget.onStart?.call(arguments);
  76 + widget.onScannerStarted?.call(arguments);
  77 + });
  78 + }
59 79
60 @override 80 @override
61 void initState() { 81 void initState() {
62 super.initState(); 82 super.initState();
63 WidgetsBinding.instance.addObserver(this); 83 WidgetsBinding.instance.addObserver(this);
64 - controller = widget.controller ??  
65 - MobileScannerController(onPermissionSet: widget.onPermissionSet);  
66 - if (!controller.isStarting) {  
67 - _startScanner();  
68 - }  
69 - } 84 + _controller = widget.controller ?? MobileScannerController();
70 85
71 - Future<void> _startScanner() async {  
72 - final arguments = await controller.start();  
73 - widget.onStart?.call(arguments);  
74 - } 86 + _barcodesSubscription = _controller.barcodes.listen(
  87 + widget.onDetect,
  88 + );
75 89
76 - bool resumeFromBackground = false; 90 + if (!_controller.isStarting) {
  91 + _startScanner(_controller);
  92 + }
  93 + }
77 94
78 @override 95 @override
79 void didChangeAppLifecycleState(AppLifecycleState state) { 96 void didChangeAppLifecycleState(AppLifecycleState state) {
80 - // App state changed before it is initialized.  
81 - if (controller.isStarting) { 97 + // App state changed before the controller was initialized.
  98 + if (_controller.isStarting) {
82 return; 99 return;
83 } 100 }
84 101
85 switch (state) { 102 switch (state) {
86 case AppLifecycleState.resumed: 103 case AppLifecycleState.resumed:
87 - resumeFromBackground = false;  
88 - _startScanner(); 104 + _resumeFromBackground = false;
  105 + _startScanner(_controller);
89 break; 106 break;
90 case AppLifecycleState.paused: 107 case AppLifecycleState.paused:
91 - resumeFromBackground = true; 108 + _resumeFromBackground = true;
92 break; 109 break;
93 case AppLifecycleState.inactive: 110 case AppLifecycleState.inactive:
94 - if (!resumeFromBackground) controller.stop(); 111 + if (!_resumeFromBackground) {
  112 + _controller.stop();
  113 + }
95 break; 114 break;
96 - default: 115 + case AppLifecycleState.detached:
97 break; 116 break;
98 } 117 }
99 } 118 }
100 119
101 @override 120 @override
102 Widget build(BuildContext context) { 121 Widget build(BuildContext context) {
103 - return ValueListenableBuilder(  
104 - valueListenable: controller.startArguments, 122 + return ValueListenableBuilder<MobileScannerArguments?>(
  123 + valueListenable: _controller.startArguments,
105 builder: (context, value, child) { 124 builder: (context, value, child) {
106 - value = value as MobileScannerArguments?;  
107 if (value == null) { 125 if (value == null) {
108 - return const ColoredBox(color: Colors.black);  
109 - } else {  
110 - controller.barcodes.listen((barcode) {  
111 - widget.onDetect(barcode);  
112 - }); 126 + return widget.placeholderBuilder?.call(context, child) ??
  127 + const ColoredBox(color: Colors.black);
  128 + }
  129 +
113 return ClipRect( 130 return ClipRect(
114 - child: SizedBox(  
115 - width: MediaQuery.of(context).size.width,  
116 - height: MediaQuery.of(context).size.height, 131 + child: LayoutBuilder(
  132 + builder: (_, constraints) {
  133 + return SizedBox.fromSize(
  134 + size: constraints.biggest,
117 child: FittedBox( 135 child: FittedBox(
118 fit: widget.fit, 136 fit: widget.fit,
119 child: SizedBox( 137 child: SizedBox(
@@ -124,35 +142,19 @@ class _MobileScannerState extends State<MobileScanner> @@ -124,35 +142,19 @@ class _MobileScannerState extends State<MobileScanner>
124 : Texture(textureId: value.textureId!), 142 : Texture(textureId: value.textureId!),
125 ), 143 ),
126 ), 144 ),
  145 + );
  146 + },
127 ), 147 ),
128 ); 148 );
129 - }  
130 }, 149 },
131 ); 150 );
132 } 151 }
133 152
134 @override 153 @override
135 - void didUpdateWidget(covariant MobileScanner oldWidget) {  
136 - super.didUpdateWidget(oldWidget);  
137 - if (oldWidget.controller == null) {  
138 - if (widget.controller != null) {  
139 - controller.dispose();  
140 - controller = widget.controller!;  
141 - }  
142 - } else {  
143 - if (widget.controller == null) {  
144 - controller =  
145 - MobileScannerController(onPermissionSet: widget.onPermissionSet);  
146 - } else if (oldWidget.controller != widget.controller) {  
147 - controller = widget.controller!;  
148 - }  
149 - }  
150 - }  
151 -  
152 - @override  
153 void dispose() { 154 void dispose() {
154 - controller.dispose();  
155 WidgetsBinding.instance.removeObserver(this); 155 WidgetsBinding.instance.removeObserver(this);
  156 + _barcodesSubscription?.cancel();
  157 + _controller.dispose();
156 super.dispose(); 158 super.dispose();
157 } 159 }
158 } 160 }
@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart'; @@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
6 import 'package:flutter/services.dart'; 6 import 'package:flutter/services.dart';
7 import 'package:mobile_scanner/mobile_scanner.dart'; 7 import 'package:mobile_scanner/mobile_scanner.dart';
8 import 'package:mobile_scanner/src/barcode_utility.dart'; 8 import 'package:mobile_scanner/src/barcode_utility.dart';
9 -import 'package:mobile_scanner/src/mobile_scanner_exception.dart';  
10 9
11 /// The [MobileScannerController] holds all the logic of this plugin, 10 /// The [MobileScannerController] holds all the logic of this plugin,
12 /// where as the [MobileScanner] class is the frontend of this plugin. 11 /// where as the [MobileScanner] class is the frontend of this plugin.
@@ -18,7 +17,9 @@ class MobileScannerController { @@ -18,7 +17,9 @@ class MobileScannerController {
18 this.torchEnabled = false, 17 this.torchEnabled = false,
19 this.formats, 18 this.formats,
20 this.returnImage = false, 19 this.returnImage = false,
  20 + @Deprecated('Instead, use the result of calling `start()` to determine if permissions were granted.')
21 this.onPermissionSet, 21 this.onPermissionSet,
  22 + this.autoStart = true,
22 }) { 23 }) {
23 // In case a new instance is created before calling dispose() 24 // In case a new instance is created before calling dispose()
24 if (controllerHashcode != null) { 25 if (controllerHashcode != null) {
@@ -64,6 +65,9 @@ class MobileScannerController { @@ -64,6 +65,9 @@ class MobileScannerController {
64 /// [DetectionSpeed.normal] (which is the default value). 65 /// [DetectionSpeed.normal] (which is the default value).
65 final int detectionTimeoutMs; 66 final int detectionTimeoutMs;
66 67
  68 + /// Automatically start the mobileScanner on initialization.
  69 + final bool autoStart;
  70 +
67 /// Sets the barcode stream 71 /// Sets the barcode stream
68 final StreamController<BarcodeCapture> _barcodesController = 72 final StreamController<BarcodeCapture> _barcodesController =
69 StreamController.broadcast(); 73 StreamController.broadcast();
@@ -74,6 +78,9 @@ class MobileScannerController { @@ -74,6 +78,9 @@ class MobileScannerController {
74 static const EventChannel _eventChannel = 78 static const EventChannel _eventChannel =
75 EventChannel('dev.steenbakker.mobile_scanner/scanner/event'); 79 EventChannel('dev.steenbakker.mobile_scanner/scanner/event');
76 80
  81 + @Deprecated(
  82 + 'Instead, use the result of calling `start()` to determine if permissions were granted.',
  83 + )
77 Function(bool permissionGranted)? onPermissionSet; 84 Function(bool permissionGranted)? onPermissionSet;
78 85
79 /// Listen to events from the platform specific code 86 /// Listen to events from the platform specific code
@@ -94,6 +101,19 @@ class MobileScannerController { @@ -94,6 +101,19 @@ class MobileScannerController {
94 101
95 bool? _hasTorch; 102 bool? _hasTorch;
96 103
  104 + /// Returns whether the device has a torch.
  105 + ///
  106 + /// Throws an error if the controller is not initialized.
  107 + bool get hasTorch {
  108 + if (_hasTorch == null) {
  109 + throw const MobileScannerException(
  110 + errorCode: MobileScannerErrorCode.controllerUninitialized,
  111 + );
  112 + }
  113 +
  114 + return _hasTorch!;
  115 + }
  116 +
97 /// Set the starting arguments for the camera 117 /// Set the starting arguments for the camera
98 Map<String, dynamic> _argumentsToMap({CameraFacing? cameraFacingOverride}) { 118 Map<String, dynamic> _argumentsToMap({CameraFacing? cameraFacingOverride}) {
99 final Map<String, dynamic> arguments = {}; 119 final Map<String, dynamic> arguments = {};
@@ -115,8 +135,14 @@ class MobileScannerController { @@ -115,8 +135,14 @@ class MobileScannerController {
115 return arguments; 135 return arguments;
116 } 136 }
117 137
118 - /// Start barcode scanning. This will first check if the required permissions  
119 - /// are set. 138 + /// Start scanning for barcodes.
  139 + /// Upon calling this method, the necessary camera permission will be requested.
  140 + ///
  141 + /// Returns an instance of [MobileScannerArguments]
  142 + /// when the scanner was successfully started.
  143 + /// Returns null if the scanner is currently starting.
  144 + ///
  145 + /// Throws a [MobileScannerException] if starting the scanner failed.
120 Future<MobileScannerArguments?> start({ 146 Future<MobileScannerArguments?> start({
121 CameraFacing? cameraFacingOverride, 147 CameraFacing? cameraFacingOverride,
122 }) async { 148 }) async {
@@ -124,6 +150,7 @@ class MobileScannerController { @@ -124,6 +150,7 @@ class MobileScannerController {
124 debugPrint("Called start() while starting."); 150 debugPrint("Called start() while starting.");
125 return null; 151 return null;
126 } 152 }
  153 +
127 isStarting = true; 154 isStarting = true;
128 155
129 // Check authorization status 156 // Check authorization status
@@ -136,16 +163,17 @@ class MobileScannerController { @@ -136,16 +163,17 @@ class MobileScannerController {
136 await _methodChannel.invokeMethod('request') as bool? ?? false; 163 await _methodChannel.invokeMethod('request') as bool? ?? false;
137 if (!result) { 164 if (!result) {
138 isStarting = false; 165 isStarting = false;
139 - onPermissionSet?.call(result);  
140 - throw MobileScannerException('User declined camera permission.'); 166 + throw const MobileScannerException(
  167 + errorCode: MobileScannerErrorCode.permissionDenied,
  168 + );
141 } 169 }
142 break; 170 break;
143 case MobileScannerState.denied: 171 case MobileScannerState.denied:
144 isStarting = false; 172 isStarting = false;
145 - onPermissionSet?.call(false);  
146 - throw MobileScannerException('User declined camera permission.'); 173 + throw const MobileScannerException(
  174 + errorCode: MobileScannerErrorCode.permissionDenied,
  175 + );
147 case MobileScannerState.authorized: 176 case MobileScannerState.authorized:
148 - onPermissionSet?.call(true);  
149 break; 177 break;
150 } 178 }
151 } 179 }
@@ -158,18 +186,27 @@ class MobileScannerController { @@ -158,18 +186,27 @@ class MobileScannerController {
158 _argumentsToMap(cameraFacingOverride: cameraFacingOverride), 186 _argumentsToMap(cameraFacingOverride: cameraFacingOverride),
159 ); 187 );
160 } on PlatformException catch (error) { 188 } on PlatformException catch (error) {
161 - debugPrint('${error.code}: ${error.message}'); 189 + MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError;
  190 +
162 if (error.code == "MobileScannerWeb") { 191 if (error.code == "MobileScannerWeb") {
163 - onPermissionSet?.call(false); 192 + errorCode = MobileScannerErrorCode.permissionDenied;
164 } 193 }
165 isStarting = false; 194 isStarting = false;
166 - return null; 195 +
  196 + throw MobileScannerException(
  197 + errorCode: errorCode,
  198 + errorDetails: MobileScannerErrorDetails(
  199 + code: error.code,
  200 + details: error.details as Object?,
  201 + message: error.message,
  202 + ),
  203 + );
167 } 204 }
168 205
169 if (startResult == null) { 206 if (startResult == null) {
170 isStarting = false; 207 isStarting = false;
171 - throw MobileScannerException(  
172 - 'Failed to start mobileScanner, no response from platform side', 208 + throw const MobileScannerException(
  209 + errorCode: MobileScannerErrorCode.genericError,
173 ); 210 );
174 } 211 }
175 212
@@ -178,13 +215,6 @@ class MobileScannerController { @@ -178,13 +215,6 @@ class MobileScannerController {
178 torchState.value = TorchState.on; 215 torchState.value = TorchState.on;
179 } 216 }
180 217
181 - if (kIsWeb) {  
182 - // If we reach this line, it means camera permission has been granted  
183 - onPermissionSet?.call(  
184 - true,  
185 - );  
186 - }  
187 -  
188 isStarting = false; 218 isStarting = false;
189 return startArguments.value = MobileScannerArguments( 219 return startArguments.value = MobileScannerArguments(
190 size: kIsWeb 220 size: kIsWeb
@@ -210,14 +240,18 @@ class MobileScannerController { @@ -210,14 +240,18 @@ class MobileScannerController {
210 240
211 /// Switches the torch on or off. 241 /// Switches the torch on or off.
212 /// 242 ///
213 - /// Only works if torch is available. 243 + /// Does nothing if the device has no torch.
  244 + ///
  245 + /// Throws if the controller was not initialized.
214 Future<void> toggleTorch() async { 246 Future<void> toggleTorch() async {
215 - if (_hasTorch == null) {  
216 - throw MobileScannerException(  
217 - 'Cannot toggle torch if start() has never been called', 247 + final hasTorch = _hasTorch;
  248 +
  249 + if (hasTorch == null) {
  250 + throw const MobileScannerException(
  251 + errorCode: MobileScannerErrorCode.controllerUninitialized,
218 ); 252 );
219 - } else if (!_hasTorch!) {  
220 - throw MobileScannerException('Device has no torch'); 253 + } else if (!hasTorch) {
  254 + return;
221 } 255 }
222 256
223 torchState.value = 257 torchState.value =
@@ -258,7 +292,6 @@ class MobileScannerController { @@ -258,7 +292,6 @@ class MobileScannerController {
258 _barcodesController.close(); 292 _barcodesController.close();
259 if (hashCode == controllerHashcode) { 293 if (hashCode == controllerHashcode) {
260 controllerHashcode = null; 294 controllerHashcode = null;
261 - onPermissionSet = null;  
262 } 295 }
263 } 296 }
264 297
@@ -307,7 +340,10 @@ class MobileScannerController { @@ -307,7 +340,10 @@ class MobileScannerController {
307 ); 340 );
308 break; 341 break;
309 case 'error': 342 case 'error':
310 - throw MobileScannerException(data as String); 343 + throw MobileScannerException(
  344 + errorCode: MobileScannerErrorCode.genericError,
  345 + errorDetails: MobileScannerErrorDetails(message: data as String?),
  346 + );
311 default: 347 default:
312 throw UnimplementedError(name as String?); 348 throw UnimplementedError(name as String?);
313 } 349 }
  1 +import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
  2 +
  3 +/// This class represents an exception thrown by the mobile scanner.
1 class MobileScannerException implements Exception { 4 class MobileScannerException implements Exception {
2 - String message;  
3 - MobileScannerException(this.message); 5 + const MobileScannerException({
  6 + required this.errorCode,
  7 + this.errorDetails,
  8 + });
  9 +
  10 + /// The error code of the exception.
  11 + final MobileScannerErrorCode errorCode;
  12 +
  13 + /// The additional error details that came with the [errorCode].
  14 + final MobileScannerErrorDetails? errorDetails;
  15 +
  16 + @override
  17 + String toString() {
  18 + if (errorDetails != null && errorDetails?.message != null) {
  19 + return "MobileScannerException: code ${errorCode.name}, message: ${errorDetails?.message}";
  20 + }
  21 + return "MobileScannerException: ${errorCode.name}";
  22 + }
  23 +}
  24 +
  25 +/// The raw error details for a [MobileScannerException].
  26 +class MobileScannerErrorDetails {
  27 + const MobileScannerErrorDetails({
  28 + this.code,
  29 + this.details,
  30 + this.message,
  31 + });
  32 +
  33 + /// The error code from the [PlatformException].
  34 + final String? code;
  35 +
  36 + /// The details from the [PlatformException].
  37 + final Object? details;
  38 +
  39 + /// The error message from the [PlatformException].
  40 + final String? message;
4 } 41 }
@@ -238,9 +238,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -238,9 +238,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
238 238
239 func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 239 func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
240 if (device == nil) { 240 if (device == nil) {
241 - result(FlutterError(code: "MobileScanner",  
242 - message: "Called toggleTorch() while stopped!",  
243 - details: nil)) 241 + result(nil)
244 return 242 return
245 } 243 }
246 do { 244 do {
@@ -260,9 +258,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -260,9 +258,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
260 258
261 func stop(_ result: FlutterResult) { 259 func stop(_ result: FlutterResult) {
262 if (device == nil) { 260 if (device == nil) {
263 - result(FlutterError(code: "MobileScanner",  
264 - message: "Called stop() while already stopped!",  
265 - details: nil)) 261 + result(nil)
  262 +
266 return 263 return
267 } 264 }
268 captureSession.stopRunning() 265 captureSession.stopRunning()
1 name: mobile_scanner 1 name: mobile_scanner
2 description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS. 2 description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS.
3 -version: 3.0.0-beta.2 3 +version: 3.0.0-beta.3
4 repository: https://github.com/juliansteenbakker/mobile_scanner 4 repository: https://github.com/juliansteenbakker/mobile_scanner
5 5
6 environment: 6 environment: