Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I wait for other events in Kotlin coroutines?

In Kotlin, using Coroutines, Lets say I have a job that starts on a click of a button, and shouldn't end until the activity's onStop is called.

Something that looks like this:

button.setOnClickListener {
    CoroutineScope(...).launch{
        print("Button clicked")

        // How to wait for "onStop()" ?

        print("Activity stopped")
    }
}

The above scenario is just an example of a general need to incorporate asynchronous events that come from within an SDK in the form of a function invocation (onStop()).

How should it be done? Thank you :]

like image 929
Sean Avatar asked Apr 26 '19 15:04

Sean


People also ask

How do you wait in coroutine?

So, whatever you want to execute when coroutine is done, you just put at the end of that coroutine and declare context (withContext) in which you want to execute it. The exception is of course if you start another async piece of code within coroutine (like another coroutine).

How do you use await in Kotlin coroutines?

In order to migrate to the async/await pattern, you have to return the async() result from your code, and call await() on the Deferred , from within another coroutine. By doing so, you can remove callbacks you used to use, to consume asynchronously provided values.

What is await in coroutines Kotlin?

Series - Kotlin coroutines Async/await is a common feature in many languages (naming might vary), that allows you to execute functions asynchronously while waiting for their results at a later point.

How do you wait for all coroutine to finish Kotlin?

We can wait for the coroutine to finish by calling join() on the Job. For example, suppose we have a suspend function to download some files. We can launch this coroutine and capture the resulting job, which we can later use to join — to wait for the operation to complete.


Video Answer


3 Answers

I would create a special channel for a button, than send something to it from onStop(0) and wait for it in your code:

val onStopChannel = Channel<Int>()

fun onStop() {
    onStopChannel.offer(0)
}

button.setOnClickListener {
    CoroutineScope(...).launch{
        print("Button clicked")

        onStopChannel.receive()

        print("Activity stopped")
    }
}

Any other observable could also work.

like image 113
voddan Avatar answered Oct 17 '22 17:10

voddan


To address the question of:

a general need to incorporate asynchronous events that come from within an SDK in the form of a function invocation

I would like to add that the standard library's way of integrating asynchronous callbacks or futures from another library or API that doesn't use coroutines into your own code that does use coroutines is the suspendCoroutine function. It suspends the execution of the current coroutine, takes a non-suspending lambda in which you register your callbacks/listeners, which when called will tell the coroutine via a Continuation object to resume execution of the suspending function with a particular value or exception obtained from the callback.

For example, in the case of a network request:

suspend fun useValueFromApiCall(apiCall: Call<Thing>) {
    val myThing: Thing = suspendCoroutine<Thing> { continuation ->
        apiCall.enqueue(object: Callback<Thing> {
            override fun onResponse(call: Call<Thing>, response: Response<Thing>) {
                continuation.resume(response.body()) // assign response.body() to myThing
            }
            override fun onFailure(call: Call<Thing>, t: Throwable) {
                continuation.resumeWithException(t) // throw t
            }
        })
    }
    // coroutine will suspend execution until the API call returns and
    // either myThing contains the response or an exception was thrown
    myThing.doSomething()
}

Here is a snippet of a talk explaining what's going on here quite nicely.

like image 4
Chuck Stein Avatar answered Oct 17 '22 17:10

Chuck Stein


I would make my job wait with join() function and then cancel it in onStop() callback. Something like:

class MainActivity : AppCompatActivity() {
    private var job: Job = Job()
    private val mainDispatchersContext = CoroutineScope(Dispatchers.Main)
    private val coroutineContext = CoroutineScope(Dispatchers.Main + job)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mainDispatchersContext.launch {
            doJob()
        }
    }

    private suspend fun doJob() {
        coroutineContext.launch {
            // do something
            Log.v(TAG, "Job started")
            job.join()
            Log.v(TAG, "This line is not executed")
        }
    }

    override fun onStop() {
        super.onStop()
        job.cancel()
        Log.v(TAG, "Job end")
    }

    companion object{
        const val TAG = "MainActivity"
    }
}
like image 1
Nicola Gallazzi Avatar answered Oct 17 '22 18:10

Nicola Gallazzi