Not able to wait for the coroutine to finish using join() in kotlin? - kotlin

I am new to kotlin and coroutines.I have been working on a client-server part of an android app.I am trying to get the routercapabilities using the following code
suspend fun getRouterCapabilities(): String? = coroutineScope {
lateinit var routerRtpCapabilities: JSONObject
val job = launch {
socket?.emit("getRouterRtpCapabilities", "", Ack { args ->
routerRtpCapabilities = args[0] as JSONObject
Log.d(TAG, routerRtpCapabilities!!.getString("error"))
})
}
job.join()
Log.d(TAG, "$routerRtpCapabilities")
return#coroutineScope routerRtpCapabilities.toString()
}
In the above code, I am able to store and print the value of routerRtpCapabilites inside the emit().But I got below mentioned error when I tried to access it from outside the emit().
kotlin.UninitializedPropertyAccessException: lateinit property routerRtpCapabilities has not been initialized
And also I am not sure about the way coroutines are used here.Please correct me If I have missed something central.

The reason your job.join() doesn't wait for the acknowledgement is that the emit function launches an asynchronous action and immediately returns. The callback with the result is called later. But the coroutine job doesn't know to wait for some callback, so it immediately completes before the acknowledgement is received sometime later.
When you have a library with an asynchronous function with a callback, you can convert it to a suspend function so it can easily be used in coroutines. Here is how you could convert this emit function:
/** Emits the [event] and suspends until acknowledgement is received. Returns the
acknowledgement arguments. */
suspend fun Socket.awaitEmit(event: String, vararg arg: Any): Array<out Any?> =
suspendCoroutine { continuation ->
emit(event, *arg, Ack { args ->
continuation.resume(args)
})
}
Some libraries such as Retrofit and Firebase come with suspend function versions of their asynchronous functions, and for those, the above step would be necessary.
A proper suspend function can be called from other suspend functions and in coroutines without doing anything special like wrapping it in another coroutine or withContext block.
suspend fun getRouterCapabilities(): String? = routerCababilitiesMutex.withLock {
val acknowledgement = socket?.awaitEmit("getRouterRtpCapabilities", "")
acknowledgement ?: return null // must have null socket
val result = acknowledgement[0] as? JSONObject
Log.d(TAG, result?.getString("error") ?: "result is null")
return result?.toString()
}
The !! should be changed because it’s unsafe.
To call your suspend function from an Activity, you would do it in a lifecycleScope coroutine, for example:
mic.setOnClickListener{
lifecycleScope.launch {
val routerCap = someOtherClass.getRouterCapabilities()
// Do stuff with routerCap here.
}
}
Edit: How I would cache the value in your view model class. You can use the mutex to ensure it isn't retrieved redundantly.
private var routerCapabilities: String? = null
private val routerCapabilitiesMutex = Mutex()
suspend fun getRouterCapabilities(): String? = routerCapabilitiesMutex.withLock {
routerCapabilities?.let { return it }
val acknowledgement = socket?.awaitEmit("getRouterRtpCapabilities", "")
acknowledgement ?: return null // must have null socket
val result = acknowledgement[0] as? JSONObject
Log.d(TAG, result?.getString("error") ?: "result is null")
result?.toString().also { routerCapabilities = it }
}

Related

Flow message not delivered in unit test

