Navaron Bracke
Committed by GitHub

Merge pull request #1180 from navaronbracke/error_codes_cleanup

fix: Clean up error codes
@@ -4,4 +4,5 @@ linter: @@ -4,4 +4,5 @@ linter:
4 rules: 4 rules:
5 - combinators_ordering 5 - combinators_ordering
6 - require_trailing_commas 6 - require_trailing_commas
7 - - unnecessary_library_directive  
  7 + - unnecessary_library_directive
  8 + - prefer_single_quotes
@@ -120,12 +120,7 @@ class MobileScanner( @@ -120,12 +120,7 @@ class MobileScanner(
120 } 120 }
121 121
122 if (!returnImage) { 122 if (!returnImage) {
123 - mobileScannerCallback(  
124 - barcodeMap,  
125 - null,  
126 - null,  
127 - null  
128 - ) 123 + mobileScannerCallback(barcodeMap, null, null, null)
129 return@addOnSuccessListener 124 return@addOnSuccessListener
130 } 125 }
131 126
  1 +package dev.steenbakker.mobile_scanner.objects
  2 +
  3 +class MobileScannerErrorCodes {
  4 + companion object {
  5 + const val ALREADY_STARTED_ERROR = "MOBILE_SCANNER_ALREADY_STARTED_ERROR"
  6 + const val ALREADY_STARTED_ERROR_MESSAGE = "The scanner was already started."
  7 + // The error code 'BARCODE_ERROR' does not have an error message,
  8 + // because it uses the error message from the underlying error.
  9 + const val BARCODE_ERROR = "MOBILE_SCANNER_BARCODE_ERROR"
  10 + // The error code 'CAMERA_ACCESS_DENIED' does not have an error message,
  11 + // because it is used for a boolean result.
  12 + const val CAMERA_ACCESS_DENIED = "MOBILE_SCANNER_CAMERA_PERMISSION_DENIED"
  13 + const val CAMERA_ERROR = "MOBILE_SCANNER_CAMERA_ERROR"
  14 + const val CAMERA_ERROR_MESSAGE = "An error occurred when opening the camera."
  15 + const val CAMERA_PERMISSIONS_REQUEST_ONGOING = "MOBILE_SCANNER_CAMERA_PERMISSION_REQUEST_PENDING"
  16 + const val CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE = "Another request is ongoing and multiple requests cannot be handled at once."
  17 + const val GENERIC_ERROR = "MOBILE_SCANNER_GENERIC_ERROR"
  18 + const val GENERIC_ERROR_MESSAGE = "An unknown error occurred."
  19 + const val INVALID_ZOOM_SCALE_ERROR_MESSAGE = "The zoom scale should be between 0 and 1 (both inclusive)"
  20 + const val NO_CAMERA_ERROR = "MOBILE_SCANNER_NO_CAMERA_ERROR"
  21 + const val NO_CAMERA_ERROR_MESSAGE = "No cameras available."
  22 + const val SET_SCALE_WHEN_STOPPED_ERROR = "MOBILE_SCANNER_SET_SCALE_WHEN_STOPPED_ERROR"
  23 + const val SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE = "The zoom scale cannot be changed when the camera is stopped."
  24 + }
  25 +}
