From d19f1de1da7e1a422914792a4bcc78cf9fe0463b Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 30 Apr 2024 15:00:20 +0200 Subject: [PATCH 01/30] remove old lifecycle bug workaround for web --- lib/mobile_scanner.dart | 3 +-- lib/src/mobile_scanner_controller.dart | 9 -------- lib/src/mobile_scanner_exception.dart | 7 ------ lib/src/web/mobile_scanner_web.dart | 32 ++++++-------------------- 4 files changed, 8 insertions(+), 43 deletions(-) diff --git a/lib/mobile_scanner.dart b/lib/mobile_scanner.dart index d5bd591ef..347a90993 100644 --- a/lib/mobile_scanner.dart +++ b/lib/mobile_scanner.dart @@ -11,8 +11,7 @@ export 'src/enums/phone_type.dart'; export 'src/enums/torch_state.dart'; export 'src/mobile_scanner.dart'; export 'src/mobile_scanner_controller.dart'; -export 'src/mobile_scanner_exception.dart' - hide PermissionRequestPendingException; +export 'src/mobile_scanner_exception.dart'; export 'src/mobile_scanner_platform_interface.dart'; export 'src/objects/address.dart'; export 'src/objects/barcode.dart'; diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index 68efb1cc8..52b50c413 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -246,13 +246,6 @@ class MobileScannerController extends ValueNotifier { ); } - // Permission was denied, do nothing. - // When the controller is stopped, - // the error is reset so the permission can be requested again if possible. - if (value.error?.errorCode == MobileScannerErrorCode.permissionDenied) { - return; - } - // Do nothing if the camera is already running. if (value.isRunning) { return; @@ -306,8 +299,6 @@ class MobileScannerController extends ValueNotifier { zoomScale: 1.0, ); } - } on PermissionRequestPendingException catch (_) { - // If a permission request was already pending, do nothing. } } diff --git a/lib/src/mobile_scanner_exception.dart b/lib/src/mobile_scanner_exception.dart index 879403cd2..52fac113f 100644 --- a/lib/src/mobile_scanner_exception.dart +++ b/lib/src/mobile_scanner_exception.dart @@ -40,13 +40,6 @@ class MobileScannerErrorDetails { final String? message; } -/// This class represents an exception that is thrown -/// when the scanner was (re)started while a permission request was pending. -/// -/// This exception type is only used internally, -/// and is not part of the public API. -class PermissionRequestPendingException implements Exception {} - /// This class represents an exception thrown by the [MobileScannerController] /// when a barcode scanning error occurs when processing an input frame. class MobileScannerBarcodeException implements Exception { diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 32fce5bf4..c42609d82 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -39,12 +39,6 @@ class MobileScannerWeb extends MobileScannerPlatform { /// The container div element for the camera view. late HTMLDivElement _divElement; - /// The flag that keeps track of whether a permission request is in progress. - /// - /// On the web, a permission request triggers a dialog, that in turn triggers a lifecycle change. - /// While the permission request is in progress, any attempts at (re)starting the camera should be ignored. - bool _permissionRequestInProgress = false; - /// The stream controller for the media track settings stream. /// /// Currently, only the facing mode setting can be supported, @@ -199,17 +193,12 @@ class MobileScannerWeb extends MobileScannerPlatform { } try { - _permissionRequestInProgress = true; - // Retrieving the media devices requests the camera permission. final MediaStream videoStream = await window.navigator.mediaDevices.getUserMedia(constraints).toDart; - _permissionRequestInProgress = false; - return videoStream; } on DOMException catch (error, stackTrace) { - _permissionRequestInProgress = false; final String errorMessage = error.toString(); MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError; @@ -272,20 +261,7 @@ class MobileScannerWeb extends MobileScannerPlatform { @override Future start(StartOptions startOptions) async { - // If the permission request has not yet completed, - // the camera view is not ready yet. - // Prevent the permission popup from triggering a restart of the scanner. - if (_permissionRequestInProgress) { - throw PermissionRequestPendingException(); - } - - _barcodeReader = ZXingBarcodeReader(); - - await _barcodeReader?.maybeLoadLibrary( - alternateScriptUrl: _alternateScriptUrl, - ); - - if (_barcodeReader?.isScanning ?? false) { + if (_barcodeReader != null) { throw const MobileScannerException( errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, errorDetails: MobileScannerErrorDetails( @@ -295,6 +271,12 @@ class MobileScannerWeb extends MobileScannerPlatform { ); } + _barcodeReader = ZXingBarcodeReader(); + + await _barcodeReader?.maybeLoadLibrary( + alternateScriptUrl: _alternateScriptUrl, + ); + // Request camera permissions and prepare the video stream. final MediaStream videoStream = await _prepareVideoStream( startOptions.cameraDirection, From c816b39765e8eb91a1ed49e08a1fe8b467ff722b Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 30 Apr 2024 15:06:09 +0200 Subject: [PATCH 02/30] shorten an error message --- lib/src/web/mobile_scanner_web.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index c42609d82..78190197d 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -265,8 +265,7 @@ class MobileScannerWeb extends MobileScannerPlatform { throw const MobileScannerException( errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, errorDetails: MobileScannerErrorDetails( - message: - 'The scanner was already started. Call stop() before calling start() again.', + message: 'The scanner was already started.', ), ); } From 47534aec2ebf0d117cf972984748f01769f48668 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 30 Apr 2024 15:16:03 +0200 Subject: [PATCH 03/30] ignore the already initialized error case --- lib/src/mobile_scanner_controller.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index 52b50c413..657542a80 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -285,6 +285,13 @@ class MobileScannerController extends ValueNotifier { ); } } on MobileScannerException catch (error) { + // If the controller is already initialized, ignore the error. + // Starting the controller while it is already started, or in the process of starting, is redundant. + if (error.errorCode == + MobileScannerErrorCode.controllerAlreadyInitialized) { + return; + } + // The initialization finished with an error. // To avoid stale values, reset the output size, // torch state and zoom scale to the defaults. From 5b80d2c7475c4351ac0765303b27ea49d0d330b1 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Fri, 17 May 2024 12:53:41 +0200 Subject: [PATCH 04/30] unify the error codes on Android --- .../mobile_scanner/MobileScannerHandler.kt | 42 ++++++++++--------- .../MobileScannerPermissions.kt | 17 +++----- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt index 15bda1cde..b228c0d0f 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt @@ -10,6 +10,7 @@ import androidx.camera.core.ExperimentalGetImage import com.google.mlkit.vision.barcode.BarcodeScannerOptions import dev.steenbakker.mobile_scanner.objects.BarcodeFormats import dev.steenbakker.mobile_scanner.objects.DetectionSpeed +import dev.steenbakker.mobile_scanner.objects.MobileScannerErrorCodes import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -28,7 +29,7 @@ class MobileScannerHandler( private val analyzeImageErrorCallback: AnalyzerErrorCallback = { Handler(Looper.getMainLooper()).post { - analyzerResult?.error("MobileScanner", it, null) + analyzerResult?.error(MobileScannerErrorCodes.GENERIC_ERROR, it, null) analyzerResult = null } } @@ -106,21 +107,21 @@ class MobileScannerHandler( @ExperimentalGetImage override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - if (mobileScanner == null) { - result.error("MobileScanner", "Called ${call.method} before initializing.", null) - return - } when (call.method) { "state" -> result.success(permissions.hasCameraPermission(activity)) "request" -> permissions.requestPermission( activity, addPermissionListener, object: MobileScannerPermissions.ResultCallback { - override fun onResult(errorCode: String?, errorDescription: String?) { + override fun onResult(errorCode: String?) { when(errorCode) { null -> result.success(true) - MobileScannerPermissions.CAMERA_ACCESS_DENIED -> result.success(false) - else -> result.error(errorCode, errorDescription, null) + MobileScannerErrorCodes.CAMERA_ACCESS_DENIED -> result.success(false) + MobileScannerErrorCodes.CAMERA_PERMISSIONS_REQUEST_ONGOING -> result.error( + MobileScannerErrorCodes.CAMERA_PERMISSIONS_REQUEST_ONGOING, + MobileScannerErrorCodes.CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE, null) + else -> result.error( + MobileScannerErrorCodes.GENERIC_ERROR, MobileScannerErrorCodes.GENERIC_ERROR_MESSAGE, null) } } }) @@ -185,29 +186,29 @@ class MobileScannerHandler( when (it) { is AlreadyStarted -> { result.error( - "MobileScanner", - "Called start() while already started", + MobileScannerErrorCodes.ALREADY_STARTED_ERROR, + MobileScannerErrorCodes.ALREADY_STARTED_ERROR_MESSAGE, null ) } is CameraError -> { result.error( - "MobileScanner", - "Error occurred when setting up camera!", + MobileScannerErrorCodes.CAMERA_ERROR, + MobileScannerErrorCodes.CAMERA_ERROR_MESSAGE, null ) } is NoCamera -> { result.error( - "MobileScanner", - "No camera found or failed to open camera!", + MobileScannerErrorCodes.NO_CAMERA_ERROR, + MobileScannerErrorCodes.NO_CAMERA_ERROR_MESSAGE, null ) } else -> { result.error( - "MobileScanner", - "Unknown error occurred.", + MobileScannerErrorCodes.GENERIC_ERROR, + MobileScannerErrorCodes.GENERIC_ERROR_MESSAGE, null ) } @@ -252,9 +253,11 @@ class MobileScannerHandler( mobileScanner!!.setScale(call.arguments as Double) result.success(null) } catch (e: ZoomWhenStopped) { - result.error("MobileScanner", "Called setScale() while stopped!", null) + result.error( + MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR, MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE, null) } catch (e: ZoomNotInRange) { - result.error("MobileScanner", "Scale should be within 0 and 1", null) + result.error( + MobileScannerErrorCodes.GENERIC_ERROR, MobileScannerErrorCodes.INVALID_ZOOM_SCALE_ERROR_MESSAGE, null) } } @@ -263,7 +266,8 @@ class MobileScannerHandler( mobileScanner!!.resetScale() result.success(null) } catch (e: ZoomWhenStopped) { - result.error("MobileScanner", "Called resetScale() while stopped!", null) + result.error( + MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR, MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE, null) } } diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerPermissions.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerPermissions.kt index 9d1f1ac70..59614250f 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerPermissions.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerPermissions.kt @@ -5,6 +5,7 @@ import android.app.Activity import android.content.pm.PackageManager import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import dev.steenbakker.mobile_scanner.objects.MobileScannerErrorCodes import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener /** @@ -12,11 +13,6 @@ import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener */ class MobileScannerPermissions { companion object { - const val CAMERA_ACCESS_DENIED = "CameraAccessDenied" - const val CAMERA_ACCESS_DENIED_MESSAGE = "Camera access permission was denied." - const val CAMERA_PERMISSIONS_REQUEST_ONGOING = "CameraPermissionsRequestOngoing" - const val CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE = "Another request is ongoing and multiple requests cannot be handled at once." - /** * When the application's activity is [androidx.fragment.app.FragmentActivity], requestCode can only use the lower 16 bits. * @see androidx.fragment.app.FragmentActivity.validateRequestPermissionsRequestCode @@ -25,7 +21,7 @@ class MobileScannerPermissions { } interface ResultCallback { - fun onResult(errorCode: String?, errorDescription: String?) + fun onResult(errorCode: String?) } private var listener: RequestPermissionsResultListener? = null @@ -53,14 +49,13 @@ class MobileScannerPermissions { addPermissionListener: (RequestPermissionsResultListener) -> Unit, callback: ResultCallback) { if (ongoing) { - callback.onResult( - CAMERA_PERMISSIONS_REQUEST_ONGOING, CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE) + callback.onResult(MobileScannerErrorCodes.CAMERA_PERMISSIONS_REQUEST_ONGOING) return } if(hasCameraPermission(activity) == 1) { // Permissions already exist. Call the callback with success. - callback.onResult(null, null) + callback.onResult(null) return } @@ -68,10 +63,10 @@ class MobileScannerPermissions { // Keep track of the listener, so that it can be unregistered later. listener = MobileScannerPermissionsListener( object: ResultCallback { - override fun onResult(errorCode: String?, errorDescription: String?) { + override fun onResult(errorCode: String?) { ongoing = false listener = null - callback.onResult(errorCode, errorDescription) + callback.onResult(errorCode) } } ) From 14f534651d38758fde8c6f6a2b654f8a8a17a953 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 27 May 2024 10:52:30 +0200 Subject: [PATCH 05/30] move barcode error code to constants on Android --- .../dev/steenbakker/mobile_scanner/MobileScannerHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt index b228c0d0f..353c091eb 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt @@ -67,7 +67,7 @@ class MobileScannerHandler( private val errorCallback: MobileScannerErrorCallback = {error: String -> barcodeHandler.publishEvent(mapOf( - "name" to "error", + "name" to MobileScannerErrorCodes.BARCODE_ERROR, "data" to error, )) } From f81bb29a9e6a08a0bf1ee5682083d909e86e0c19 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 27 May 2024 11:00:25 +0200 Subject: [PATCH 06/30] unify MacOS error codes for FlutterError(); fix barcode error forat and don't send a FlutterError through the sink --- .../MobileScannerErrorCodes.swift | 2 +- .../mobile_scanner/MobileScannerPlugin.swift | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerErrorCodes.swift b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerErrorCodes.swift index 821ce3c42..3c16d9587 100644 --- a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerErrorCodes.swift +++ b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerErrorCodes.swift @@ -14,7 +14,7 @@ struct MobileScannerErrorCodes { // because it uses the error message from the undelying error. static let BARCODE_ERROR = "MOBILE_SCANNER_BARCODE_ERROR" // The error code 'CAMERA_ERROR' does not have an error message, - // because it uses the error message from the underlying error. + // because it uses the error message from the underlying error. static let CAMERA_ERROR = "MOBILE_SCANNER_CAMERA_ERROR" static let NO_CAMERA_ERROR = "MOBILE_SCANNER_NO_CAMERA_ERROR" static let NO_CAMERA_ERROR_MESSAGE = "No cameras available." diff --git a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index 349b41af4..42d583cc3 100644 --- a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -131,7 +131,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, if error != nil { DispatchQueue.main.async { - self?.sink?(FlutterError(code: "MobileScanner", message: error?.localizedDescription, details: nil)) + self?.sink?([ + "name": MobileScannerErrorCodes.BARCODE_ERROR, + "data": error?.localizedDescription, + ]) } return } @@ -180,9 +183,12 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, } try imageRequestHandler.perform([barcodeRequest]) - } catch let e { + } catch let error { DispatchQueue.main.async { - self?.sink?(FlutterError(code: "MobileScanner", message: e.localizedDescription, details: nil)) + self?.sink?([ + "name": MobileScannerErrorCodes.BARCODE_ERROR, + "data": error.localizedDescription, + ]) } } } @@ -262,8 +268,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { if (device != nil || captureSession != nil) { - result(FlutterError(code: "MobileScanner", - message: "Called start() while already started!", + result(FlutterError(code: MobileScannerErrorCodes.ALREADY_STARTED_ERROR, + message: MobileScannerErrorCodes.ALREADY_STARTED_ERROR_MESSAGE, details: nil)) return } @@ -294,8 +300,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, } if (device == nil) { - result(FlutterError(code: "MobileScanner", - message: "No camera found or failed to open camera!", + result(FlutterError(code: MobileScannerErrorCodes.NO_CAMERA_ERROR, + message: MobileScannerErrorCodes.NO_CAMERA_ERROR_MESSAGE, details: nil)) return } @@ -313,7 +319,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, let input = try AVCaptureDeviceInput(device: device) captureSession!.addInput(input) } catch { - result(FlutterError(code: "MobileScanner", message: error.localizedDescription, details: nil)) + result(FlutterError( + code: MobileScannerErrorCodes.CAMERA_ERROR, + message: error.localizedDescription, details: nil)) return } captureSession!.sessionPreset = AVCaptureSession.Preset.photo From 8236ee6bef03d500a831d92418db0720ddac63fd Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Fri, 31 May 2024 15:27:27 +0200 Subject: [PATCH 07/30] port error codes to iOS as well --- ios/Classes/MobileScannerErrorCodes.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Classes/MobileScannerErrorCodes.swift b/ios/Classes/MobileScannerErrorCodes.swift index d70f53390..e5eb64e6b 100644 --- a/ios/Classes/MobileScannerErrorCodes.swift +++ b/ios/Classes/MobileScannerErrorCodes.swift @@ -20,7 +20,7 @@ struct MobileScannerErrorCodes { // because it uses the error message from the undelying error. static let BARCODE_ERROR = "MOBILE_SCANNER_BARCODE_ERROR" // The error code 'CAMERA_ERROR' does not have an error message, - // because it uses the error message from the underlying error. + // because it uses the error message from the underlying error. static let CAMERA_ERROR = "MOBILE_SCANNER_CAMERA_ERROR" static let GENERIC_ERROR = "MOBILE_SCANNER_GENERIC_ERROR" static let GENERIC_ERROR_MESSAGE = "An unknown error occurred." From d1ac1192ff5032a1877ded477b9eafecbe890621 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Fri, 31 May 2024 15:48:44 +0200 Subject: [PATCH 08/30] use well defined error code for barcode errors on iOS --- ios/Classes/MobileScannerPlugin.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ios/Classes/MobileScannerPlugin.swift b/ios/Classes/MobileScannerPlugin.swift index 2b1f2a9b0..e9b768bfc 100644 --- a/ios/Classes/MobileScannerPlugin.swift +++ b/ios/Classes/MobileScannerPlugin.swift @@ -42,7 +42,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { init(barcodeHandler: BarcodeHandler, registry: FlutterTextureRegistry) { self.mobileScanner = MobileScanner(registry: registry, mobileScannerCallback: { barcodes, error, image in if error != nil { - barcodeHandler.publishEvent(["name": "error", "data": error!.localizedDescription]) + barcodeHandler.publishEvent([ + "name": MobileScannerErrorCodes.BARCODE_ERROR, + "data": error!.localizedDescription, + ]) return } From dbd5250878880833c6a23e0137d95fd02f2b37bc Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Fri, 31 May 2024 15:55:50 +0200 Subject: [PATCH 09/30] refactor iOS impl to use well formatted error codes & messages for FlutterError --- ios/Classes/MobileScannerPlugin.swift | 54 +++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/ios/Classes/MobileScannerPlugin.swift b/ios/Classes/MobileScannerPlugin.swift index e9b768bfc..9e108f35e 100644 --- a/ios/Classes/MobileScannerPlugin.swift +++ b/ios/Classes/MobileScannerPlugin.swift @@ -153,20 +153,20 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { } } } catch MobileScannerError.alreadyStarted { - result(FlutterError(code: "MobileScanner", - message: "Called start() while already started!", + result(FlutterError(code: MobileScannerErrorCodes.ALREADY_STARTED_ERROR, + message: MobileScannerErrorCodes.ALREADY_STARTED_ERROR_MESSAGE, details: nil)) } catch MobileScannerError.noCamera { - result(FlutterError(code: "MobileScanner", - message: "No camera found or failed to open camera!", + result(FlutterError(code: MobileScannerErrorCodes.NO_CAMERA_ERROR, + message: MobileScannerErrorCodes.NO_CAMERA_ERROR_MESSAGE, details: nil)) } catch MobileScannerError.cameraError(let error) { - result(FlutterError(code: "MobileScanner", - message: "Error occured when setting up camera!", - details: error)) + result(FlutterError(code: MobileScannerErrorCodes.CAMERA_ERROR, + message: error.localizedDescription, + details: nil)) } catch { - result(FlutterError(code: "MobileScanner", - message: "Unknown error occured.", + result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, + message: MobileScannerErrorCodes.GENERIC_ERROR_MESSAGE, details: nil)) } } @@ -189,25 +189,25 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { private func setScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let scale = call.arguments as? CGFloat if (scale == nil) { - result(FlutterError(code: "MobileScanner", - message: "You must provide a scale when calling setScale!", - details: nil)) + result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, + message: MobileScannerErrorCodes.INVALID_ZOOM_SCALE_ERROR_MESSAGE, + details: "The invalid zoom scale was nil.")) return } do { try mobileScanner.setScale(scale!) result(nil) } catch MobileScannerError.zoomWhenStopped { - result(FlutterError(code: "MobileScanner", - message: "Called setScale() while stopped!", + result(FlutterError(code: MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR, + message: MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE, details: nil)) } catch MobileScannerError.zoomError(let error) { - result(FlutterError(code: "MobileScanner", - message: "Error while zooming.", - details: error)) + result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, + message: error.localizedDescription, + details: nil)) } catch { - result(FlutterError(code: "MobileScanner", - message: "Error while zooming.", + result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, + message: MobileScannerErrorCodes.GENERIC_ERROR_MESSAGE, details: nil)) } } @@ -218,16 +218,16 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { try mobileScanner.resetScale() result(nil) } catch MobileScannerError.zoomWhenStopped { - result(FlutterError(code: "MobileScanner", - message: "Called resetScale() while stopped!", + result(FlutterError(code: MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR, + message: MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE, details: nil)) } catch MobileScannerError.zoomError(let error) { - result(FlutterError(code: "MobileScanner", - message: "Error while zooming.", - details: error)) + result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, + message: error.localizedDescription, + details: nil)) } catch { - result(FlutterError(code: "MobileScanner", - message: "Error while zooming.", + result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, + message: MobileScannerErrorCodes.GENERIC_ERROR_MESSAGE, details: nil)) } } @@ -269,7 +269,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { barcodeScannerOptions: scannerOptions, callback: { barcodes, error in if error != nil { DispatchQueue.main.async { - result(FlutterError(code: "MobileScanner", + result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, message: error?.localizedDescription, details: nil)) } From 43e6cb5939f3b9729e0023beab30d80574de41df Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 17 Jun 2024 16:45:54 +0200 Subject: [PATCH 10/30] fix error from stale build file --- .../mobile_scanner/MobileScannerPermissionsListener.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerPermissionsListener.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerPermissionsListener.kt index fd49487a8..8f148b992 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerPermissionsListener.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerPermissionsListener.kt @@ -1,6 +1,7 @@ package dev.steenbakker.mobile_scanner import android.content.pm.PackageManager +import dev.steenbakker.mobile_scanner.objects.MobileScannerErrorCodes import io.flutter.plugin.common.PluginRegistry /** @@ -29,11 +30,9 @@ internal class MobileScannerPermissionsListener( // grantResults could be empty if the permissions request with the user is interrupted // https://developer.android.com/reference/android/app/Activity#onRequestPermissionsResult(int,%20java.lang.String[],%20int[]) if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) { - resultCallback.onResult( - MobileScannerPermissions.CAMERA_ACCESS_DENIED, - MobileScannerPermissions.CAMERA_ACCESS_DENIED_MESSAGE) + resultCallback.onResult(MobileScannerErrorCodes.CAMERA_ACCESS_DENIED) } else { - resultCallback.onResult(null, null) + resultCallback.onResult(null) } return true From 518081220c3e317dbde4619327e163bea42d839a Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 18 Jun 2024 09:47:17 +0200 Subject: [PATCH 11/30] emit barcode errors through the channel --- .../mobile_scanner_method_channel.dart | 25 ++++++++++++++++--- lib/src/mobile_scanner_controller.dart | 3 +++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/src/method_channel/mobile_scanner_method_channel.dart b/lib/src/method_channel/mobile_scanner_method_channel.dart index a5134dc01..885b09bc0 100644 --- a/lib/src/method_channel/mobile_scanner_method_channel.dart +++ b/lib/src/method_channel/mobile_scanner_method_channel.dart @@ -16,6 +16,10 @@ import 'package:mobile_scanner/src/objects/start_options.dart'; /// An implementation of [MobileScannerPlatform] that uses method channels. class MethodChannelMobileScanner extends MobileScannerPlatform { + /// The name of the error event that is sent when a barcode scan error occurs. + @visibleForTesting + static const String kBarcodeErrorEventName = 'MOBILE_SCANNER_BARCODE_ERROR'; + /// The method channel used to interact with the native platform. @visibleForTesting final methodChannel = const MethodChannel( @@ -40,11 +44,22 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { int? _textureId; /// Parse a [BarcodeCapture] from the given [event]. + /// + /// If the event name is [kBarcodeErrorEventName], + /// a [MobileScannerBarcodeException] is thrown. BarcodeCapture? _parseBarcode(Map? event) { if (event == null) { return null; } + if (event + case { + 'name': kBarcodeErrorEventName, + 'data': final String? errorDescription + }) { + throw MobileScannerBarcodeException(errorDescription); + } + final Object? data = event['data']; if (data == null || data is! List) { @@ -121,9 +136,13 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { @override Stream get barcodesStream { - return eventsStream - .where((event) => event['name'] == 'barcode') - .map((event) => _parseBarcode(event)); + // Handle both incoming barcode events and barcode error events. + return eventsStream.where( + (event) { + return event['name'] == 'barcode' || + event['name'] == kBarcodeErrorEventName; + }, + ).map((event) => _parseBarcode(event)); } @override diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index 657542a80..72f1fcea3 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -102,6 +102,9 @@ class MobileScannerController extends ValueNotifier { StreamController.broadcast(); /// Get the stream of scanned barcodes. + /// + /// If an error occurred during the detection of a barcode, + /// an [MobileScannerBarcodeException] error is emitted to the stream. Stream get barcodes => _barcodesController.stream; StreamSubscription? _barcodesSubscription; From 196a4b0f95d01f03d6ede9f6c108cd3833f89161 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 18 Jun 2024 09:54:18 +0200 Subject: [PATCH 12/30] forward scan errors to the controller --- lib/src/mobile_scanner_controller.dart | 27 ++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index 72f1fcea3..b72d11d69 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -124,14 +124,25 @@ class MobileScannerController extends ValueNotifier { } void _setupListeners() { - _barcodesSubscription = MobileScannerPlatform.instance.barcodesStream - .listen((BarcodeCapture? barcode) { - if (_barcodesController.isClosed || barcode == null) { - return; - } - - _barcodesController.add(barcode); - }); + _barcodesSubscription = + MobileScannerPlatform.instance.barcodesStream.listen( + (BarcodeCapture? barcode) { + if (_barcodesController.isClosed || barcode == null) { + return; + } + + _barcodesController.add(barcode); + }, + onError: (Object error) { + if (_barcodesController.isClosed) { + return; + } + + _barcodesController.addError(error); + }, + // Errors are handled gracefully by forwarding them. + cancelOnError: false, + ); _torchStateSubscription = MobileScannerPlatform.instance.torchStateStream .listen((TorchState torchState) { From 9d1a70fcf0919de317c2ffee65ae2158e36d0840 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 18 Jun 2024 10:00:33 +0200 Subject: [PATCH 13/30] add a not to onDetect about error handling --- lib/src/mobile_scanner.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/src/mobile_scanner.dart b/lib/src/mobile_scanner.dart index 39f1541b5..ef4663d3a 100644 --- a/lib/src/mobile_scanner.dart +++ b/lib/src/mobile_scanner.dart @@ -35,6 +35,12 @@ class MobileScanner extends StatefulWidget { /// The function that signals when new codes were detected by the [controller]. /// If null, use the controller.barcodes stream directly to capture barcodes. + /// + /// This method does not receive any [MobileScannerBarcodeException]s + /// that are emitted by the scanner. + /// + /// To handle both [BarcodeCapture]s and [MobileScannerBarcodeException]s, + /// use the [MobileScannerController.barcodes] stream directly. final void Function(BarcodeCapture barcodes)? onDetect; /// The error builder for the camera preview. From b5ce2297a091feffb463a2cbda6c2be9ba639b3a Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 18 Jun 2024 10:34:59 +0200 Subject: [PATCH 14/30] adjust analyze image to throw barcode exceptions if needed --- .../mobile_scanner/MobileScannerHandler.kt | 2 +- ios/Classes/MobileScannerPlugin.swift | 2 +- .../mobile_scanner_method_channel.dart | 37 ++++++++++++------- lib/src/mobile_scanner_controller.dart | 3 ++ 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt index 353c091eb..0ffdf082e 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt @@ -29,7 +29,7 @@ class MobileScannerHandler( private val analyzeImageErrorCallback: AnalyzerErrorCallback = { Handler(Looper.getMainLooper()).post { - analyzerResult?.error(MobileScannerErrorCodes.GENERIC_ERROR, it, null) + analyzerResult?.error(MobileScannerErrorCodes.BARCODE_ERROR, it, null) analyzerResult = null } } diff --git a/ios/Classes/MobileScannerPlugin.swift b/ios/Classes/MobileScannerPlugin.swift index 9e108f35e..2b8be817c 100644 --- a/ios/Classes/MobileScannerPlugin.swift +++ b/ios/Classes/MobileScannerPlugin.swift @@ -269,7 +269,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { barcodeScannerOptions: scannerOptions, callback: { barcodes, error in if error != nil { DispatchQueue.main.async { - result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, + result(FlutterError(code: MobileScannerErrorCodes.BARCODE_ERROR, message: error?.localizedDescription, details: nil)) } diff --git a/lib/src/method_channel/mobile_scanner_method_channel.dart b/lib/src/method_channel/mobile_scanner_method_channel.dart index 885b09bc0..60bc1d962 100644 --- a/lib/src/method_channel/mobile_scanner_method_channel.dart +++ b/lib/src/method_channel/mobile_scanner_method_channel.dart @@ -164,21 +164,30 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { String path, { List formats = const [], }) async { - final Map? result = - await methodChannel.invokeMapMethod( - 'analyzeImage', - { - 'filePath': path, - 'formats': formats.isEmpty - ? null - : [ - for (final BarcodeFormat format in formats) - if (format != BarcodeFormat.unknown) format.rawValue, - ], - }, - ); + try { + final Map? result = + await methodChannel.invokeMapMethod( + 'analyzeImage', + { + 'filePath': path, + 'formats': formats.isEmpty + ? null + : [ + for (final BarcodeFormat format in formats) + if (format != BarcodeFormat.unknown) format.rawValue, + ], + }, + ); - return _parseBarcode(result); + return _parseBarcode(result); + } on PlatformException catch (error) { + // Handle any errors from analyze image requests. + if (error.code == kBarcodeErrorEventName) { + throw MobileScannerBarcodeException(error.message); + } + + return null; + } } @override diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index b72d11d69..10e884c16 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -191,6 +191,9 @@ class MobileScannerController extends ValueNotifier { /// This is only supported on Android, iOS and MacOS. /// /// Returns the [BarcodeCapture] that was found in the image. + /// + /// If an error occurred during the analysis of the image, + /// a [MobileScannerBarcodeException] error is thrown. Future analyzeImage(String path) { return MobileScannerPlatform.instance.analyzeImage(path); } From fd9941c20187171143c3936b0cfc97675345865f Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 18 Jun 2024 11:55:51 +0200 Subject: [PATCH 15/30] bump version and changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6a9391c3..b37b87272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ ## NEXT +Improvements: * [MacOS] Added the corners and size information to barcode results. * [MacOS] Added support for `analyzeImage`. * [MacOS] Added a Privacy Manifest. * [web] Added the size information to barcode results. * Added support for barcode formats to image analysis. +* Updated the scanner to report any scanning errors that were encountered during processing. + +Bugs fixed: +* Fixed a bug that would cause the scanner to emit an error when it was already started. Now it ignores any calls to start while it is starting. +* [Android] Fixed a bug that prevented `useNewCameraSelector` from being used correctly. (thanks @bswhite1!) ## 5.2.3 From a57d460d701505db66a14e0e059079da3330b213 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 18 Jun 2024 15:18:14 +0200 Subject: [PATCH 16/30] fix bug in app lifecycle guidance --- CHANGELOG.md | 3 +++ README.md | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37b87272..ed83ecd2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Improvements: * [web] Added the size information to barcode results. * Added support for barcode formats to image analysis. * Updated the scanner to report any scanning errors that were encountered during processing. +* Introduced a new getter `hasCameraPermission` for the `MobileScannerState`. +* Fixed a bug in the lifecycle handling sample. Now instead of checking `isInitialized`, +the sample recommends using `hasCameraPermission`, which also guards against camera permission errors. Bugs fixed: * Fixed a bug that would cause the scanner to emit an error when it was already started. Now it ignores any calls to start while it is starting. diff --git a/README.md b/README.md index 24099550f..5e724be17 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ class MyState extends State with WidgetsBindingObserver { void didChangeAppLifecycleState(AppLifecycleState state) { // If the controller is not ready, do not try to start or stop it. // Permission dialogs can trigger lifecycle changes before the controller is ready. - if (!controller.value.isInitialized) { + if (!controller.value.hasCameraPermission) { return; } @@ -192,4 +192,4 @@ Future dispose() async { To display the camera preview, pass the controller to a `MobileScanner` widget. See the [examples](example/README.md) for runnable examples of various usages, -such as the basic usage, applying a scan window, or retrieving images from the barcodes. +such as the basic usage, applying a scan window, or retrieving images from the barcodes. \ No newline at end of file From 47a0779a39a4a7a0e75b1d773f71c469882d139b Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Wed, 18 Sep 2024 11:05:36 +0200 Subject: [PATCH 17/30] fix typo --- lib/src/mobile_scanner_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index 10e884c16..cabfef726 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -104,7 +104,7 @@ class MobileScannerController extends ValueNotifier { /// Get the stream of scanned barcodes. /// /// If an error occurred during the detection of a barcode, - /// an [MobileScannerBarcodeException] error is emitted to the stream. + /// a [MobileScannerBarcodeException] error is emitted to the stream. Stream get barcodes => _barcodesController.stream; StreamSubscription? _barcodesSubscription; From 53a4134fc33be7ce063a1b8a2cd93e9050dbc2d7 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Wed, 18 Sep 2024 11:21:27 +0200 Subject: [PATCH 18/30] fix MacOS leftover error codes --- .../Sources/mobile_scanner/MobileScannerPlugin.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index 42d583cc3..a76bdb1ba 100644 --- a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -484,8 +484,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, if error != nil { DispatchQueue.main.async { - // TODO: fix error code - result(FlutterError(code: "MobileScanner", message: error?.localizedDescription, details: nil)) + result(FlutterError( + code: MobileScannerErrorCodes.BARCODE_ERROR, + message: error?.localizedDescription, details: nil)) } return } @@ -510,10 +511,11 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, } try imageRequestHandler.perform([barcodeRequest]) - } catch let e { - // TODO: fix error code + } catch let error { DispatchQueue.main.async { - result(FlutterError(code: "MobileScanner", message: e.localizedDescription, details: nil)) + result(FlutterError( + code: MobileScannerErrorCodes.BARCODE_ERROR, + message: error.localizedDescription, details: nil)) } } } From 4d92d5de17b305c2ec368d42c9682a8132da4e08 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Wed, 18 Sep 2024 12:36:51 +0200 Subject: [PATCH 19/30] remove outdated line in changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed83ecd2a..b5638eca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ the sample recommends using `hasCameraPermission`, which also guards against cam Bugs fixed: * Fixed a bug that would cause the scanner to emit an error when it was already started. Now it ignores any calls to start while it is starting. -* [Android] Fixed a bug that prevented `useNewCameraSelector` from being used correctly. (thanks @bswhite1!) ## 5.2.3 From 3ec0dc12833ee219f275bfb9086c2e0eb3525f74 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 24 Sep 2024 15:32:02 +0200 Subject: [PATCH 20/30] publis barcode error event for Android --- .../kotlin/dev/steenbakker/mobile_scanner/BarcodeHandler.kt | 6 ++++++ .../dev/steenbakker/mobile_scanner/MobileScannerHandler.kt | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/BarcodeHandler.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/BarcodeHandler.kt index 631e6f0e2..00a309170 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/BarcodeHandler.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/BarcodeHandler.kt @@ -24,6 +24,12 @@ class BarcodeHandler(binaryMessenger: BinaryMessenger) : EventChannel.StreamHand } } + fun publishError(errorCode: String, errorMessage: String, errorDetails: Any?) { + Handler(Looper.getMainLooper()).post { + eventSink?.error(errorCode, errorMessage, errorDetails) + } + } + override fun onListen(event: Any?, eventSink: EventChannel.EventSink?) { this.eventSink = eventSink } diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt index 0ffdf082e..4ef12d644 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt @@ -66,10 +66,7 @@ class MobileScannerHandler( } private val errorCallback: MobileScannerErrorCallback = {error: String -> - barcodeHandler.publishEvent(mapOf( - "name" to MobileScannerErrorCodes.BARCODE_ERROR, - "data" to error, - )) + barcodeHandler.publishError(MobileScannerErrorCodes.BARCODE_ERROR, error, null) } private var methodChannel: MethodChannel? = null From eda540fd46a008ed03079d2a4d593122bc729c3f Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 28 Sep 2024 10:38:11 +0200 Subject: [PATCH 21/30] forward barcode error events as FlutterError()'s on iOS & MacOS --- ios/Classes/BarcodeHandler.swift | 6 ++++++ ios/Classes/MobileScannerPlugin.swift | 8 ++++---- .../mobile_scanner/MobileScannerPlugin.swift | 14 ++++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/ios/Classes/BarcodeHandler.swift b/ios/Classes/BarcodeHandler.swift index 8df420641..8545fdbb9 100644 --- a/ios/Classes/BarcodeHandler.swift +++ b/ios/Classes/BarcodeHandler.swift @@ -19,6 +19,12 @@ public class BarcodeHandler: NSObject, FlutterStreamHandler { eventChannel.setStreamHandler(self) } + func publishError(_ error: FlutterError) { + DispatchQueue.main.async { + self.eventSink?(error) + } + } + func publishEvent(_ event: [String: Any?]) { DispatchQueue.main.async { self.eventSink?(event) diff --git a/ios/Classes/MobileScannerPlugin.swift b/ios/Classes/MobileScannerPlugin.swift index 2b8be817c..1db5e4988 100644 --- a/ios/Classes/MobileScannerPlugin.swift +++ b/ios/Classes/MobileScannerPlugin.swift @@ -42,10 +42,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { init(barcodeHandler: BarcodeHandler, registry: FlutterTextureRegistry) { self.mobileScanner = MobileScanner(registry: registry, mobileScannerCallback: { barcodes, error, image in if error != nil { - barcodeHandler.publishEvent([ - "name": MobileScannerErrorCodes.BARCODE_ERROR, - "data": error!.localizedDescription, - ]) + barcodeHandler.publishError( + FlutterError(code: MobileScannerErrorCodes.BARCODE_ERROR, + message: error?.localizedDescription, + details: nil)) return } diff --git a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index a76bdb1ba..ef389b8a9 100644 --- a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -131,10 +131,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, if error != nil { DispatchQueue.main.async { - self?.sink?([ - "name": MobileScannerErrorCodes.BARCODE_ERROR, - "data": error?.localizedDescription, - ]) + self?.sink?(FlutterError( + code: MobileScannerErrorCodes.BARCODE_ERROR, + message: error?.localizedDescription, details: nil)) } return } @@ -185,10 +184,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, try imageRequestHandler.perform([barcodeRequest]) } catch let error { DispatchQueue.main.async { - self?.sink?([ - "name": MobileScannerErrorCodes.BARCODE_ERROR, - "data": error.localizedDescription, - ]) + self?.sink?(FlutterError( + code: MobileScannerErrorCodes.BARCODE_ERROR, + message: error.localizedDescription, details: nil)) } } } From 8bc967cf6a18f3587f59aa8775c02ccf9b185db8 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 28 Sep 2024 10:58:32 +0200 Subject: [PATCH 22/30] forward errors from ZXing --- lib/src/web/mobile_scanner_web.dart | 9 ++++++++ lib/src/web/zxing/zxing_barcode_reader.dart | 23 ++++++++++++++------- lib/src/web/zxing/zxing_exception.dart | 10 +++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 lib/src/web/zxing/zxing_exception.dart diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 78190197d..afaf21cd1 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -322,6 +322,15 @@ class MobileScannerWeb extends MobileScannerPlatform { _barcodesController.add(barcode); }, + onError: (Object error) { + if (_barcodesController.isClosed) { + return; + } + + _barcodesController.addError(error); + }, + // Errors are handled gracefully by forwarding them. + cancelOnError: false, ); final bool hasTorch = await _barcodeReader?.hasTorch() ?? false; diff --git a/lib/src/web/zxing/zxing_barcode_reader.dart b/lib/src/web/zxing/zxing_barcode_reader.dart index 32f23a8ce..1a3402aab 100644 --- a/lib/src/web/zxing/zxing_barcode_reader.dart +++ b/lib/src/web/zxing/zxing_barcode_reader.dart @@ -3,6 +3,7 @@ import 'dart:js_interop'; import 'dart:ui'; import 'package:mobile_scanner/src/enums/barcode_format.dart'; +import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; import 'package:mobile_scanner/src/objects/barcode_capture.dart'; import 'package:mobile_scanner/src/objects/start_options.dart'; import 'package:mobile_scanner/src/web/barcode_reader.dart'; @@ -10,6 +11,7 @@ import 'package:mobile_scanner/src/web/javascript_map.dart'; import 'package:mobile_scanner/src/web/media_track_constraints_delegate.dart'; import 'package:mobile_scanner/src/web/zxing/result.dart'; import 'package:mobile_scanner/src/web/zxing/zxing_browser_multi_format_reader.dart'; +import 'package:mobile_scanner/src/web/zxing/zxing_exception.dart'; import 'package:web/web.dart' as web; /// A barcode reader implementation that uses the ZXing library. @@ -98,16 +100,23 @@ final class ZXingBarcodeReader extends BarcodeReader { _reader?.decodeContinuously.callAsFunction( _reader, _reader?.videoElement, - (Result? result, JSAny? error) { - if (controller.isClosed || result == null) { + (Result? result, ZXingException? error) { + if (controller.isClosed) { return; } - controller.add( - BarcodeCapture( - barcodes: [result.toBarcode], - ), - ); + if (error != null) { + controller.addError(MobileScannerBarcodeException(error.message)); + return; + } + + if (result != null) { + controller.add( + BarcodeCapture( + barcodes: [result.toBarcode], + ), + ); + } }.toJS, ); }; diff --git a/lib/src/web/zxing/zxing_exception.dart b/lib/src/web/zxing/zxing_exception.dart new file mode 100644 index 000000000..b1858627c --- /dev/null +++ b/lib/src/web/zxing/zxing_exception.dart @@ -0,0 +1,10 @@ +import 'dart:js_interop'; + +/// The JS static interop class for the Result class in the ZXing library. +/// +/// See also: https://github.com/zxing-js/library/blob/master/src/core/Exception.ts +@JS('ZXing.Exception') +extension type ZXingException._(JSObject _) implements JSObject { + /// The error message of the exception, if any. + external String? get message; +} From e9b3ad4796b4bbaae6678da83ba6364ea1d4ae28 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 28 Sep 2024 11:19:24 +0200 Subject: [PATCH 23/30] handle barcode error events in the method channel implementation --- .../mobile_scanner_method_channel.dart | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/src/method_channel/mobile_scanner_method_channel.dart b/lib/src/method_channel/mobile_scanner_method_channel.dart index 60bc1d962..de64f4d1d 100644 --- a/lib/src/method_channel/mobile_scanner_method_channel.dart +++ b/lib/src/method_channel/mobile_scanner_method_channel.dart @@ -44,22 +44,11 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { int? _textureId; /// Parse a [BarcodeCapture] from the given [event]. - /// - /// If the event name is [kBarcodeErrorEventName], - /// a [MobileScannerBarcodeException] is thrown. BarcodeCapture? _parseBarcode(Map? event) { if (event == null) { return null; } - if (event - case { - 'name': kBarcodeErrorEventName, - 'data': final String? errorDescription - }) { - throw MobileScannerBarcodeException(errorDescription); - } - final Object? data = event['data']; if (data == null || data is! List) { @@ -94,6 +83,19 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { ); } + /// Parse a [MobileScannerBarcodeException] from the given [error] and [stackTrace], and throw it. + /// + /// If the error is not a [PlatformException], + /// with [kBarcodeErrorEventName] as [PlatformException.code], the error is rethrown as-is. + Never _parseBarcodeError(Object error, StackTrace stackTrace) { + if (error case PlatformException(:final String code, :final String? message) + when code == kBarcodeErrorEventName) { + throw MobileScannerBarcodeException(message); + } + + Error.throwWithStackTrace(error, stackTrace); + } + /// Request permission to access the camera. /// /// Throws a [MobileScannerException] if the permission is not granted. @@ -136,13 +138,12 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { @override Stream get barcodesStream { - // Handle both incoming barcode events and barcode error events. - return eventsStream.where( - (event) { - return event['name'] == 'barcode' || - event['name'] == kBarcodeErrorEventName; - }, - ).map((event) => _parseBarcode(event)); + // Handle incoming barcode events. + // The error events are transformed to `MobileScannerBarcodeException` where possible. + return eventsStream + .where((e) => e['name'] == 'barcode') + .map((event) => _parseBarcode(event)) + .handleError(_parseBarcodeError); } @override From d346a45afffa5f8271dbeab9b18572b4d2cec24f Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 28 Sep 2024 11:23:08 +0200 Subject: [PATCH 24/30] sort methods --- .../dev/steenbakker/mobile_scanner/BarcodeHandler.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/BarcodeHandler.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/BarcodeHandler.kt index 00a309170..aaafb1467 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/BarcodeHandler.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/BarcodeHandler.kt @@ -18,15 +18,15 @@ class BarcodeHandler(binaryMessenger: BinaryMessenger) : EventChannel.StreamHand eventChannel.setStreamHandler(this) } - fun publishEvent(event: Map) { + fun publishError(errorCode: String, errorMessage: String, errorDetails: Any?) { Handler(Looper.getMainLooper()).post { - eventSink?.success(event) + eventSink?.error(errorCode, errorMessage, errorDetails) } } - fun publishError(errorCode: String, errorMessage: String, errorDetails: Any?) { + fun publishEvent(event: Map) { Handler(Looper.getMainLooper()).post { - eventSink?.error(errorCode, errorMessage, errorDetails) + eventSink?.success(event) } } From e967f54f049fa5bf92dea1678ff0fb44eba0c975 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 28 Sep 2024 11:36:01 +0200 Subject: [PATCH 25/30] use const for barcode event name --- lib/src/method_channel/mobile_scanner_method_channel.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/method_channel/mobile_scanner_method_channel.dart b/lib/src/method_channel/mobile_scanner_method_channel.dart index de64f4d1d..793492bf3 100644 --- a/lib/src/method_channel/mobile_scanner_method_channel.dart +++ b/lib/src/method_channel/mobile_scanner_method_channel.dart @@ -16,6 +16,10 @@ import 'package:mobile_scanner/src/objects/start_options.dart'; /// An implementation of [MobileScannerPlatform] that uses method channels. class MethodChannelMobileScanner extends MobileScannerPlatform { + /// The name of the barcode event that is sent when a barcode is scanned. + @visibleForTesting + static const String kBarcodeEventName = 'barcode'; + /// The name of the error event that is sent when a barcode scan error occurs. @visibleForTesting static const String kBarcodeErrorEventName = 'MOBILE_SCANNER_BARCODE_ERROR'; @@ -141,7 +145,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { // Handle incoming barcode events. // The error events are transformed to `MobileScannerBarcodeException` where possible. return eventsStream - .where((e) => e['name'] == 'barcode') + .where((e) => e['name'] == kBarcodeEventName) .map((event) => _parseBarcode(event)) .handleError(_parseBarcodeError); } From 447996d88c015d798777d45ad8ed550e15a4c78f Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 28 Sep 2024 11:50:50 +0200 Subject: [PATCH 26/30] handle no code detected for ZXing --- lib/src/web/zxing/zxing_barcode_reader.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/web/zxing/zxing_barcode_reader.dart b/lib/src/web/zxing/zxing_barcode_reader.dart index 1a3402aab..407f972cb 100644 --- a/lib/src/web/zxing/zxing_barcode_reader.dart +++ b/lib/src/web/zxing/zxing_barcode_reader.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:js_interop'; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:mobile_scanner/src/enums/barcode_format.dart'; import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; import 'package:mobile_scanner/src/objects/barcode_capture.dart'; @@ -18,6 +19,11 @@ import 'package:web/web.dart' as web; final class ZXingBarcodeReader extends BarcodeReader { ZXingBarcodeReader(); + /// ZXing reports an error with this message if the code could not be detected. + @visibleForTesting + static const String kNoCodeDetectedErrorMessage = + 'No MultiFormat Readers were able to detect the code.'; + /// The listener for media track settings changes. void Function(web.MediaTrackSettings)? _onMediaTrackSettingsChanged; @@ -105,7 +111,8 @@ final class ZXingBarcodeReader extends BarcodeReader { return; } - if (error != null) { + // Skip the event if no code was detected. + if (error != null && error.message != kNoCodeDetectedErrorMessage) { controller.addError(MobileScannerBarcodeException(error.message)); return; } From ed3f54fcd0e1525d397f6dc2241fda9a6f27bc97 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 28 Sep 2024 12:12:18 +0200 Subject: [PATCH 27/30] fix macos entitlements for example & use raw value for sample --- CHANGELOG.md | 1 + example/lib/barcode_scanner_analyze_image.dart | 11 +++++++++-- example/macos/Runner/DebugProfile.entitlements | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5638eca3..479efd560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ the sample recommends using `hasCameraPermission`, which also guards against cam Bugs fixed: * Fixed a bug that would cause the scanner to emit an error when it was already started. Now it ignores any calls to start while it is starting. +* [MacOS] Fixed a bug that prevented the `anaylzeImage()` sample from working properly. ## 5.2.3 diff --git a/example/lib/barcode_scanner_analyze_image.dart b/example/lib/barcode_scanner_analyze_image.dart index de0a7a4a1..187f7f6e9 100644 --- a/example/lib/barcode_scanner_analyze_image.dart +++ b/example/lib/barcode_scanner_analyze_image.dart @@ -22,7 +22,14 @@ class _BarcodeScannerAnalyzeImageState final XFile? file = await ImagePicker().pickImage(source: ImageSource.gallery); - if (!mounted || file == null) { + if (!mounted) { + return; + } + + if (file == null) { + setState(() { + _barcodeCapture = null; + }); return; } @@ -43,7 +50,7 @@ class _BarcodeScannerAnalyzeImageState if (_barcodeCapture != null) { label = Text( - _barcodeCapture?.barcodes.firstOrNull?.displayValue ?? + _barcodeCapture?.barcodes.firstOrNull?.rawValue ?? 'No barcode detected', ); } diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements index 0830a9c55..e9bec60a3 100644 --- a/example/macos/Runner/DebugProfile.entitlements +++ b/example/macos/Runner/DebugProfile.entitlements @@ -10,5 +10,7 @@ com.apple.security.network.server + com.apple.security.files.user-selected.read-only + From f5dd22d07527573043b4bf028d510637aa2de51f Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 28 Sep 2024 13:07:31 +0200 Subject: [PATCH 28/30] fix deprecated tag --- example/web/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/web/index.html b/example/web/index.html index 1af22e3a9..9b5c810ca 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -21,7 +21,7 @@ - + From c4bddc386a3880fb75d4903dd7e67b2c17d40644 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 28 Sep 2024 13:29:24 +0200 Subject: [PATCH 29/30] add swiftpm dir to macos example gitignore; fix secure coding warning for sample --- example/.gitignore | 2 ++ example/macos/Runner/AppDelegate.swift | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/example/.gitignore b/example/.gitignore index 24476c5d1..6c319542b 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index 8e02df288..b3c176141 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } From bb999fe4d794cf3e242a01ed3d3f5512ed2c1e42 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 30 Sep 2024 08:56:18 +0200 Subject: [PATCH 30/30] adjust iOS example development team & bundle identifier --- example/ios/Runner.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index fad022828..a1b2c52e4 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -488,14 +488,14 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 75Y2P2WSQQ; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner"; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner-example"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -670,14 +670,14 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 75Y2P2WSQQ; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner"; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner-example"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -696,14 +696,14 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 75Y2P2WSQQ; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner"; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner-example"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";