This message was deleted.
# compose
s
This message was deleted.
j
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow talks about difference in behaviour between LiveData and StateFlow in this scenario •
LiveData.observe()
 automatically unregisters the consumer when the view goes to the 
STOPPED
 state, whereas collecting from a 
StateFlow
 or any other flow does not.
t
collectAsState will keep track of livecycle for you.
j
I'm seeing in this case that flow is still active when app goes in to background
t
What do you mean with background?
j
home button vs say back button
l
it will cancel when that location in composition is disposed
j
the link above (for pre-compose UI) talks about having to explicitly cancel in
onStop()
not sure if there's something equivalent we can/should do in compose world
l
collectAsState AFAIK uses rememberCoroutineScope which is a coroutinescope which will get cancelled automatically whenever it is removed from the slot table (which is when that location gets disposed)
i don’t have my IDE open at the moment so i can’t verify this, but i’m pretty sure this is the case
l
@John O'Reilly You can use the
flatMapLatest
operator on the
Lifecycle.isStartedFlow()
and bring your flow when started, and
emptyFlow()
otherwise. The
Lifecycle.isStartedFlow()
I'm referring to is documented and available here: https://github.com/LouisCAD/Splitties/tree/main/modules/lifecycle-coroutines
⏸️ 1
l
OK, i’m part right, part wrong
LaunchedEffect is fundamentally what is being used here, which implements a CompositionLifecycleObserver which will cancel the job as soon as it leaves the slot table
so cancellation will occur when i said
but it uses the applyCoroutineContext by default
j
how does that map then to say activity going in to STOPPED state?
t
Stopped state of activity will not change anything. Destroyed is interessting maybe. But your compose views should be disposed before the activity is destroyed
l
it depends on whether or not you have any code that disposes your composition in onStop
but i think typically it will mean nothing happens in this case
but i think might be different if you use the navigation AAC w/ compose
j
so, sounds like this t something app needs to explicitly hook in to right now (to trigger cancellation) (as per that article above)
l
or rather, I think NavHost might dispose the screen in this case. not sure
j
in this example I'm just testing with one screen (no navigation) and then hitting home button (or equivalent) to put app in background
l
right now i think that is correct, yes. we might want to look into different default behavior for onStop
though i should verify that right now as well, one sec
we’ve been discussing some related questions
for instance, when we have a composition of a row of LazyColumn that is off screen, should we dispose it or should there be some “stopped” like state. and should recompositions happen or not, etc.
👀 3
i
(PSA: Navigation does nothing in response to onStop)
j
fwiw I've pushed changes to following branch that demonstrate this scenario (though probably easy enough to replicate anyway) https://github.com/joreilly/PeopleInSpace/tree/iss_position_poll
Repository exposes api that returns flow that polls periodically (for ISS Position), in ViewModel that's converted to StateFlow (using stateIn()) and then compose code uses colllectAsState()
looks like
collectAsState()
can be passed a
CoroutineContext
...wondering if that could perhaps be used to influence behaviour in this scenario
i
Keep in mind that when you use
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
, you'll always be collecting on the upstream flow as long as the
ViewModel
exists, no matter what state / how many collectors you have downstream from that - whether you're stopped, started, in the background, etc. won't matter
You'd need to use
SharingStarted.WhileSubscribed
if you want your upstream collecting to be cancelled when your subscribers go away: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-sharing-started/-while-subscribed.html
and only then would it matter whether you cancel when stopped vs cancelled when destroyed/disposed
j
ah, interesting, thanks....I'd recently started using
stateIn
but didn't pay enough attention to that param!
regarding "it depends on whether or not you have any code that disposes your composition in onStop" Is that something that I can influence right now?
It also feels like whatever is settled on in terms of lifecycle/scope related behaviour here has implications for value (or not) of continuing to use AAC
ViewModel
I tried to capture my notes on this behaviour (and resulting changes I made to a sample project I have) in following https://johnoreilly.dev/posts/jetpack-compose-stateflow-livedata/. @Leland Richardson [G] @Ian Lake it would be great if you could cast an eye over it if you have a chance in case any glaring errors (or anything you think needs clarifying)
l
@John O'Reilly I don't know if you saw my mention earlier where I was talking about another approach, but here's what I had in mind exactly, which I believe makes collecting a flow while the lifecycle is started, easier:
Copy code
import androidx.lifecycle.Lifecycle
import kotlinx.coroutines.flow.*
import splitties.lifecycle.coroutines.isStartedFlow

fun <T> Flow<T>.whileStarted(lifecycle: Lifecycle): Flow<T> {
    return lifecycle.isStartedFlow().flatMapLatest { isStarted ->
        if (isStarted) this else emptyFlow()
    }
}
With that, you can then at use site simply do this:
Copy code
someFlow.whileStarted(lifecycle).collectAsState()
You can even go one step further and make an extension named
collectAsStateWhileStarted(lifecycle)
. I find this neater and more scalable than tangling your composable code with the activity bound
onStart
and
onStop
where you keep the
Job
in a
var
. What do you think?
Also, maybe a
collectAsStateWhileStarted
@Composable
function might not even need the
Lifecycle
parameter if it can be retrieved via the ambients.
j
@louiscad changes I ended up making were to move from using StateFlow in ViewModel to use asLiveData() and then observeAsState() in Compose code....this has provided exactly behaviour I needed (ensuring I don't keep polling if app goes in to background). I'm also a little reluctant to pull in another library for this if possible but will take a closer look at splitties.
l
You can copy paste what is in Splitties, the
isStartedFlow
function fits in a small file with only dependencies to kotlinx.coroutines and AndroidX Lifecycle.
j
k, thanks...will definitely take a look...be great though if this was behaviour that could be included in Compose (assuming it made sense)
l
I think the best would be to have it in a platform independent way (not relying on androidx.lifecycle` so it can work on other platforms such as #compose-desktop)
j
yeah, that's a good point too
l
That means Compose would need to abstract the different kinds of lifecycles. I'd think the paused/resumed and started/stopped states of Android translate well to desktop where part of the window is visible or not, but folks might want more granular control though. I'm wondering if there's already been considerations about that and API desing concerns.