CoroutineScope extension function in a different class - kotlin

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

Related

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

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.

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
}

Mockk: Verify method called within coroutine

I have a simple object which provides a suspend function to simulate a delaying network request and afterwards calls another method from the object.
class CoroutinesObject {
suspend fun doApiCall() {
delay(1000)
println("Hello from API")
val apiResult = "result #1"
callMe(apiResult)
}
fun callMe(result: String) {
println("[${Thread.currentThread().name}] call me with result: $result")
}
}
I would like to write a simple test which should verify that the method callMe has been called.
class CoroutinesTest {
#Test
fun doApiCall_callsCallMe() {
val obj = CoroutinesObject()
runBlocking {
obj.doApiCall()
}
coVerify { obj.callMe("result #1") }
}
}
Unfortunately the test fails with the following exception and I'm not sure why this happens.
io.mockk.MockKException: Missing calls inside verify { ... } block.
Anybody got an idea whats the problem and how to write a test which is able to verify the called method?
Okay, it seems as if a missing mock for my object was the problem. The following test works:
#Test
fun doApiCall_callsCallMe() {
val obj = spyk(CoroutinesObject())
runBlocking {
obj.doApiCall()
}
coVerify { obj.callMe(any()) }
}

How would I "wrap" this not-quite-"by lazy" result caching function call in idiomatic Kotlin?

I can't use "by lazy" because the callbacks require suspendCoroutine, which borks in android if it blocks the main thread, so I have to use the following "cache the result" pattern over and over. Is there a way to wrap it in a funButUseCachedResultsIfTheyAlreadyExist pattern to encapsulate the xCached object?
private var cameraDeviceCached: CameraDevice? = null
private suspend fun cameraDevice(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
... deep callbacks with cont.resume(camera) ...
}.also {
cameraDeviceCached = it
}
}
When what I'd really like to write is
private suspend fun cameraDevice(): CameraDevice = theMagicFunction { cont ->
... deep callbacks with cont.resume(camera) ...
}
You can build a generalized solution by wrapping an async call as follows:
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart.LAZY
class LazySuspendFun<out T>(
scope: CoroutineScope,
private val block: suspend () -> T
) {
private val deferred = scope.async(Dispatchers.Unconfined, LAZY) { block() }
suspend operator fun invoke() = deferred.await()
}
fun <T> CoroutineScope.lazySuspendFun(block: suspend () -> T) =
LazySuspendFun(this, block)
This is a simple example of how you can use it. Note that we are able to compose them so that we use a lazy-inited value as a dependency to getting another one:
val fetchToken = lazySuspendFun<String> {
suspendCoroutine { continuation ->
Thread {
info { "Fetching token" }
sleep(3000)
info { "Got token" }
continuation.resume("hodda_")
}.start()
}
}
val fetchPosts = lazySuspendFun<List<String>> {
val token = fetchToken()
suspendCoroutine { continuation ->
Thread {
info { "Fetching posts" }
sleep(3000)
info { "Got posts" }
continuation.resume(listOf("${token}post1", "${token}post2"))
}
}
}
On the calling side you must be inside some coroutine context so you can call the suspending functions:
myScope.launch {
val posts = fetchPosts()
...
}
This solution is robust enough that you can concurrently request the value several times and the initializer will run only once.
I'll write this as an answer, since it's not possible to post much code in comments.
What you're looking for is something like this:
private suspend fun cameraDevice() = theMagicFunction {
CameraDevice()
}()
suspend fun theMagicFunction(block: ()->CameraDevice): () -> CameraDevice {
var cameraDeviceCached: CameraDevice? = null
return fun(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
cont.resume(block())
}.also {
cameraDeviceCached = it
}
}
}
Unfortunately, this will not compile, since closures cannot be suspendable, and neither are local functions.
Best I can suggest, unless I miss a solution there, is to encapsulate this in a class, if this variable bothers you too much.

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