androidkotlinkotlin-coroutineshivemq

Calling a suspend function from legacy library on background thread


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.


Solution

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