@@ -60,7 +60,7 @@ class _BarcodeScannerWithControllerState @@ -60,7 +60,7 @@ class _BarcodeScannerWithControllerState
60 60
61 @override 61 @override
62 void didChangeAppLifecycleState(AppLifecycleState state) { 62 void didChangeAppLifecycleState(AppLifecycleState state) {
63 - if (!controller.value.isInitialized) { 63 + if (!controller.value.hasCameraPermission) {
64 return; 64 return;
65 } 65 }
66 66
@@ -6,6 +6,12 @@ @@ -6,6 +6,12 @@
6 // 6 //
7 import Foundation 7 import Foundation
8 8
  9 +// TODO: decide if we should keep or discard this enum
  10 +// When merging the iOS / MacOS implementations we should either keep the enum or remove it
  11 +
  12 +// This enum is a bit of a leftover from older parts of the iOS implementation.
  13 +// It is used by the handler that throws these error codes,
  14 +// while the plugin class intercepts these and converts them to `FlutterError()`s.
9 enum MobileScannerError: Error { 15 enum MobileScannerError: Error {
10 case noCamera 16 case noCamera
11 case alreadyStarted 17 case alreadyStarted
  1 +//
  2 +// MobileScannerErrorCodes.swift
  3 +// mobile_scanner
  4 +//
  5 +// Created by Navaron Bracke on 28/05/2024.
  6 +//
  7 +
  8 +import Foundation
  9 +
  10 +/// This struct defines the error codes and error messages for MobileScanner errors.
  11 +///
  12 +/// These are used by `FlutterError` as error code and error message.
  13 +///
  14 +/// This struct should not be confused with `MobileScannerError`,
  15 +/// which is an implementation detail for the iOS implementation.
  16 +struct MobileScannerErrorCodes {
  17 + static let ALREADY_STARTED_ERROR = "MOBILE_SCANNER_ALREADY_STARTED_ERROR"
  18 + static let ALREADY_STARTED_ERROR_MESSAGE = "The scanner was already started."
  19 + // The error code 'BARCODE_ERROR' does not have an error message,
  20 + // because it uses the error message from the undelying error.
  21 + static let BARCODE_ERROR = "MOBILE_SCANNER_BARCODE_ERROR"
  22 + // The error code 'CAMERA_ERROR' does not have an error message,
  23 + // because it uses the error message from the underlying error.
  24 + static let CAMERA_ERROR = "MOBILE_SCANNER_CAMERA_ERROR"
  25 + static let GENERIC_ERROR = "MOBILE_SCANNER_GENERIC_ERROR"
  26 + static let GENERIC_ERROR_MESSAGE = "An unknown error occurred."
  27 + // This message is used with the 'GENERIC_ERROR' error code.
  28 + static let INVALID_ZOOM_SCALE_ERROR_MESSAGE = "The zoom scale should be between 0 and 1 (both inclusive)"
  29 + static let NO_CAMERA_ERROR = "MOBILE_SCANNER_NO_CAMERA_ERROR"
  30 + static let NO_CAMERA_ERROR_MESSAGE = "No cameras available."
  31 + static let SET_SCALE_WHEN_STOPPED_ERROR = "MOBILE_SCANNER_SET_SCALE_WHEN_STOPPED_ERROR"
  32 + static let SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE = "The zoom scale cannot be changed when the camera is stopped."
  33 +}
@@ -258,9 +258,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -258,9 +258,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
258 let uiImage = UIImage(contentsOfFile: (call.arguments as! Dictionary<String, Any?>)["filePath"] as? String ?? "") 258 let uiImage = UIImage(contentsOfFile: (call.arguments as! Dictionary<String, Any?>)["filePath"] as? String ?? "")
259 259
260 if (uiImage == nil) { 260 if (uiImage == nil) {
261 - result(FlutterError(code: "MobileScanner",  
262 - message: "No image found in analyzeImage!",  
263 - details: nil)) 261 + result(nil)
264 return 262 return
265 } 263 }
266 264
  1 +import 'package:flutter/services.dart';
1 import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; 2 import 'package:mobile_scanner/src/mobile_scanner_controller.dart';
2 3
3 /// This enum defines the different error codes for the mobile scanner. 4 /// This enum defines the different error codes for the mobile scanner.
@@ -24,5 +25,25 @@ enum MobileScannerErrorCode { @@ -24,5 +25,25 @@ enum MobileScannerErrorCode {
24 permissionDenied, 25 permissionDenied,
25 26
26 /// Scanning is unsupported on the current device. 27 /// Scanning is unsupported on the current device.
27 - unsupported, 28 + unsupported;
  29 +
  30 + /// Convert the given [PlatformException.code] to a [MobileScannerErrorCode].
  31 + factory MobileScannerErrorCode.fromPlatformException(
  32 + PlatformException exception,
  33 + ) {
  34 + // The following error code mapping should be kept in sync with their native counterparts.
  35 + // These are located in `MobileScannerErrorCodes.kt` and `MobileScannerErrorCodes.swift`.
  36 + return switch (exception.code) {
  37 + // In case the scanner was already started, report the right error code.
  38 + // If the scanner is already starting,
  39 + // this error code is a signal to the controller to just ignore the attempt.
  40 + 'MOBILE_SCANNER_ALREADY_STARTED_ERROR' =>
  41 + MobileScannerErrorCode.controllerAlreadyInitialized,
  42 + // In case no cameras are available, using the scanner is not supported.
  43 + 'MOBILE_SCANNER_NO_CAMERA_ERROR' => MobileScannerErrorCode.unsupported,
  44 + 'MOBILE_SCANNER_CAMERA_PERMISSION_DENIED' =>
  45 + MobileScannerErrorCode.permissionDenied,
  46 + _ => MobileScannerErrorCode.genericError,
  47 + };
  48 + }
28 } 49 }
@@ -187,8 +187,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { @@ -187,8 +187,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
187 throw const MobileScannerException( 187 throw const MobileScannerException(
188 errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, 188 errorCode: MobileScannerErrorCode.controllerAlreadyInitialized,
189 errorDetails: MobileScannerErrorDetails( 189 errorDetails: MobileScannerErrorDetails(
190 - message:  
191 - 'The scanner was already started. Call stop() before calling start() again.', 190 + message: 'The scanner was already started.',
192 ), 191 ),
193 ); 192 );
194 } 193 }
@@ -204,7 +203,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { @@ -204,7 +203,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
204 ); 203 );
205 } on PlatformException catch (error) { 204 } on PlatformException catch (error) {
206 throw MobileScannerException( 205 throw MobileScannerException(
207 - errorCode: MobileScannerErrorCode.genericError, 206 + errorCode: MobileScannerErrorCode.fromPlatformException(error),
208 errorDetails: MobileScannerErrorDetails( 207 errorDetails: MobileScannerErrorDetails(
209 code: error.code, 208 code: error.code,
210 details: error.details as Object?, 209 details: error.details as Object?,
@@ -240,17 +239,13 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { @@ -240,17 +239,13 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
240 startResult['currentTorchState'] as int? ?? -1, 239 startResult['currentTorchState'] as int? ?? -1,
241 ); 240 );
242 241
243 - final Map<Object?, Object?>? sizeInfo =  
244 - startResult['size'] as Map<Object?, Object?>?;  
245 - final double? width = sizeInfo?['width'] as double?;  
246 - final double? height = sizeInfo?['height'] as double?;  
247 -  
248 final Size size; 242 final Size size;
249 243
250 - if (width == null || height == null) {  
251 - size = Size.zero;  
252 - } else { 244 + if (startResult['size']
  245 + case {'width': final double width, 'height': final double height}) {
253 size = Size(width, height); 246 size = Size(width, height);
  247 + } else {
  248 + size = Size.zero;
254 } 249 }
255 250
256 return MobileScannerViewAttributes( 251 return MobileScannerViewAttributes(
@@ -281,8 +281,7 @@ class _MobileScannerState extends State<MobileScanner> @@ -281,8 +281,7 @@ class _MobileScannerState extends State<MobileScanner>
281 281
282 @override 282 @override
283 void didChangeAppLifecycleState(AppLifecycleState state) { 283 void didChangeAppLifecycleState(AppLifecycleState state) {
284 - if (widget.controller != null) return;  
285 - if (!controller.value.isInitialized) { 284 + if (widget.controller != null || !controller.value.hasCameraPermission) {
286 return; 285 return;
287 } 286 }
288 287
1 import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; 1 import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
2 2
3 -/// This class represents an exception thrown by the mobile scanner. 3 +/// This class represents an exception thrown by the [MobileScannerController].
4 class MobileScannerException implements Exception { 4 class MobileScannerException implements Exception {
5 const MobileScannerException({ 5 const MobileScannerException({
6 required this.errorCode, 6 required this.errorCode,
@@ -16,9 +16,9 @@ class MobileScannerException implements Exception { @@ -16,9 +16,9 @@ class MobileScannerException implements Exception {
16 @override 16 @override
17 String toString() { 17 String toString() {
18 if (errorDetails != null && errorDetails?.message != null) { 18 if (errorDetails != null && errorDetails?.message != null) {
19 - return "MobileScannerException: code ${errorCode.name}, message: ${errorDetails?.message}"; 19 + return 'MobileScannerException(${errorCode.name}, ${errorDetails?.message})';
20 } 20 }
21 - return "MobileScannerException: ${errorCode.name}"; 21 + return 'MobileScannerException(${errorCode.name})';
22 } 22 }
23 } 23 }
24 24
@@ -46,3 +46,22 @@ class MobileScannerErrorDetails { @@ -46,3 +46,22 @@ class MobileScannerErrorDetails {
46 /// This exception type is only used internally, 46 /// This exception type is only used internally,
47 /// and is not part of the public API. 47 /// and is not part of the public API.
48 class PermissionRequestPendingException implements Exception {} 48 class PermissionRequestPendingException implements Exception {}
  49 +
  50 +/// This class represents an exception thrown by the [MobileScannerController]
  51 +/// when a barcode scanning error occurs when processing an input frame.
  52 +class MobileScannerBarcodeException implements Exception {
  53 + /// Creates a new [MobileScannerBarcodeException] with the given error message.
  54 + const MobileScannerBarcodeException(this.message);
  55 +
  56 + /// The error message of the exception.
  57 + final String? message;
  58 +
  59 + @override
  60 + String toString() {
  61 + if (message?.isNotEmpty ?? false) {
  62 + return 'MobileScannerBarcodeException($message)';
  63 + }
  64 +
  65 + return 'MobileScannerBarcodeException(Could not detect a barcode in the input image.)';
  66 + }
  67 +}
1 import 'dart:ui'; 1 import 'dart:ui';
2 2
3 import 'package:mobile_scanner/src/enums/camera_facing.dart'; 3 import 'package:mobile_scanner/src/enums/camera_facing.dart';
  4 +import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
4 import 'package:mobile_scanner/src/enums/torch_state.dart'; 5 import 'package:mobile_scanner/src/enums/torch_state.dart';
5 import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; 6 import 'package:mobile_scanner/src/mobile_scanner_exception.dart';
6 7
@@ -43,7 +44,8 @@ class MobileScannerState { @@ -43,7 +44,8 @@ class MobileScannerState {
43 44
44 /// Whether the mobile scanner has initialized successfully. 45 /// Whether the mobile scanner has initialized successfully.
45 /// 46 ///
46 - /// This is `true` if the camera is ready to be used. 47 + /// This does not indicate that the camera permission was granted.
  48 + /// To check if the camera permission was granted, use [hasCameraPermission].
47 final bool isInitialized; 49 final bool isInitialized;
48 50
49 /// Whether the mobile scanner is currently running. 51 /// Whether the mobile scanner is currently running.
@@ -60,6 +62,12 @@ class MobileScannerState { @@ -60,6 +62,12 @@ class MobileScannerState {
60 /// The current zoom scale of the camera. 62 /// The current zoom scale of the camera.
61 final double zoomScale; 63 final double zoomScale;
62 64
  65 + /// Whether permission to access the camera was granted.
  66 + bool get hasCameraPermission {
  67 + return isInitialized &&
  68 + error?.errorCode != MobileScannerErrorCode.permissionDenied;
  69 + }
  70 +
63 /// Create a copy of this state with the given parameters. 71 /// Create a copy of this state with the given parameters.
64 MobileScannerState copyWith({ 72 MobileScannerState copyWith({
65 int? availableCameras, 73 int? availableCameras,
  1 +//
  2 +// MobileScannerErrorCodes.swift
  3 +// mobile_scanner
  4 +//
  5 +// Created by Navaron Bracke on 27/05/2024.
  6 +//
  7 +
  8 +import Foundation
  9 +
  10 +struct MobileScannerErrorCodes {
  11 + static let ALREADY_STARTED_ERROR = "MOBILE_SCANNER_ALREADY_STARTED_ERROR"
  12 + static let ALREADY_STARTED_ERROR_MESSAGE = "The scanner was already started."
  13 + // The error code 'BARCODE_ERROR' does not have an error message,
  14 + // because it uses the error message from the undelying error.
  15 + static let BARCODE_ERROR = "MOBILE_SCANNER_BARCODE_ERROR"
  16 + // The error code 'CAMERA_ERROR' does not have an error message,
  17 + // because it uses the error message from the underlying error.
  18 + static let CAMERA_ERROR = "MOBILE_SCANNER_CAMERA_ERROR"
  19 + static let NO_CAMERA_ERROR = "MOBILE_SCANNER_NO_CAMERA_ERROR"
  20 + static let NO_CAMERA_ERROR_MESSAGE = "No cameras available."
  21 +}
@@ -273,7 +273,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -273,7 +273,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
273 273
274 let argReader = MapArgumentReader(call.arguments as? [String: Any]) 274 let argReader = MapArgumentReader(call.arguments as? [String: Any])
275 275
276 - // let ratio: Int = argReader.int(key: "ratio")  
277 let torch:Bool = argReader.bool(key: "torch") ?? false 276 let torch:Bool = argReader.bool(key: "torch") ?? false
278 let facing:Int = argReader.int(key: "facing") ?? 1 277 let facing:Int = argReader.int(key: "facing") ?? 1
279 let speed:Int = argReader.int(key: "speed") ?? 0 278 let speed:Int = argReader.int(key: "speed") ?? 0
@@ -327,7 +326,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -327,7 +326,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
327 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) 326 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
328 captureSession!.addOutput(videoOutput) 327 captureSession!.addOutput(videoOutput)
329 for connection in videoOutput.connections { 328 for connection in videoOutput.connections {
330 - // connection.videoOrientation = .portrait  
331 if position == .front && connection.isVideoMirroringSupported { 329 if position == .front && connection.isVideoMirroringSupported {
332 connection.isVideoMirrored = true 330 connection.isVideoMirrored = true
333 } 331 }
@@ -459,20 +457,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -459,20 +457,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
459 let symbologies:[VNBarcodeSymbology] = argReader.toSymbology() 457 let symbologies:[VNBarcodeSymbology] = argReader.toSymbology()
460 458
461 guard let filePath: String = argReader.string(key: "filePath") else { 459 guard let filePath: String = argReader.string(key: "filePath") else {
462 - // TODO: fix error code  
463 - result(FlutterError(code: "MobileScanner",  
464 - message: "No image found in analyzeImage!",  
465 - details: nil)) 460 + result(nil)
466 return 461 return
467 } 462 }
468 463
469 let fileUrl = URL(fileURLWithPath: filePath) 464 let fileUrl = URL(fileURLWithPath: filePath)
470 465
471 guard let ciImage = CIImage(contentsOf: fileUrl) else { 466 guard let ciImage = CIImage(contentsOf: fileUrl) else {
472 - // TODO: fix error code  
473 - result(FlutterError(code: "MobileScanner",  
474 - message: "No image found in analyzeImage!",  
475 - details: nil)) 467 + result(nil)
476 return 468 return
477 } 469 }
478 470