I have a consumer that reads messages off MutableSharedFlow (which acts as an EventBus in my application). I am trying to write a unit test to show that passing a message into the Flow triggers my Listener.
This is my Flow definition:
class MessageBus {
private val _messages = MutableSharedFlow<Message>()
val messages = _messages.asSharedFlow()
suspend fun send(message: Message) {
_messages.emit(message)
}
}
Here is the Listener:
class Listener(private val messageBus: MessageBus) {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
init {
scope.launch {
messageBus.messages.collectLatest { message ->
when (message) {
is CustomMessage -> handleCustomMessage(message)
}
}
}
}
And finally here is my unit test:
class CommandTest {
#Test
fun `should process CustomMessage`(): Unit = runBlocking {
val messageBus = MessageBus()
val listener = Listener(messageBus)
messageBus.send(CustomMessage("test command"))
//argumentCaptor...verify[removed for brevity]
}
}
Unfortunately the above code does not trigger the break point in my Listener (breakpoint on line init is triggered, but a message is never received and no breakpoints triggered in the collectLatest block).
I even tried adding a Thread.sleep(5_000) before the verify statement but the result is the same. Am I missing something obvious with how coroutines work?
Edit: if it matters this is not an Android project. Simply Kotlin + Ktor
I imagine that since the code is in the init block in the Listener once you initialize val listener = Listener(messageBus, this) in the test it reads all messages and at this point you have none then in the next line you emit a message messageBus.send(CustomMessage("test command")) but your launch block should have finished by then. You can emit the message first or place your launch in an loop or in a different method that can be called after you emit the message
First of all I would recomend reading this article about how to test flows in Android.
Secondly in your example the issues arise from having the scope inside the Listener hardcoded. You should pass the scope as a parameter and inject it in the test:
class Listener(private val messageBus: MessageBus, private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()))
class CommandTest {
#Test
fun `should process CustomMessage`(): Unit = runBlockingTest {
val messageBus = MessageBus()
val listener = Listener(messageBus, this)
messageBus.send(CustomMessage("test command"))
//argumentCaptor...verify[removed for brevity]
}
}
I would also recomend using runBlockingTest instead of runBlocking so your tests don't have to actually wait. It will also fail in case any coroutines are left running once the test finishes.
You could use something like this
class Emitter {
private val emitter: MutableSharedFlow<String> = MutableSharedFlow()
suspend fun publish(messages: Flow<String>) = messages.onEach {
emitter.emit(it)
}.collect()
fun stream(): Flow<String> = emitter
}
the collect at the end of your onEach will be used to trigger the collection initially as a terminal operation... I need further understanding on emit because it does not work as I expect in all cases and when used in this way you have initially it does not post anything in your Flow unless you collect first to process
Then in your collector itself
class Collector {
suspend fun collect(emitter: Emitter): Unit = coroutineScope {
println("Starting collection...")
emitter.stream().collect { println("collecting message: $it") }
}
}
then your main (or test)
fun main() = runBlocking {
withContext(Dispatchers.Default + Job()) {
val emitter = Emitter()
val collector = Collector()
launch {
collector.collect(emitter)
}
emitter.publish(listOf("article#1", "article#2", "article#3", "article#4").asFlow())
}
}
output:
Starting collection...
collecting message: article#1
collecting message: article#2
collecting message: article#3
collecting message: article#4

Mix and match Coroutines and Rxjava

