Unable to Execute code after Kotlin Flow collect - kotlin

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
}

Related

Catching an error of a coroutine launch call

in the following code:
private fun executeCognitoRequest(result: MethodChannel.Result, block: suspend CoroutineScope.() -> Any?) {
try {
CoroutineScope(Dispatchers.Default).launch {
val requestResult = block()
withContext(Dispatchers.Main) {
result.success(requestResult)
}
}
} catch (exception: Exception) {
val cognitoErrorType = CognitoErrorType.getByException(exception)
result.error(cognitoErrorType.code, null, null)
}
}
if the call to block throws, will it be caught?
It will be caught, but the problem with your code is that you violate the principles of structured concurrency and launch a coroutine in the GlobalScope. So if you test your code from a main function like this:
fun main() {
runBlocking {
executeCognitoRequest(MethodChannel.Result()) {
funThatThrows()
}
}
}
the whole program will end before the coroutine has completed execution.
This is how you should write your function:
private fun CoroutineScope.executeCognitoRequest(
result: MethodChannel.Result,
block: suspend CoroutineScope.() -> Any?
) {
try {
launch(Dispatchers.IO) {
val requestResult = block()
withContext(Dispatchers.Main) {
result.success(requestResult)
}
}
} catch (exception: Exception) {
val cognitoErrorType = CognitoErrorType.getByException(exception)
result.error(cognitoErrorType.code, null, null)
}
}
Now your function is an extension on CoroutineScope and launch is automatically called with that receiver. Also, for blocking IO calls you shouldn't use the Default but the IO dispatcher.
However, I find your higher-level design weird, you start from blocking code and turn it into async, callback-oriented code. Coroutines are there to help you get rid of callbacks.

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

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

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