Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Ignore additional calls to start() when the controller is starting #1086

Merged
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d19f1de
remove old lifecycle bug workaround for web
navaronbracke Apr 30, 2024
c816b39
shorten an error message
navaronbracke Apr 30, 2024
47534ae
ignore the already initialized error case
navaronbracke Apr 30, 2024
5b80d2c
unify the error codes on Android
navaronbracke May 17, 2024
14f5346
move barcode error code to constants on Android
navaronbracke May 27, 2024
f81bb29
unify MacOS error codes for FlutterError(); fix barcode error forat a…
navaronbracke May 27, 2024
8236ee6
port error codes to iOS as well
navaronbracke May 31, 2024
d1ac119
use well defined error code for barcode errors on iOS
navaronbracke May 31, 2024
dbd5250
refactor iOS impl to use well formatted error codes & messages for Fl…
navaronbracke May 31, 2024
43e6cb5
fix error from stale build file
navaronbracke Jun 17, 2024
5180812
emit barcode errors through the channel
navaronbracke Jun 18, 2024
196a4b0
forward scan errors to the controller
navaronbracke Jun 18, 2024
9d1a70f
add a not to onDetect about error handling
navaronbracke Jun 18, 2024
b5ce229
adjust analyze image to throw barcode exceptions if needed
navaronbracke Jun 18, 2024
fd9941c
bump version and changelog
navaronbracke Jun 18, 2024
a57d460
fix bug in app lifecycle guidance
navaronbracke Jun 18, 2024
47a0779
fix typo
navaronbracke Sep 18, 2024
53a4134
fix MacOS leftover error codes
navaronbracke Sep 18, 2024
4d92d5d
remove outdated line in changelog
navaronbracke Sep 18, 2024
3ec0dc1
publis barcode error event for Android
navaronbracke Sep 24, 2024
eda540f
forward barcode error events as FlutterError()'s on iOS & MacOS
navaronbracke Sep 28, 2024
8bc967c
forward errors from ZXing
navaronbracke Sep 28, 2024
e9b3ad4
handle barcode error events in the method channel implementation
navaronbracke Sep 28, 2024
d346a45
sort methods
navaronbracke Sep 28, 2024
e967f54
use const for barcode event name
navaronbracke Sep 28, 2024
447996d
handle no code detected for ZXing
navaronbracke Sep 28, 2024
ed3f54f
fix macos entitlements for example & use raw value for sample
navaronbracke Sep 28, 2024
f5dd22d
fix deprecated tag
navaronbracke Sep 28, 2024
c4bddc3
add swiftpm dir to macos example gitignore; fix secure coding warning…
navaronbracke Sep 28, 2024
bb999fe
adjust iOS example development team & bundle identifier
navaronbracke Sep 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
## 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.
* 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.

## 5.2.3

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class MyState extends State<MyStatefulWidget> 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;
}

Expand Down Expand Up @@ -192,4 +192,4 @@ Future<void> 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.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class BarcodeHandler(binaryMessenger: BinaryMessenger) : EventChannel.StreamHand
}
}

fun publishError(errorCode: String, errorMessage: String, errorDetails: Any?) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like publishEvent above, but for result.error()

Handler(Looper.getMainLooper()).post {
eventSink?.error(errorCode, errorMessage, errorDetails)
}
}

override fun onListen(event: Any?, eventSink: EventChannel.EventSink?) {
this.eventSink = eventSink
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,7 +29,7 @@ class MobileScannerHandler(

private val analyzeImageErrorCallback: AnalyzerErrorCallback = {
Handler(Looper.getMainLooper()).post {
analyzerResult?.error("MobileScanner", it, null)
analyzerResult?.error(MobileScannerErrorCodes.BARCODE_ERROR, it, null)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be consistent with the results from the scanning stream, I used the same barcode error constant

analyzerResult = null
}
}
Expand Down Expand Up @@ -65,10 +66,7 @@ class MobileScannerHandler(
}

private val errorCallback: MobileScannerErrorCallback = {error: String ->
barcodeHandler.publishEvent(mapOf(
"name" to "error",
"data" to error,
))
barcodeHandler.publishError(MobileScannerErrorCodes.BARCODE_ERROR, error, null)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an actual error event now.

}

private var methodChannel: MethodChannel? = null
Expand Down Expand Up @@ -106,21 +104,21 @@ class MobileScannerHandler(

@ExperimentalGetImage
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
if (mobileScanner == null) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check isn't needed. The scanner is set up when attached to the activity

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?) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plumbing through the new error codes

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)
}
}
})
Expand Down Expand Up @@ -185,29 +183,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
)
}
Expand Down Expand Up @@ -252,9 +250,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)
}
}

Expand All @@ -263,7 +263,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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@ 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

/**
* This class handles the camera permissions for the Mobile Scanner.
*/
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
Expand All @@ -25,7 +21,7 @@ class MobileScannerPermissions {
}

interface ResultCallback {
fun onResult(errorCode: String?, errorDescription: String?)
fun onResult(errorCode: String?)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second argument was unused, since we return a bool to the interface

}

private var listener: RequestPermissionsResultListener? = null
Expand Down Expand Up @@ -53,25 +49,24 @@ 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
}

if(listener == null) {
// 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)
}
}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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

/**
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions ios/Classes/BarcodeHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion ios/Classes/MobileScannerErrorCodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
navaronbracke marked this conversation as resolved.
Show resolved Hide resolved
static let GENERIC_ERROR = "MOBILE_SCANNER_GENERIC_ERROR"
static let GENERIC_ERROR_MESSAGE = "An unknown error occurred."
Expand Down
59 changes: 31 additions & 28 deletions ios/Classes/MobileScannerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.publishError(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an actual error event now

FlutterError(code: MobileScannerErrorCodes.BARCODE_ERROR,
message: error?.localizedDescription,
details: nil))
return
}

Expand Down Expand Up @@ -150,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))
}
}
Expand All @@ -186,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))
}
}
Expand All @@ -215,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))
}
}
Expand Down Expand Up @@ -266,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.BARCODE_ERROR,
message: error?.localizedDescription,
details: nil))
}
Expand Down
3 changes: 1 addition & 2 deletions lib/mobile_scanner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Loading
Loading