How to run EventListener in background in a Spring/Kotlin Coroutines project - kotlin

Currently I am using Spring 2.6.7 + WebFlux/Kotlin Coroutines stack in a project.
In a Java project, it is easy to run the EventListener in an async thread like this.
#Component
class MyEventListener{
#EventListener
#Async
void onOrderPlaced(event) {
}
}
But in the Kotlin Coroutines, the #Async does not work, and the EventListener dose not accept a suspend fun.
#Component
class MyEventListener{
#EventListener
#Async //does not work
fun onOrderPlaced(event) {
}
#EventListener
suspend fun onOrderPlaced(event) { // `suspend` does not work
}
#EventListener
fun onOrderPlaced(event) = runBlocking { // works, but did not run this continuation in background like `#Async`
}
}

One way I can think is to use application specific coroutine context in your application configuration.
#Configuration
class ApplicationConfiguration {
private val applicationCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
#Bean
fun applicationScope(): CoroutineScope = applicationCoroutineScope
}
and then
#Component
class MyEventListener(private val applicationScope: CoroutineScope) {
#EventListener
fun onOrderPlaced(event) {
applicationScope.launch {
// Do something with the event.
}
}
}
Something around that line should work for your use case.

Related

Unable to Execute code after Kotlin Flow collect

I'm trying to execute some code after calling collect on a Flow<MyClass>. I'm still kind of new to using Flows so I don't understand why the code after the function doesn't get called.
How I use the Flow:
incidentListener = FirebaseUtils.databaseReference
.child(AppConstants.FIREBASE_PATH_AS)
.child(id)
.listen<MyClass>() //This returns a Flow<MyClass?>?
How I consume the Flow:
private suspend fun myFun() {
viewmodel.getListener()?.collect { myClass->
//do something here
}
withContext(Dispatchers.Main) { updateUI() } //the code never reaches this part
}
How myFun() is called:
CoroutineScope(Dispatchers.IO).launch {
myFun()
}
As far as what I've tried to make it work I've tried closing the coroutine context and it didn't work. I'm assuming Flows work differently than regular coroutines.
Update:
I'm listening through Firebase using this block of code. I don't know if it'll help but maybe the way I implemented it is causing the issue?
inline fun <reified T> Query.listen(): Flow<T?>? =
callbackFlow {
val valueListener = object : ValueEventListener {
override fun onCancelled(databaseError: DatabaseError) {
close()
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
try {
val value = dataSnapshot.getValue(T::class.java)
offer(value)
} catch (exp: Exception) {
if (!isClosedForSend) offer(null)
}
}
}
addValueEventListener(valueListener)
awaitClose { removeEventListener(valueListener) }
}
collect is a suspending function, the code after collect will only run once the flow completes.
Launch it in a separate coroutine:
private suspend fun myFun() {
coroutineScope {
launch {
viewmodel.getListener()?.collect { myClass->
//do something here
}
}
withContext(Dispatchers.Main) { updateUI() } //the code never reaches this part
}
}
I forgot to post my own answer to this. I've found the problem before. It's because I wasn't returning the Coroutine Context.
My code has been updated since but with the code above as an example it should be written as follows:
private suspend fun myFun() {
viewmodel.getListener()?.collect { myClass->
//do something here
return#collect
}
withContext(Dispatchers.Main) { return#withContext updateUI() }
//the code should flow downwards as usual
}

Unit testing class with own coroutine scope

I have a class that looks something like
class Foo {
private val scope = Job() + Dispatchers.IO
val emissions = PublishSubject.create<Bar>()
fun doSomething() {
scope.launch {
// Do a whole bunch of work...
withContext(Dispatchers.Main) emissions.onNext(bar)
}
}
}
And I'm trying to come up with a way to unit test it. I've tried making the scope injectable and writing something like
#Test fun testFoo() = runBlockingTest {
Dispatchers.setMain(TestCoroutineDispatcher())
val foo = Foo(this)
foo.doSomething()
foo.emissions.assertStuff()
}
but that doesn't seem to work. The assertion happens before the coroutine inside doSomething() has finished.
I've also tried making this dispatchers injectable, providing Dispatchers.Unconfined, but that didn't help either. Is there something wrong with this approach?
If you are able to expose the job for public API, you can try
class Foo {
private val scope: CoroutineScope = CoroutineScope(Job() + Dispatchers.IO)
val emissions = PublishSubject.create<Bar>()
fun doSomething() = scope.launch {
// Do a whole bunch of work...
withContext(Dispatchers.Main) { emissions.onNext(bar) }
}
}
class Test {
private val testFoo = Foo()
private val testObserver: TestObserver<Bar> = TestObserver.create()
#BeforeEach
fun setUp() {
testFoo.emissions.subscribe(testObserver)
}
#Test fun testFoo() {
runBlockingTest {
Dispatchers.setMain(TestCoroutineDispatcher())
testFoo.doSomething().join()
testObserver.assertValue(bar)
}
}
}
Testing asynchronous code can be done well with this library: Awaitility (or its kotlin extension)
You would write something like:
#Test fun testFoo() {
val foo = Foo()
foo.doSomething()
await().atMost(5, MILLIS).until(/* your check like - foo.emissions.onNext(bar) */);
}

