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.
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
).