Trigger event listeners async with Kotlin Coroutines - kotlin

I have created an abstract Event class which is used to create events in Kotlin. Now I would like to use Coroutines to call each subscriber asynchronously.
abstract class Event<T> {
private var handlers = listOf<(T) -> Unit>()
infix fun on(handler: (T) -> Unit) {
handlers += handler
println(handlers.count())
}
fun emit(event: T) =
runBlocking {
handlers.forEach { subscriber ->
GlobalScope.launch {
subscriber(event)
}
}
}
}
And a concrete class that can be used to create event listeners and event publishers
class AsyncEventTest {
companion object : Event<AsyncEventTest>()
fun emit() = emit(this)
}
The issue is that when I run the following code I can see it creates all the listeners, but not even half of them are executed.
fun main(args: Array<String>) {
val random = Random(1000)
runBlocking {
// Create a 1000 event listeners with a random delay of 0 - 1000 ms
for (i in 1..1000)
AsyncEventTest on {
GlobalScope.launch {
delay(random.nextLong())
println(i)
}
}
}
println("================")
runBlocking {
// Trigger the event
AsyncEventTest().emit()
}
}
What am I missing here?
Update
When I remove delay(random.nextLong(), all handlers are executed. This is weird, since I'm trying to simulate different response times from the handlers that way and I think a handler should always execute or throw an exception.

You are running the event listeners with GlobalScope.launch() that does not interact with the surrounding runBlocking() scope. Means runBlocking() returns before all launched coroutines are finished. That is the reason you don't see the output.
BTW: your usage of coroutines and runBlocking is not recommended
You should add suspend to the emit() function. The same is true for the handler parameter - make it suspendable.

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())
}

How to call suspend function from Service Android?