CoroutineScope extension function in a different class

I'm trying to use an extension function to CoroutineScope to launch some asynchronous work.
I'm not sure how to call this method from my main class, see below:
class MyService {
fun CoroutineScope.getFoo() = async(IO|Single|Default) { ... }
}
class MyProgram(val service : MyService) : CoroutineScope {
fun main() {
launch {
// Doesn't work, unresloved `service.getFoo`.
val deferred = service.getFoo() getFoo
// Works, but looks a bit odd IMO.
val deferred = with(service) { getFoo() }
deferred.await()
}
}
}
I know I could just move the async {} keyword to my main method, but in this way, the caller would have to decide the scheduler.
The service knows the nature of its work (IO/Computation bound single-threaded?, etc) and I think it should be the one deciding the scheduler.
As far as I understand your intent is to let the service specify the scheduler. Why not split the specification of the scheduler and the decision to run asynchronously?
Let the service function be suspendable and use withContext to specify the scheduler.
And let the caller decide, if the function should run asynchronously.
class MyService {
suspend fun getFoo() = withContext(Dispatchers.IO) {
//work
}
}
abstract class MyProgram(val service: MyService) : CoroutineScope {
fun main() {
launch {
val deferred = async { service.getFoo() }
//some work
deferred.await()
}
}
}
Why not make getFoo a normal function and pass in the scope:
fun getFoo(scope: CoroutineScope) = scope.async {
//work }
}
launch {
service.getFoo(this)
}

Wait for service to be bound using coroutines

So I have a method that binds to the service.
fun bindService() {
val intent = Intent(this, BluetoothService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
Inside onCreate method I use this code:
bindService()
launch {
delay(500L)
service = serviceConnection.serviceBinder?.getService() as BluetoothService
}
Is there more elegant way to wait for the service to be bound than using delay()?
I wrote this just now, and haven't tried it, but hopefully something like it could work. The magic is in suspendCoroutine, which pauses the current coroutine and then gives you a continuation thingy you can use to resume it later. In our case we resume it when the onServiceConnected is called.
// helper class which holds data
class BoundService(
private val context: Context,
val name: ComponentName?,
val service: IBinder?,
val conn: ServiceConnection) {
fun unbind() {
context.unbindService(conn)
}
}
// call within a coroutine to bind service, waiting for onServiceConnected
// before the coroutine resumes
suspend fun bindServiceAndWait(context: Context, intent: Intent, flags: Int) = suspendCoroutine<BoundService> { continuation ->
val conn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
continuation.resume(BoundService(context, name, service, this))
}
override fun onServiceDisconnected(name: ComponentName?) {
// ignore, not much we can do
}
}
context.bindService(intent, conn, flags)
}
// just an example
suspend fun exampleUsage() {
val bs = bindServiceAndWait(context, intent, Context.BIND_AUTO_CREATE)
try {
// ...do something with bs.service...
} finally {
bs.unbind()
}
}

RxJava 2 overriding IO scheduler in unit test

