https://kotlinlang.org logo
s

Stylianos Gakis

06/15/2023, 12:05 PM
I got a StateFlow, built using
stateIn(scope, WhileSubscribed(), ...)
so that it’s live only when there are subscribers, and it starts collecting the flow each time there’s a new subscriber after it was “not live” for any period of time. While this is live, I want to also be running another coroutine, which will do some networking, doesn’t matter, what does matter is that I need to be in a suspend context. What I’ve done now is basically
Copy code
combine(
  flow<Unit> { emit(Unit) Must emit Unit so the combine doesn't get stuck waiting for this flow to emit something ; here I do this work that I mention },
  someFlowINeedTheResultFrom<Int>(),
  someFlowINeedTheResultFrom#2<String>(),
) { _, someInt, someString ->
  // handle the two things I need, simply ignore the first item
}.stateIn(scope, WhileSubscribed(), ...)
But it feels really awkward to put it in the combine just to get the right scope for when it should run, while ignoring the result. Another thing I considered was something like
Copy code
combine(
  someFlowINeedTheResultFrom<Int>(),
  someFlowINeedTheResultFrom#2<String>(),
) { _, someInt, someString ->
  // handle the two things I need, simply ignore the first item
}
.onStart {
  flow { here I do this work that I mention }.collect {
    // <---
  },
}
.stateIn(scope, WhileSubscribed(), ...)
But now while I am on a suspending context, it’s actually blocking the first emission, since it will try to run to a finish before the result of my combine will emit something. I’d optimally want this to be a
launch
in there, but I can’t do that either since I am not inside some sort of
CoroutineScope
, but just have access to the CoroutineContext of the flow, where I can’t do such launches, while still making sure that it’s cancelled when the last subscriber drops. Any thoughts on this? Is my first approach my best bet here?
a

alex.krupa

06/15/2023, 3:21 PM
but I can't do that either since I am not inside some sort of
CoroutineScope
You can use the coroutineScope builder to create a scope with parent's
CoroutineContext
.
s

Stylianos Gakis

06/15/2023, 3:23 PM
This is still suspending, meaning it will wait for the child coroutines to finish before it exits. In this case, it’d mean that onStart {} would never finish and therefore the original flow will never emit anything, since the work I do is collecting on an infinite flow.
u

律素

06/15/2023, 4:43 PM
In the second solution, you can use
viewModelScope.launch (<http://Dispatchers.IO|Dispatchers.IO>) {}
in
onStart{}
or use SharedFlow to emit values in
onStart{}
and
onCompletion{}
to notify the start and end of another flow.
s

Stylianos Gakis

06/15/2023, 7:11 PM
If I do vMS.launch it won't be cancelled when an observer no longer is observing my original StateFlow. Keeping a job reference locally and starting it onStart, and canceling (and making it null) on completion does sound possible yeah, but definitely even worse than this combine thing in my opinion 🤔 Also I wonder if onCompletion is called in this particular case of all the observers being dropped. I wouldn't be 100% sure, since completion in my mind means the flow has completed sending all events. I don't know if stateIn() calls that too when it stops being hot.
u

律素

06/16/2023, 6:23 AM
Also I wonder if onCompletion is called in this particular case of all the observers being dropped.
OnCompletion
is called when the flow emits all events or the subscriptionCount of flow is changed to 0. And the stateIn() with WhileSubscribed() makes the flow restart when the subscriptionCount changes from 0 to a value gt 0. Using
onStart
and
onCompletion
can indeed make the problem more complex. Your first solution can solve this problem, and I think
merge
can be used to replace the `emit(Unit)`:
Copy code
val stateFlow = merge(
    flow<Unit> { /* do your work */ },
    combine(
        flowOf(0, 1, 2),
        flowOf("A", "B", "C")
    ) { a, b ->
        // handle the two things
    }
).stateIn(scope, SharingStarted.WhileSubscribed(), initialValue)
m

myanmarking

06/16/2023, 8:04 AM
Hacky solution. Use channel flow, launch your condition but emit the single value immediatly
3 Views