How to provide scope or how to call suspend function from Service Android?
Usually, activity or viewmodel provides us the scope, from where we can launch suspend but there is no similar thing in Service
You can create your own CoroutineScope with a SupervisorJob that you can cancel in the onDestroy() method. The coroutines created with this scope will live as long as your Service is being used. Once onDestroy() of your service is called, all coroutines started with this scope will be cancelled.
class YourService : Service() {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
...
fun foo() {
scope.launch {
// Call your suspend function
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
Edit: Changed Dispatchers.Main to Dispatchers.IO
SupervisorJob() (Kotlin GitHub) is a job that provides uniderectional cancellation; it allows for cancellations to propogate downwards only.
The SupervisorJob ... is similar to a regular Job with the only exception that cancellation is propagated only downwards. [KotlinLang.org]
Use case: You have a service that makes a log entry, checks settings, and depending on those settings goes ahead and performs some actions. Do you want all children jobs of the parent job (scope) to cancel if, for instance, a job run based on settings' values throws an exception? If not (i.e. you still want your logging and check for settings jobs to complete at the least) then you want to use the SupervisorJob(), or even supervisorScope (Kotlin GitHub) for 'scoped concurrency' [KotlinLang.org], as both provide unidirectional job cancellation - and in that case the provided answer works.
Coroutine Exception Handling - Supervision (KotlinLang.org)
However, there is a more direct solution that answers the question.
To provide to your service a scope with which to run coroutines (or suspending functions) that execute blocking code, you can simply create a new CoroutineScope() with an EmptyCoroutineContext:
(Snippet from CoroutineScope Documentation)
If the given context does not contain a Job element, then a default Job() is created. This way, cancellation or failure of any child coroutine in this scope cancels all the other children, just like inside coroutineScope block [Kotlin GitHub]
class YourClass : Extended() {
...
private val serviceScope: CoroutineScope( EmptyCoroutineContext )
...
private inner class ServiceHandler( looper: Looper ): Handler( looper ) {
override fun handleMessage( msg: Message ) {
super.handleMessage( msg )
serviceScope.launch {
try{
+ ...
} catch( e: Exception ) {
+ ...
} finally {
stopSelf( msg.arg1 )
}
}
}
}
override fun onCreate(){
+ ...
}
override fun onDestroy(){
/* In a service, unlike in an activity, we do not
need to make a call to the super implementation */
//super.onDestory()
serviceScope.cancel()
}
}
for me worked like that
import androidx.lifecycle.lifecycleScope
class ServiceLife : LifecycleService() {
private var supervisorJob = SupervisorJob(parent = null)
override fun onCreate() {
super.onCreate()
val serviceJob = lifecycleScope.launch {
//some suspend fun
}
supervisorJob[serviceJob.key]
supervisorJob.cancel()
}
}

How can I guarantee to get latest data when I use Coroutine in Kotlin?

The Code A is from the project architecture-samples, you can see it here.
The updateTasksFromRemoteDataSource() is suspend function, so it maybe run asynchronously.
When I call the function getTasks(forceUpdate: Boolean) with the paramter True, I'm afraid that return tasksLocalDataSource.getTasks() will be fired before updateTasksFromRemoteDataSource().
I don't know if the Code B can guarantee return tasksLocalDataSource.getTasks() will be fired after updateTasksFromRemoteDataSource().
Code A
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
// Set app as busy while this function executes.
wrapEspressoIdlingResource {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource()
} catch (ex: Exception) {
return Result.Error(ex)
}
}
return tasksLocalDataSource.getTasks()
}
}
private suspend fun updateTasksFromRemoteDataSource() {
val remoteTasks = tasksRemoteDataSource.getTasks()
if (remoteTasks is Success) {
// Real apps might want to do a proper sync, deleting, modifying or adding each task.
tasksLocalDataSource.deleteAllTasks()
remoteTasks.data.forEach { task ->
tasksLocalDataSource.saveTask(task)
}
} else if (remoteTasks is Result.Error) {
throw remoteTasks.exception
}
}
...
}
Code B
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
// Set app as busy while this function executes.
wrapEspressoIdlingResource {
coroutineScope {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource()
} catch (ex: Exception) {
return Result.Error(ex)
}
}
}
return tasksLocalDataSource.getTasks()
}
}
...
}
Added Content
To Tenfour04: Thanks!
If somebody implement updateTasksFromRemoteDataSource() with lauch just like Code C, are you sure the Code C is return tasksLocalDataSource.getTasks() will be fired after updateTasksFromRemoteDataSource() when I call the function getTasks(forceUpdate: Boolean) with the paramter True?
Code C
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
// Set app as busy while this function executes.
wrapEspressoIdlingResource {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource()
} catch (ex: Exception) {
return Result.Error(ex)
}
}
return tasksLocalDataSource.getTasks()
}
}
private suspend fun updateTasksFromRemoteDataSource() {
val remoteTasks = tasksRemoteDataSource.getTasks()
if (remoteTasks is Success) {
// Real apps might want to do a proper sync, deleting, modifying or adding each task.
tasksLocalDataSource.deleteAllTasks()
launch { //I suppose that launch can be fired
remoteTasks.data.forEach { task ->
tasksLocalDataSource.saveTask(task)
}
}
} else if (remoteTasks is Result.Error) {
throw remoteTasks.exception
}
}
}
New Added Content
To Joffrey: Thanks!
I think that the Code D can be compiled.
In this case, when forceUpdate is true, tasksLocalDataSource.getTasks() maybe be run before updateTasksFromRemoteDataSource() is done.
Code D
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private val myCoroutineScope: CoroutineScope
) : TasksRepository {
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
// Set app as busy while this function executes.
wrapEspressoIdlingResource {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource(myCoroutineScope)
} catch (ex: Exception) {
return Result.Error(ex)
}
}
return tasksLocalDataSource.getTasks()
}
}
private suspend fun updateTasksFromRemoteDataSource(myCoroutineScope: CoroutineScope) {
val remoteTasks = tasksRemoteDataSource.getTasks()
if (remoteTasks is Success) {
// Real apps might want to do a proper sync, deleting, modifying or adding each task.
tasksLocalDataSource.deleteAllTasks()
myCoroutineScope.launch {
remoteTasks.data.forEach { task ->
tasksLocalDataSource.saveTask(task)
}
}
} else if (remoteTasks is Result.Error) {
throw remoteTasks.exception
}
}
...
}
suspend functions look like regular functions from the call site's point of view because they execute sequentially just like regular synchronous functions.
What I mean by this is that the instructions following a plain call to a suspend function do not execute until the called function completes its execution.
This means that code A is fine (when forceUpdate is true, tasksLocalDataSource.getTasks() will never run before updateTasksFromRemoteDataSource() is done), and the coroutineScope in code B is unnecessary.
Now regarding code C, structured concurrency is here to save you.
People simply cannot call launch without a CoroutineScope receiver.
Since TaskRepository doesn't extend CoroutineScope, the code C as-is will not compile.
There are 2 ways to make this compile though:
Using GlobalScope.launch {}: this will cause the problem you expect, indeed. The body of such a launch will be run asynchronously and independently of the caller. updateTasksFromRemoteDataSource can in this case return before the launch's body is done. The only way to control this is to use .join() on the Job returned by the call to launch (which waits until it's done). This is why it is usually not recommended to use the GlobalScope, because it can "leak" coroutines.
wrapping calls to launch in a coroutineScope {...} inside updateTasksFromRemoteDataSource. This will ensure that all coroutines launched within the coroutineScope block are actually finished before the coroutineScope call completes. Note that everything that's inside the coroutineScope block may very well run concurrently, though, depending on how launch/async are used, but this is the whole point of using launch in the first place, isn't it?
Now with Code D, my answer for code C sort of still holds. Whether you pass a scope or use the GlobalScope, you're effectively creating coroutines with a bigger lifecycle than the suspending function that starts them.
Therefore, it does create the problem you fear.
But why would you pass a CoroutineScope if you don't want implementers to launch long lived coroutines in the provided scope?
Assuming you don't do that, it's unlikely that a developer would use the GlobalScope (or any scope) to do this. It's generally bad style to create long-lived coroutines from a suspending function. If your function is suspending, callers usually expect that when it completes, it has actually done its work.

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
}