Existing 3-Function Callback to Kotlin Coroutines

Existing 3-function callback to Kotlin Coroutines

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.

How to convert custom callback to coroutine

You can use suspendCancellableCoroutine to model your callback-based one-shot request like so:

suspend fun retrievePaymentIntent(clientSecret: String): PaymentIntent =
suspendCancellableCoroutine { continuation ->
Terminal.getInstance().retrievePaymentIntent(clientSecret,
object : PaymentIntentCallback {
override fun onFailure(e: TerminalException)
{
continuation.resumeWithException(e)
}

override fun onSuccess(paymentIntent: PaymentIntent)
{
continuation.resume(paymentIntent)
}
})

continuation.invokeOnCancellation { /*cancel the payment intent retrieval if possible*/ }
}

Nested Callback function with coroutines

you have to do something like this

@ExperimentalCoroutinesApi
suspend fun getVideo(id: String): Video? = coroutineScope {
val offlineVideo: Video? = suspendCancellableCoroutine { cont ->
offlineCatalog.findOfflineVideoById(id, object : OfflineCallback<Video> {
override fun onSuccess(video: Video?) {
cont.resume(video)
}
override fun onFailure(throwable: Throwable?) {
cont.resume(null)
}
})
}
offlineVideo ?: suspendCancellableCoroutine { cont ->
// offlineVideo not found so search from onlineCatalog
onlineCatalog.findVideoByID(id, object : VideoListener() {
override fun onVideo(video: Video?) {
cont.resume(video)
}

override fun onError(errors: MutableList<CatalogError>) {
super.onError(errors)
cont.resumeWithException(someException)
}
})
}
}

then you can call it as you wanted

someScope.launch {
try {
val video: Video? = getVideo(id)
//do something
} catch (throwable: Throwable) {
Log.e("Video not found")
}
}

Read more about suspendCancellableCoroutine here

Using Coroutines with Third party library that's using callback handlers

When using suspendCoroutine, you need to call resume/resumeWithException/etc on the continuation object. You can store/pass this object anywhere, for example to your listener:

class Handler(val context: Context) {
val listener = Listener()
val device = Controller.getInstance(context, listener)

suspend fun connectBT(BTDevice:BluetoothDevice){
suspendCoroutine<Unit> { continuation ->
listener.continuation = continuation
device.connectBT(BTDevice)
}
}
}

class Listener: BBDeviceController.BBDeviceControllerListener{
var continuation: Continuation<Unit>? = null

override fun onBTConnected(device: BluetoothDevice?) {
println("Device Connected")
if (continuation != null) {
continuation?.resume(Unit)
continuation = null
}
}
}

Use coroutines to make a function return a value obtained in a callback

You need to wrap your callbacks inside a suspendCancellableCoroutine block in order to turn your blocking API call into a suspending function, so you can call it from a coroutine. It works like this:

suspend fun sendAudioMessage(): String = suspendCancellableCoroutine { continuation ->
WebSocketUtils.Listener {
// ... some (not relevant) functions
override fun onDisconnect(code: Int, reason: String?) {
//Do some stuff
when (code) {
OK -> continuation.resume("myResult -> $code")
ERROR -> continuation.resumeWithException(Exception(reason))
}
}
}
}

When your API call returns successfully you can return the result to your coroutine calling continuation.resume with the result as argument.

When your API call returns with an error you can throw an exception calling continuation.resumeWithException.

Now you can call sendAudioMessage inside a coroutine and work with its result as usual:

class MyClass: CoroutineScope by CoroutineScope(Dispatchers.Default) {

...

override fun send(request: String) {
launch(Dispatchers.IO) {
val response = d.sendAudioMessage()
analyzeResponse(response, request)
}
}

...
}


Related Topics



Leave a reply



Submit