How can I have the flow collector below receive "hello"? The collector is calling myFunction1() which in turn calls myFunction2(). Both are suspend functions.
Currently nothing happens when I hit run and no flow is received. Am I missing something here?
CoroutineScope(IO).launch {
val flowCollector = repo.myFunction1()
.onEach { string ->
Log.d("flow received: ", string)
}
.launchIn(GlobalScope)
}
class Repo {
suspend fun myFunction1(): Flow<String> = flow {
/*some code*/
myFunction2()
}
suspend fun myFunction2(): Flow<String> = flow {
/*some code*/
emit("hello")
}
}
You can try to use emitAll function for your case:
fun myFunction1(): Flow<String> = flow {
/*some code*/
emitAll(myFunction2())
}
fun myFunction2(): Flow<String> = flow {
/*some code*/
emit("hello")
}
emitAll function collects all the values from the Flow, created by myFunction2() function and emits them to the collector.
And there is no reason to set a suspend modifier before each function, flow builder isn't suspend.
Unless you have a very specific reason the functions returning a Flow from your repo shouldn't be suspending (As the flow{} builder isn't suspending). Since the suspending operation is collecting (waiting for values to come out of it).
From the code you've provided you're looking for the flatMapLatest function. Docs here
class Repo {
fun function1() =
flow {
val value = doSomething()
emit(value)
}
.flatMapLatest { emittedValue -> function2() }
fun function2() = flow {...}
}
Related
So I have the below kotlin code with flows and suspend function.
suspend fun doWork(): Int {
// do some time intensive task
return if (task.isSuccessful){1}else{-1}
}
suspend fun sendTaskStatus(status:Int): Boolean {
// send this status data to the server via internet
return task.isSuccessful()
}
fun listenForCompletion() = flow <String> {
val isTaskCompleted = false
val taskStatus = doWork()
if (taskStaus == 1){
val isDataSent = sendTaskStatus()
if(isDataSent)
isTaskCompleted = true
}
emit(isTaskCompleted)
}
Now my question is if I collect listenForCompletion() function from a coroutine, what will be the sequence of execution of this code? If this isn't correct what would be the correct way of doing this assuming that flow and suspend functions are preserved?
I have my code below
interface Listener {
fun onGetData(data: Int)
fun onClose()
}
class MyEmitter {
var listener: Listener? = null
fun sendData(data: Int) = listener?.onGetData(data)
fun close() = listener?.onClose()
}
fun handleInput(myEmitter: MyEmitter) = channelFlow {
myEmitter.listener = object:Listener {
override fun onGetData(data: Int) { trySend(data) }
override fun onClose() { close() }
}
}
fun main(): Unit = runBlocking {
val myEmitter = MyEmitter()
handleInput(myEmitter).collect {
println(it)
}
myEmitter.sendData(1)
myEmitter.sendData(2)
myEmitter.close()
}
Whenever I send the data e.g. myEmitter.sendData(1), it does get into trySend(data), but the result is closed.
Why is it closed? How can I keep it open?
I think it's not documented terribly clearly, but just like the flow builder, the channelFlow's Flow is considered complete once the suspend lambda returns. Since all you are doing is setting a listener and not waiting around, it will return almost immediately. When a channel Flow is completed, it's channel is also closed.
If you want your channelFlow to stay open until the Flow is canceled, call awaitClose() at the end. This function suspends until the channel is closed, so it will hold your Flow open until it's canceled or the event in your listener closes the Channel.
fun handleInput(myEmitter: MyEmitter) = channelFlow {
myEmitter.listener = object:Listener {
override fun onGetData(data: Int) { trySend(data) }
override fun onClose() { close() }
}
awaitClose()
}
If you are familiar with callbackFlow, it is a specialized version of channelFlow and it enforces the awaitClose() call because it is meant for waiting for a listener, so there's no reason you would ever not want to await. It's also where you can deregister any listener you created inside the flow builder.
To get this working, I did 3 things
Add awaitClose to ensure the flow is not terminated
Move the entire flow behind launch, so that it is not blocking the main() function flow
Add a little delay before myEmitter.sendData(1), so that to ensure the launch get triggered first before doing the external sendData.
Full changed code as below
interface Listener {
fun onGetData(data: Int)
fun onClose()
}
class MyEmitter {
var listener: Listener? = null
fun sendData(data: Int) = listener?.onGetData(data)
fun close() = listener?.onClose()
}
fun handleInput(myEmitter: MyEmitter) = channelFlow {
myEmitter.listener = object:Listener {
override fun onGetData(data: Int) { trySend(data) }
override fun onClose() { close() }
}
awaitClose { myEmitter.listener = null } // Need awaitClose to keep the flow alive
}
fun main(): Unit = runBlocking {
val myEmitter = MyEmitter()
launch { // Need to run to avoid it from blocking the main() function flow due to having `awaitClose` there
handleInput(myEmitter).collect {
println(it)
}
}
delay(100) // Add some delay to get this triggered after the launch run
myEmitter.sendData(1)
myEmitter.sendData(2)
myEmitter.close()
}
The 3rd step is a little hack I think.
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
}
Consider an asynchronous API that reports progress on its operations:
suspend fun operationWithIO(input: String, progressUpdate: (String) -> Unit): String {
withContext(Dispatchers.IO) {
// ...
}
}
Is it possible to implement calls to progressUpdate such that callbacks are handled on the caller's dispatcher? Or is there a better way to deliver status updates back to the caller?
You should send progress updates on a channel. That will allow the caller to listen to the channel using whatever dispatcher it wants.
suspend fun operationWithIO(input: String, progressChannel: Channel<String>): String {
withContext(Dispatchers.IO) {
// ...
progressChannel.send("Done!")
progressChannel.close()
}
}
The caller can use it by doing something like this:
val progressChannel = Channel<String>()
someScope.launch {
operationWithIO(input, progressChannel)
}
// Remember the call to progressChannel.close(), so that this iteration stops.
for (progressUpdate in progressChannel) {
println(progressUpdate)
}
How about wrapping the callback function and calling the wrapped function:
/** Return a new callback that invokes this callback on the current context. */
suspend fun <T> ((T) -> Unit).onCurrentContext(): (T) -> Unit =
coroutineContext.let { context ->
{ value: T ->
runBlocking {
launch(context) {
this#onCurrentContext.invoke(value)
}
}
}
}
/** Perform a background operation, delivering status updates on the caller's context. */
suspend fun operationWithIO(statusUpdate: (String) -> Unit): String {
val cb = statusUpdate.onCurrentContext()
return withContext(Dispatchers.IO) {
cb("Phase 1")
delay(150)
cb("Phase 2")
delay(150)
"Result"
}
}
// In use
runBlocking {
val result = operationWithIO {
println("received callback status $it")
}
println("result is $result")
}
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.