Reputation: 22280
I'm using the HiveMQ Android Client in my Android App. I "probe" for a device using a transient object for the transaction, which wraps an Mqtt3AsyncClient
.
fun probe(callback: ((String) -> Unit)) {
...
client
.connectWith()
.cleanSession(true)
.keepAlive(60)
.simpleAuth()...applySimpleAuth().send()
.whenComplete { _, _ ->
client
.subscribeWith()
.addSubscription().topicFilter(eventTopic).applySubscription()
.callback { message ->
handleMessage(message.topic, message.payloadAsBytes))
}
.send()
.whenComplete { _, _ -> attemptToCommunicate()}
}
}
The problem is that my handleMessage
function (which will run async because that's what the async client is all about) makes a call to a suspend fun
in it's body, e.g.
fun handleMessage(topic:String, message:Bytes) {
...
saveDataFuncThatIsDefinedWithSuspend(...)
...
}
I can't exactly mark handleMessage
as suspend
, because it's call from the callback will be an error. Marking the probe
function as suspend doesn't solve that either. And I'm not at liberty to not use the HiveMQ client, so I'm stuck with the callback.
I toyed with wrapping the suspend function as such:
CoroutineScope(Dispatchers.IO).launch { saveDataFuncThatIsDefinedWithSuspend(...) }
but I got the impression, that this is considered a bad idea? Or I could wrap a runBlocking
around it, but again, that seems frowned upon. Should I make my transient transaction object itself a CoroutineScope implementor? I'm struggling with how one bridges these two worlds in a case like this.
Upvotes: 0
Views: 204
Reputation: 93902
This is what I believe is the one situation on Android where runBlocking
is an appropriate solution. If you cannot design a blocking version of your suspend function, there’s no way you can avoid tying up two threads for this (one from the coroutine dispatcher, and the one the client library that waits for it). So runBlocking
is the simplest, cleanest way to achieve this.
If your message handler doesn’t need to wait for your suspend function to return, then it does make sense to launch
a coroutine to call it. But doing this with a one-off CoroutineScope that you create and abandon the reference for is an anti-pattern. Either you want to launch the coroutine globally so it doesn’t automatically get cancelled under some event like navigating away from a related screen or you don’t. For a global coroutine like the first case, you would use GlobalScope, and for the second case you would use a scope tied to the relevant lifecycle for whatever you’re doing.
Upvotes: 1
Reputation: 16092
Yes, in most cases you will want to define a new CoroutineScope
as a child of another scope, and not create a new scope out of the blue.
That said, you can always pass in a scope as a parameter (to probe
and/or handleMessage
) so that you can use it like this:
scope.launch {
saveDataFuncThatIsDefinedWithSuspend(...)
}
That scope should be created somewhere outside of your legacy code where you have access to a parent scope, for example in a suspend function (possibly the one that calls probe
).
Upvotes: 1