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
How to Launch an Activity Without a Ui
How to Hide One Item in an Android Spinner
Pass Arraylist Data into Soap Web Service in Android
Android: What Is Better - Multiple Activities or Switching Views Manually
Should Accessing Sharedpreferences Be Done Off the UI Thread
Failure [Install_Failed_Update_Incompatible] Even If App Appears to Not Be Installed
Out of Memory Error While Loading Bitmaps
Android Ble Bluetoothgatt.Writedescriptor() Return Sometimes False
Properly Using Asynctask Get()
How to Deal with Different Aspect Ratios in Libgdx
Where Is the All Android Broadcast Intent List
Android Detect Usb Storage for Kitkat (4.4)