I’m debugging `collectAsState()` vs `collectAsStat...
# compose
u
I’m debugging
collectAsState()
vs
collectAsStateWithLifecycle()
on a viewmodel state in a app where screens are fragments. I found out, surprisingly, that they behave the same. If I push a new fragment, then the current fragment’s view gets destroyed, therefore compose “instance” there dies, and nothing is collected (observed), so no need for the
withLifecycle
Guess that is nice and ultimately expected. However, what I found as well, is that the state collection stops as well when app goes to background (still using
collectAsState, not the withLifecycle
How is this possible? Does compose have some implicit hooks into the
Activity
? Since in this case the fragment’s view does not get destroyed
m
Recompositions stop when the app goes to the background (that’s why you don’t see updates on the screen or logs saying that the composable recomposed) but the underlying flow collection is kept active. Flows still emit in the background when
collectAsState
is used. If you use
collectAsStateWithLifecycle
the flow collection stops until the app is in the foreground again
u
okay so the subscription is kept open, but there is some check downstream “naively” like this
Copy code
collect {
	if (inForeground) {
		recompose(it)
	}
}
if so, does it really matter, when the backing flow is a mutablestateflow, which is hot, not cold?
m
If the backing flow is hot, it doesn't matter which API is used. However, it might not be a good idea to rely on implementation details as those could change in the future
u
hmm, thats true
btw am I correct on the fragment transition assumption? that I don’t see recomposition due to the fragment view being destroyed when replacing the fragment with next one?
m
Not sure how you're using
withLifecycle
but it's a good practice to use the fragment's view lifecycle when triggering coroutines
u
nn I mean when my app is still based on Fragments for screens, however now I’m turning the xml view definitions of a given Fragment to compose, i.e. compose is a impl detail of a FooFragment class
and when I push BarFragment (`fragmentManager.replace*), then I see recompositions stop as well, same way as if the app when to background, which we just discussed
so, am I correct in thinking, that this is due to FooFragment’s view being destoyed, due to using
fragmentManager.replace
,(as opposed to
fragmentManager.add
?
also, how does compose know it’s on background? does it have som implicit hooks to Activity lifecycle? (lowercase L)
m
Yes, it has some Android-specific hooks
u
do you know where exactly in compose source?
is it this?
d
hi manuel - i am having an issue where the state is being collected even though the lifecycle state is lower than minactive state
sample code below
Copy code
val homeState by viewModel.homeState.collectAsStateWithLifecycle(
        viewLifecycleOwner,
        Lifecycle.State.RESUMED,
    )

    val context = LocalContext.current
    val nextScreen = homeState.nextScreen
    val fragmentManager = getFragmentManagerFromContext()

    Timber.i("lifecyclestate is ${viewLifecycleOwner.lifecycle.currentState}")
    Timber.i("home is $nextScreen")
these are the logs
Copy code
2023-01-13 11:53:07.788 17057-17103 FA                      co.well.wellapp.debug                V  Activity paused, time: 6877042
2023-01-13 11:53:07.928 17057-17057 HomeFragmentKt          co.well.wellapp.debug                I  lifecyclestate is CREATED
2023-01-13 11:53:07.929 17057-17057 HomeFragmentKt          co.well.wellapp.debug                I  home is Deeplink(deeplink=<wellapp://open/journeys>)
you can see that even though I have minActiveState set to
RESUMED
collection is occurring while lifecycle state is
CREATED
unless I am misunderstanding fundamental about how
collectAsStateWithLifecycle
works
this is how I am calling the composable
Copy code
class HomeFragment : BaseFragment() {

    override val eventScreenName: Event.Screen = Event.Screen.Home
    private val viewModel: HomeViewModel by viewModel()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        viewModel.verifyNextScreen()
        return ComposeView(requireContext()).apply {
            setContent {
                HomeRoute(
                    viewModel = viewModel,
                    navController = findNavController()
                )
            }
        }
    }
}
u
dude
d
yea - am i misunderstanding things?
u
make your own thread, and, youre not supposed to tag team members
d
think its because the initial value is always emitted regardless of minActiveState - sorry for the noise ❤️ 🍕 🦜
279 Views