I have a general question with a specific example: I'd like to use Kotlin coroutine magic instead of callback hell in Android when taking a picture.
manager.openCamera(cameraId, object : CameraDevice.StateCallback() { override fun onOpened(openedCameraDevice: CameraDevice) { println("Camera onOpened") // even more callbacks with openedCameraDevice.createCaptureRequest().... } override fun onDisconnected(cameraDevice: CameraDevice) { println("Camera onDisconnected") cameraDevice.close() } ...
How would I convert that to something less ugly? Is it possible to take an average callback with three or so functions, and turn it into a promise-chain by designating the primary flow as the promise-result path? And if so, should/do I use coroutines to make it async?
I'd love something with async and .await that would result in
manager.open(cameraId).await().createCaptureRequest()
I'm trying to do it through something like the following, but I don't think I'm using CompletableDeferred
right!
suspend fun CameraManager.open(cameraId:String): CameraDevice { val response = CompletableDeferred<CameraDevice>() this.openCamera(cameraId, object : CameraDevice.StateCallback() { override fun onOpened(cameraDevice: CameraDevice) { println("camera onOpened $cameraDevice") response.complete(cameraDevice) } override fun onDisconnected(cameraDevice: CameraDevice) { response.completeExceptionally(Exception("Camera onDisconnected $cameraDevice")) cameraDevice.close() } override fun onError(cameraDevice: CameraDevice, error: Int) { response.completeExceptionally(Exception("Camera onError $cameraDevice $error")) cameraDevice.close() } }, Handler()) return response.await() }
Kotlin coroutines & structured concurrencyIn Kotlin, we can create coroutines using builders such as launch and async , which return a Job instance. This Job may further contain nested coroutine builders that create children Job instances that are computed concurrently.
Coroutines can be executed concurrently using a multi-threaded dispatcher like the Dispatchers.
In this particular case you can use a general approach to convert a callback-based API to a suspending function via suspendCoroutine
function:
suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? = suspendCoroutine { cont -> val callback = object : CameraDevice.StateCallback() { override fun onOpened(camera: CameraDevice) { cont.resume(camera) } override fun onDisconnected(camera: CameraDevice) { cont.resume(null) } override fun onError(camera: CameraDevice, error: Int) { // assuming that we don't care about the error in this example cont.resume(null) } } openCamera(cameraId, callback, null) }
Now, in your application code you can just do manager.openCamera(cameraId)
and get a reference to CameraDevice
if it was opened successfully or null
if it was not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With