I'm trying to test the following RxKotlin/RxJava 2 code:
validate(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { ... }
I'm attempting to override the schedulers as follows:
// Runs before each test suite
RxJavaPlugins.setInitIoSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
However, I get the following error when running the test:
java.lang.ExceptionInInitializerError
...
Caused by: java.lang.NullPointerException: Scheduler Callable result can't be null
at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
at io.reactivex.plugins.RxJavaPlugins.applyRequireNonNull(RxJavaPlugins.java:1317)
at io.reactivex.plugins.RxJavaPlugins.initIoScheduler(RxJavaPlugins.java:306)
at io.reactivex.schedulers.Schedulers.<clinit>(Schedulers.java:84)
Has anyone experienced this problem?
The test worked fine when using RxKotlin/RxJava 1 and the following scheduler overrides:
RxAndroidPlugins.getInstance().registerSchedulersHook(object : RxAndroidSchedulersHook() {
override fun getMainThreadScheduler() = Schedulers.immediate()
})
RxJavaPlugins.getInstance().registerSchedulersHook(object : RxJavaSchedulersHook() {
override fun getIOScheduler() = Schedulers.immediate()
})
I suggest you take a different approach and add a layer of abstraction to your schedulers. This guy has a nice article about it.
It would look something like this in Kotlin
interface SchedulerProvider {
fun ui(): Scheduler
fun computation(): Scheduler
fun trampoline(): Scheduler
fun newThread(): Scheduler
fun io(): Scheduler
}
And then you override that with your own implementation of SchedulerProvider:
class AppSchedulerProvider : SchedulerProvider {
override fun ui(): Scheduler {
return AndroidSchedulers.mainThread()
}
override fun computation(): Scheduler {
return Schedulers.computation()
}
override fun trampoline(): Scheduler {
return Schedulers.trampoline()
}
override fun newThread(): Scheduler {
return Schedulers.newThread()
}
override fun io(): Scheduler {
return Schedulers.io()
}
}
And one for testing classes:
class TestSchedulerProvider : SchedulerProvider {
override fun ui(): Scheduler {
return Schedulers.trampoline()
}
override fun computation(): Scheduler {
return Schedulers.trampoline()
}
override fun trampoline(): Scheduler {
return Schedulers.trampoline()
}
override fun newThread(): Scheduler {
return Schedulers.trampoline()
}
override fun io(): Scheduler {
return Schedulers.trampoline()
}
}
Your code would look like this where you call RxJava:
mCompositeDisposable.add(mDataManager.getQuote()
.subscribeOn(mSchedulerProvider.io())
.observeOn(mSchedulerProvider.ui())
.subscribe(Consumer<Quote> {
...
And you'll just override your implementation of SchedulerProvider based on where you test it. Here's a sample project for reference, I am linking the test file that would use the testable-version of SchedulerProvider: https://github.com/Obaied/DingerQuotes/blob/master/app/src/test/java/com/obaied/dingerquotes/QuotePresenterTest.kt#L31
Figured it out! It had to do with the fact that in this code:
validate(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { ... }
validate(data) was returning an Observable, which was emitting the following: emitter.onNext(null). Since RxJava 2 no longer accepts null values, flatMap was not getting called. I changed validate to return a Completable and updated the scheduler override to the following:
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
Now the tests pass!
As an alternative to proposed solutions, this has been working fine for a while in my projects.
You can use it in your test classes like this:
#get:Rule
val immediateSchedulersRule = ImmediateSchedulersRule()
And the class looks like this:
class ImmediateSchedulersRule : ExternalResource() {
val immediateScheduler: Scheduler = object : Scheduler() {
override fun createWorker() = ExecutorScheduler.ExecutorWorker(Executor { it.run() })
// This prevents errors when scheduling a delay
override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
return super.scheduleDirect(run, 0, unit)
}
}
override fun before() {
RxJavaPlugins.setIoSchedulerHandler { immediateScheduler }
RxJavaPlugins.setComputationSchedulerHandler { immediateScheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { immediateScheduler }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediateScheduler }
RxAndroidPlugins.setMainThreadSchedulerHandler { immediateScheduler }
}
override fun after() {
RxJavaPlugins.reset()
}
}
You can find a way to migrate from TestRule to ExternalResource here and get more info on testing RxJava 2 here.
This is the exact syntax that worked for me:
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline())