Julian Steenbakker

bug: fix black screen on pageview and scanwindow not being reverted.

@@ -19,23 +19,6 @@ import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters @@ -19,23 +19,6 @@ import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters
19 import io.flutter.view.TextureRegistry 19 import io.flutter.view.TextureRegistry
20 import kotlin.math.roundToInt 20 import kotlin.math.roundToInt
21 21
22 -  
23 -typealias MobileScannerCallback = (barcodes: List<Map<String, Any?>>, image: ByteArray?, width: Int?, height: Int?) -> Unit  
24 -typealias AnalyzerCallback = (barcodes: List<Map<String, Any?>>?) -> Unit  
25 -typealias MobileScannerErrorCallback = (error: String) -> Unit  
26 -typealias TorchStateCallback = (state: Int) -> Unit  
27 -typealias MobileScannerStartedCallback = (parameters: MobileScannerStartParameters) -> Unit  
28 -  
29 -  
30 -class NoCamera : Exception()  
31 -class AlreadyStarted : Exception()  
32 -class AlreadyStopped : Exception()  
33 -class TorchError : Exception()  
34 -class CameraError : Exception()  
35 -class TorchWhenStopped : Exception()  
36 -class ZoomWhenStopped : Exception()  
37 -class ZoomNotInRange : Exception()  
38 -  
39 class MobileScanner( 22 class MobileScanner(
40 private val activity: Activity, 23 private val activity: Activity,
41 private val textureRegistry: TextureRegistry, 24 private val textureRegistry: TextureRegistry,
@@ -43,22 +26,21 @@ class MobileScanner( @@ -43,22 +26,21 @@ class MobileScanner(
43 private val mobileScannerErrorCallback: MobileScannerErrorCallback 26 private val mobileScannerErrorCallback: MobileScannerErrorCallback
44 ) { 27 ) {
45 28
  29 + /// Internal variables
46 private var cameraProvider: ProcessCameraProvider? = null 30 private var cameraProvider: ProcessCameraProvider? = null
47 private var camera: Camera? = null 31 private var camera: Camera? = null
48 private var preview: Preview? = null 32 private var preview: Preview? = null
49 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null 33 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
50 - var scanWindow: List<Float>? = null  
51 -  
52 - private var detectionSpeed: DetectionSpeed = DetectionSpeed.NO_DUPLICATES  
53 - private var detectionTimeout: Long = 250 34 + private var scanner = BarcodeScanning.getClient()
54 private var lastScanned: List<String?>? = null 35 private var lastScanned: List<String?>? = null
55 -  
56 private var scannerTimeout = false 36 private var scannerTimeout = false
57 37
  38 + /// Configurable variables
  39 + var scanWindow: List<Float>? = null
  40 + private var detectionSpeed: DetectionSpeed = DetectionSpeed.NO_DUPLICATES
  41 + private var detectionTimeout: Long = 250
58 private var returnImage = false 42 private var returnImage = false
59 43
60 - private var scanner = BarcodeScanning.getClient()  
61 -  
62 /** 44 /**
63 * callback for the camera. Every frame is passed through this function. 45 * callback for the camera. Every frame is passed through this function.
64 */ 46 */
@@ -87,10 +69,10 @@ class MobileScanner( @@ -87,10 +69,10 @@ class MobileScanner(
87 69
88 val barcodeMap: MutableList<Map<String, Any?>> = mutableListOf() 70 val barcodeMap: MutableList<Map<String, Any?>> = mutableListOf()
89 71
90 - for ( barcode in barcodes) {  
91 - if(scanWindow != null) {  
92 - val match = isbarCodeInScanWindow(scanWindow!!, barcode, imageProxy)  
93 - if(!match) { 72 + for (barcode in barcodes) {
  73 + if (scanWindow != null) {
  74 + val match = isBarcodeInScanWindow(scanWindow!!, barcode, imageProxy)
  75 + if (!match) {
94 continue 76 continue
95 } else { 77 } else {
96 barcodeMap.add(barcode.data) 78 barcodeMap.add(barcode.data)
@@ -126,7 +108,11 @@ class MobileScanner( @@ -126,7 +108,11 @@ class MobileScanner(
126 108
127 // scales the scanWindow to the provided inputImage and checks if that scaled 109 // scales the scanWindow to the provided inputImage and checks if that scaled
128 // scanWindow contains the barcode 110 // scanWindow contains the barcode
129 - private fun isbarCodeInScanWindow(scanWindow: List<Float>, barcode: Barcode, inputImage: ImageProxy): Boolean { 111 + private fun isBarcodeInScanWindow(
  112 + scanWindow: List<Float>,
  113 + barcode: Barcode,
  114 + inputImage: ImageProxy
  115 + ): Boolean {
130 val barcodeBoundingBox = barcode.boundingBox ?: return false 116 val barcodeBoundingBox = barcode.boundingBox ?: return false
131 117
132 val imageWidth = inputImage.height 118 val imageWidth = inputImage.height
  1 +package dev.steenbakker.mobile_scanner
  2 +
  3 +import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters
  4 +
  5 +typealias MobileScannerCallback = (barcodes: List<Map<String, Any?>>, image: ByteArray?, width: Int?, height: Int?) -> Unit
  6 +typealias AnalyzerCallback = (barcodes: List<Map<String, Any?>>?) -> Unit
  7 +typealias MobileScannerErrorCallback = (error: String) -> Unit
  8 +typealias TorchStateCallback = (state: Int) -> Unit
  9 +typealias MobileScannerStartedCallback = (parameters: MobileScannerStartParameters) -> Unit
  1 +package dev.steenbakker.mobile_scanner
  2 +
  3 +class NoCamera : Exception()
  4 +class AlreadyStarted : Exception()
  5 +class AlreadyStopped : Exception()
  6 +class TorchError : Exception()
  7 +class CameraError : Exception()
  8 +class TorchWhenStopped : Exception()
  9 +class ZoomWhenStopped : Exception()
  10 +class ZoomNotInRange : Exception()
@@ -230,6 +230,6 @@ class MobileScannerHandler( @@ -230,6 +230,6 @@ class MobileScannerHandler(
230 } 230 }
231 231
232 private fun updateScanWindow(call: MethodCall) { 232 private fun updateScanWindow(call: MethodCall) {
233 - mobileScanner!!.scanWindow = call.argument<List<Float>>("rect") 233 + mobileScanner!!.scanWindow = call.argument<List<Float>?>("rect")
234 } 234 }
235 } 235 }
@@ -56,6 +56,12 @@ class MobileScanner extends StatefulWidget { @@ -56,6 +56,12 @@ class MobileScanner extends StatefulWidget {
56 /// [BoxFit] 56 /// [BoxFit]
57 final Rect? scanWindow; 57 final Rect? scanWindow;
58 58
  59 + /// Only set this to true if you are starting another instance of mobile_scanner
  60 + /// right after disposing the first one, like in a PageView.
  61 + ///
  62 + /// Default: false
  63 + final bool startDelay;
  64 +
59 /// Create a new [MobileScanner] using the provided [controller] 65 /// Create a new [MobileScanner] using the provided [controller]
60 /// and [onBarcodeDetected] callback. 66 /// and [onBarcodeDetected] callback.
61 const MobileScanner({ 67 const MobileScanner({
@@ -67,6 +73,7 @@ class MobileScanner extends StatefulWidget { @@ -67,6 +73,7 @@ class MobileScanner extends StatefulWidget {
67 this.onScannerStarted, 73 this.onScannerStarted,
68 this.placeholderBuilder, 74 this.placeholderBuilder,
69 this.scanWindow, 75 this.scanWindow,
  76 + this.startDelay = false,
70 super.key, 77 super.key,
71 }); 78 });
72 79
@@ -88,7 +95,7 @@ class _MobileScannerState extends State<MobileScanner> @@ -88,7 +95,7 @@ class _MobileScannerState extends State<MobileScanner>
88 95
89 MobileScannerException? _startException; 96 MobileScannerException? _startException;
90 97
91 - Widget __buildPlaceholderOrError(BuildContext context, Widget? child) { 98 + Widget _buildPlaceholderOrError(BuildContext context, Widget? child) {
92 final error = _startException; 99 final error = _startException;
93 100
94 if (error != null) { 101 if (error != null) {
@@ -104,18 +111,28 @@ class _MobileScannerState extends State<MobileScanner> @@ -104,18 +111,28 @@ class _MobileScannerState extends State<MobileScanner>
104 } 111 }
105 112
106 /// Start the given [scanner]. 113 /// Start the given [scanner].
107 - void _startScanner(MobileScannerController scanner) { 114 + Future<void> _startScanner() async {
  115 + if (widget.startDelay) {
  116 + await Future.delayed(const Duration(seconds: 1, milliseconds: 500));
  117 + }
  118 +
108 if (!_controller.autoStart) { 119 if (!_controller.autoStart) {
109 debugPrint( 120 debugPrint(
110 'mobile_scanner: not starting automatically because autoStart is set to false in the controller.', 121 'mobile_scanner: not starting automatically because autoStart is set to false in the controller.',
111 ); 122 );
112 return; 123 return;
113 } 124 }
114 - scanner.start().then((arguments) { 125 +
  126 + _barcodesSubscription = _controller.barcodes.listen(
  127 + widget.onDetect,
  128 + );
  129 +
  130 + _controller.start().then((arguments) {
115 // ignore: deprecated_member_use_from_same_package 131 // ignore: deprecated_member_use_from_same_package
116 widget.onStart?.call(arguments); 132 widget.onStart?.call(arguments);
117 widget.onScannerStarted?.call(arguments); 133 widget.onScannerStarted?.call(arguments);
118 }).catchError((error) { 134 }).catchError((error) {
  135 + debugPrint('mobile_scanner: $error');
119 if (mounted) { 136 if (mounted) {
120 setState(() { 137 setState(() {
121 _startException = error as MobileScannerException; 138 _startException = error as MobileScannerException;
@@ -129,14 +146,7 @@ class _MobileScannerState extends State<MobileScanner> @@ -129,14 +146,7 @@ class _MobileScannerState extends State<MobileScanner>
129 super.initState(); 146 super.initState();
130 WidgetsBinding.instance.addObserver(this); 147 WidgetsBinding.instance.addObserver(this);
131 _controller = widget.controller ?? MobileScannerController(); 148 _controller = widget.controller ?? MobileScannerController();
132 -  
133 - _barcodesSubscription = _controller.barcodes.listen(  
134 - widget.onDetect,  
135 - );  
136 -  
137 - if (!_controller.isStarting) {  
138 - _startScanner(_controller);  
139 - } 149 + _startScanner();
140 } 150 }
141 151
142 @override 152 @override
@@ -149,7 +159,7 @@ class _MobileScannerState extends State<MobileScanner> @@ -149,7 +159,7 @@ class _MobileScannerState extends State<MobileScanner>
149 switch (state) { 159 switch (state) {
150 case AppLifecycleState.resumed: 160 case AppLifecycleState.resumed:
151 _resumeFromBackground = false; 161 _resumeFromBackground = false;
152 - _startScanner(_controller); 162 + _startScanner();
153 break; 163 break;
154 case AppLifecycleState.paused: 164 case AppLifecycleState.paused:
155 _resumeFromBackground = true; 165 _resumeFromBackground = true;
@@ -228,7 +238,7 @@ class _MobileScannerState extends State<MobileScanner> @@ -228,7 +238,7 @@ class _MobileScannerState extends State<MobileScanner>
228 valueListenable: _controller.startArguments, 238 valueListenable: _controller.startArguments,
229 builder: (context, value, child) { 239 builder: (context, value, child) {
230 if (value == null) { 240 if (value == null) {
231 - return __buildPlaceholderOrError(context, child); 241 + return _buildPlaceholderOrError(context, child);
232 } 242 }
233 243
234 if (widget.scanWindow != null && scanWindow == null) { 244 if (widget.scanWindow != null && scanWindow == null) {
@@ -238,6 +248,7 @@ class _MobileScannerState extends State<MobileScanner> @@ -238,6 +248,7 @@ class _MobileScannerState extends State<MobileScanner>
238 value.size, 248 value.size,
239 Size(constraints.maxWidth, constraints.maxHeight), 249 Size(constraints.maxWidth, constraints.maxHeight),
240 ); 250 );
  251 +
241 _controller.updateScanWindow(scanWindow!); 252 _controller.updateScanWindow(scanWindow!);
242 } 253 }
243 254
@@ -268,8 +279,10 @@ class _MobileScannerState extends State<MobileScanner> @@ -268,8 +279,10 @@ class _MobileScannerState extends State<MobileScanner>
268 279
269 @override 280 @override
270 void dispose() { 281 void dispose() {
  282 + _controller.updateScanWindow(null);
271 WidgetsBinding.instance.removeObserver(this); 283 WidgetsBinding.instance.removeObserver(this);
272 _barcodesSubscription?.cancel(); 284 _barcodesSubscription?.cancel();
  285 + _barcodesSubscription = null;
273 _controller.dispose(); 286 _controller.dispose();
274 super.dispose(); 287 super.dispose();
275 } 288 }
@@ -20,20 +20,7 @@ class MobileScannerController { @@ -20,20 +20,7 @@ class MobileScannerController {
20 @Deprecated('Instead, use the result of calling `start()` to determine if permissions were granted.') 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 this.autoStart = true,
23 - }) {  
24 - // In case a new instance is created before calling dispose()  
25 - if (controllerHashcode != null) {  
26 - stop();  
27 - }  
28 - controllerHashcode = hashCode;  
29 - events = _eventChannel  
30 - .receiveBroadcastStream()  
31 - .listen((data) => _handleEvent(data as Map));  
32 - }  
33 -  
34 - /// The hashcode of the controller to check if the correct object is mounted.  
35 - /// Must be static to keep the same value on new instances  
36 - static int? controllerHashcode; 23 + });
37 24
38 /// Select which camera should be used. 25 /// Select which camera should be used.
39 /// 26 ///
@@ -84,7 +71,7 @@ class MobileScannerController { @@ -84,7 +71,7 @@ class MobileScannerController {
84 Function(bool permissionGranted)? onPermissionSet; 71 Function(bool permissionGranted)? onPermissionSet;
85 72
86 /// Listen to events from the platform specific code 73 /// Listen to events from the platform specific code
87 - late StreamSubscription events; 74 + StreamSubscription? events;
88 75
89 /// A notifier that provides several arguments about the MobileScanner 76 /// A notifier that provides several arguments about the MobileScanner
90 final ValueNotifier<MobileScannerArguments?> startArguments = 77 final ValueNotifier<MobileScannerArguments?> startArguments =
@@ -164,6 +151,11 @@ class MobileScannerController { @@ -164,6 +151,11 @@ class MobileScannerController {
164 151
165 isStarting = true; 152 isStarting = true;
166 153
  154 + events?.cancel();
  155 + events = _eventChannel
  156 + .receiveBroadcastStream()
  157 + .listen((data) => _handleEvent(data as Map));
  158 +
167 // Check authorization status 159 // Check authorization status
168 if (!kIsWeb) { 160 if (!kIsWeb) {
169 final MobileScannerState state = MobileScannerState 161 final MobileScannerState state = MobileScannerState
@@ -327,11 +319,8 @@ class MobileScannerController { @@ -327,11 +319,8 @@ class MobileScannerController {
327 /// If you call this, you cannot use this controller object anymore. 319 /// If you call this, you cannot use this controller object anymore.
328 void dispose() { 320 void dispose() {
329 stop(); 321 stop();
330 - events.cancel(); 322 + events?.cancel();
331 _barcodesController.close(); 323 _barcodesController.close();
332 - if (hashCode == controllerHashcode) {  
333 - controllerHashcode = null;  
334 - }  
335 } 324 }
336 325
337 /// Handles a returning event from the platform side 326 /// Handles a returning event from the platform side
@@ -394,9 +383,13 @@ class MobileScannerController { @@ -394,9 +383,13 @@ class MobileScannerController {
394 } 383 }
395 } 384 }
396 385
397 - /// updates the native scanwindow  
398 - Future<void> updateScanWindow(Rect window) async {  
399 - final data = [window.left, window.top, window.right, window.bottom]; 386 + /// updates the native ScanWindow
  387 + Future<void> updateScanWindow(Rect? window) async {
  388 + List? data;
  389 + if (window != null) {
  390 + data = [window.left, window.top, window.right, window.bottom];
  391 + }
  392 +
400 await _methodChannel.invokeMethod('updateScanWindow', {'rect': data}); 393 await _methodChannel.invokeMethod('updateScanWindow', {'rect': data});
401 } 394 }
402 } 395 }