Coroutines and RxJava3
I have the following method that first makes a call to a suspend method and in the same launch scope I make 2 calls to RxJava.
I am wondering if there is a way to remove the Rxjava code out of the viewModelScope.launch scope and return the result of fetchRecentUseCase.execute().
Basically, is it possible for the viewModelScope.launch to return the listOfProducts rather than doing everything in the launch scope?
fun loadRecentlyViewed() {
viewModelScope.launch {
val listOfProducts = withContext(Dispatchers.IO) {
fetchRecentUseCase.execute()
}
val listOfSkus = listOfProducts.map { it.sku }
if (listOfSkus.isNotEmpty()) {
loadProductUseCase.execute(listOfSkus)
.subscribeOn(schedulersFacade.io)
.flatMap(convertProductDisplayUseCase::execute)
.map { /* work being done */ }
.observeOn(schedulersFacade.ui)
.subscribeBy(
onError = Timber::e,
onSuccess = { }
)
}
}
}
Usecase for the suspend method
class FetchRecentUseCaseImp() {
override suspend fun execute(): List<Products> {
// Call to network
}
}
Many thanks in advance
With coroutines, the way to return a single item that is produced asynchronously is to use a suspend function. So instead of launching a coroutine, you mark the function as suspend and convert blocking or async callback functions into non-blocking code.
The places where coroutines are launched are typically at UI interactions (click listeners), or when classes are first created (on Android, this is places like in a ViewModel constructor or Fragment's onViewCreated()).
As a side note, it is against convention for any suspend function to expect the caller to have to specify a dispatcher. It should internally delegate if it needs to, for example:
class FetchRecentUseCaseImp() {
override suspend fun execute(): List<Products> = withContext(Dispatchers.IO) {
// Synchronous call to network
}
}
But if you were using a library like Retrofit, you'd simply make your Request and await() it without specifying a dispatcher, because await() is a suspend function itself.
So your function should look something like:
suspend fun loadRecentlyViewed(): List<SomeProductType> {
val listOfSkus = fetchRecentUseCase.execute().map(Product::sku)
if (listOfSkus.isEmpty()) {
return emptyList()
}
return runCatching {
loadProductUseCase.execute(listOfSkus) // A Single, I'm assuming
.await() // Only if you're not completely stripping Rx from project
.map { convertProductDisplayUseCase.execute(it).await() } // Ditto for await()
.toList()
.flatten()
}.onFailure(Timber::e)
.getOrDefault(emptyList())
}

What is the diffrent about write or not suspendCoroutine/resume in suspend function

What is the diffrent about below function.
suspend fun doSomething1():Boolean{
val res = longtimeFunction()
return res
}
suspend fun doSomething2():Boolean = suspendCoroutine{ continuation->
val res = longtimeFunction()
continuation.resume(res)
}
There is no difference because this is not how you use suspendCoroutine. In order to achieve the suspending, non-blocking behavior, first you need an API that doesn't perform blocking calls and instead has a method that starts an operation and returns immediately, but takes a callback from you that will be notified of the result. For example:
suspend fun doSomething2() = suspendCoroutine<Boolean> { continuation ->
asyncLongtimeFunction(object: Callback {
override fun onSuccess(res: Boolean) {
continuation.resume(res)
}
})
}

How to suspend kotlin coroutine until notified

I would like to suspend a kotlin coroutine until a method is called from outside, just like the old Java object.wait() and object.notify() methods. How do I do that?
Here: Correctly implementing wait and notify in Kotlin is an answer how to implement this with Kotlin threads (blocking). And here: Suspend coroutine until condition is true is an answer how to do this with CompleteableDeferreds but I do not want to have to create a new instance of CompleteableDeferred every time.
I am doing this currently:
var nextIndex = 0
fun handleNext(): Boolean {
if (nextIndex < apps.size) {
//Do the actual work on apps[nextIndex]
nextIndex++
}
//only execute again if nextIndex is a valid index
return nextIndex < apps.size
}
handleNext()
// The returned function will be called multiple times, which I would like to replace with something like notify()
return ::handleNext
From: https://gitlab.com/SuperFreezZ/SuperFreezZ/blob/master/src/superfreeze/tool/android/backend/Freezer.kt#L69
Channels can be used for this (though they are more general):
When capacity is 0 – it creates RendezvousChannel. This channel does not have any buffer at all. An element is transferred from sender to receiver only when send and receive invocations meet in time (rendezvous), so send suspends until another coroutine invokes receive and receive suspends until another coroutine invokes send.
So create
val channel = Channel<Unit>(0)
And use channel.receive() for object.wait(), and channel.offer(Unit) for object.notify() (or send if you want to wait until the other coroutine receives).
For notifyAll, you can use BroadcastChannel instead.
You can of course easily encapsulate it:
inline class Waiter(private val channel: Channel<Unit> = Channel<Unit>(0)) {
suspend fun doWait() { channel.receive() }
fun doNotify() { channel.offer(Unit) }
}
It is possible to use the basic suspendCoroutine{..} function for that, e.g.
class SuspendWait() {
private lateinit var myCont: Continuation<Unit>
suspend fun sleepAndWait() = suspendCoroutine<Unit>{ cont ->
myCont = cont
}
fun resume() {
val cont = myCont
myCont = null
cont.resume(Unit)
}
}
It is clear, the code have issues, e.g. myCont field is not synchonized, it is expected that sleepAndWait is called before the resume and so on, hope the idea is clear now.
There is another solution with the Mutex class from the kotlinx.coroutines library.
class SuspendWait2 {
private val mutex = Mutex(locaked = true)
suspend fun sleepAndWait() = mutex.withLock{}
fun resume() {
mutex.unlock()
}
}
I suggest using a CompletableJob for that.
My use case:
suspend fun onLoad() {
var job1: CompletableJob? = Job()
var job2: CompletableJob? = Job()
lifecycleScope.launch {
someList.collect {
doSomething(it)
job1?.complete()
}
}
lifecycleScope.launch {
otherList.collect {
doSomethingElse(it)
job2?.complete()
}
}
joinAll(job1!!, job2!!) // suspends until both jobs are done
job1 = null
job2 = null
// Do something one time
}

Kotlin async/await syntax without blocking caller

I'd like to figure out if Kotlin can replace our current way of dealing with asynchronous code. Right now, we use CompletableFutures to handle asynchronous code. Here is an example of a such a method:
public void onBalanceRequest(Client client, String name) {
db.fetchBalance(name)
.thenAccept(balance -> {
client.sendMessage("Your money: " + balance);
});
}
The important point here is that onBalanceRequest is called from the main thread, that must not be blocked. Internally, db.fetchBalance runs asynchronous operations and resolves the future on completion, so the given call is not blocking the main thread.
After reviewing the Kotlin docs regarding coroutines, I had hope that we can do something like JavaScript's async/await. For example, this is what we can do in JavaScript:
async function onBalanceRequest(client, name) {
let balance = await db.fetchBalance(name);
client.sendMessage("Your money: " + balance);
}
Now, I've tried to connect our existing API to a Kotlin project:
private fun onBalanceRequest(client: Client) = runBlocking {
val money = db.fetchBalance(client.name)
client.sendMessage("Your money: $money")
}
suspend fun fetchBalance(player: String): Double? {
var result: Double? = null
GlobalScope.launch {
originalFetchBalance(player).thenAccept {
result = it
}
}.join()
return result
}
However, since I used runBlocking, the execution of onBalanceRequest is blocking the main thread. So I'm aksing you, if I can achieve something similar to async/await with Kotlin.
Thank you.
If your JS function is async, the corresponding Kotlin function should be suspend:
private suspend fun onBalanceRequest(client: Client) {
val money = db.fetchBalance(client.name)
client.sendMessage("Your money: $money")
}
There's no need for await, because Kotlin is statically typed and the compiler already knows which functions are suspend and need to be treated specially (though C#, which is also statically typed, uses async/await model for explicitness).
Note that it can only be called directly from suspend functions; if you want to "fire-and-forget" it, use launch:
private fun onBalanceRequest(client: Client) = GlobalScope.launch {
val money = db.fetchBalance(client.name)
client.sendMessage("Your money: $money")
}
And for using your CompletableFuture-returning functions, use kotlinx-coroutines-jdk8:
// should be suggested by IDE
import kotlinx.coroutines.future.await
suspend fun fetchBalance(player: String) = originalFetchBalance(player).await()
Of course runBlocking will run blocking. So instead use launch.
private fun onBalanceRequest(client: Client) = GlobalScope.launch {
val money = db.fetchBalance(client.name)
client.sendMessage("Your money: $money")
}
To bridge your CompletableFuture, you can use CompletableDeferred then
suspend fun fetchBalance(player: String): Double {
val async = CompletableDeferred()
originalFetchBalance(player).whenComplete { (value, error) ->
if (value != null) async.complete(value)
if (error != null) async.completeExceptionally(error)
}
return async.await()
}