https://kotlinlang.org logo
#compose
Title
# compose
m

Matti MK

02/02/2022, 8:46 PM
I’m wondering if I’m doing something wrong when collecting
StateFlow
from Compose, the issue is that the state always “restarts” from initial state when switching between screens on bottom nav. This is an issue when the VM has something different than the initial state, as for some reason the Compose local state always restarts from intial state, before getting the updated state from the VM. On the VM side my flow looks as follows:
Copy code
public val viewState: StateFlow<ScreenState> = _internalState.stateIn(
        clientScope,
        SharingStarted.WhileSubscribed(),
        ScreenState(showLoading = true)
    )
And on my Compose screen collection looks like so:
Copy code
fun Screen(
    viewModel: ViewModel = getViewModel(),
) {
    val lifecycleOwner = LocalLifecycleOwner.current

    val viewStateFlow = remember(viewModel.viewState, lifecycleOwner) {
        viewModel.viewState.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
    }
    val viewState by viewStateFlow.collectAsState(ViewModel.ScreenState(showLoading = true))

    Logger.i { "VM state ${viewModel.viewState.value}" } // 1
    Logger.i { " Screen local state is ${viewState}" } // 2
A bit better explanation: the VM makes a network call upon init that eventually switches the ScreenState to
showLoading = false
. Once the screen is entered the first time the initial state is correct, but if I navigate to another screen and then back to this screen the VM state will no longer be
showLoading = true
, however, the
viewState
local to Compose Screen always restarts from the
showLoading = true
before getting the updated value from the VM. So, in the above sample prints of
// 1
and
// 2
will print out different values when entering the screen after
viewModel.viewState.value}
!= initial value.
Just to add: I’m somewhat confident the issue is not in the bottom navigation: I have a screen that doesn’t collect external state, when navigating between screens it does keep it’s internal state just fine (scroll positions etc)
Should I ignore the
StateFlowValueCalledInComposition
and set the initial value as what ever is on the VM’s state everything works just fine:
val viewState by viewStateFlow.collectAsState(viewModel.viewState.value)
I guess this is not the way to go 😅
a

Albert Chang

02/03/2022, 2:36 AM
You are not collecting a
StateFlow
.
viewStateFlow
is a
Flow
so you must specify an initial value yourself.
val viewState by viewStateFlow.collectAsState(viewModel.viewState.value)
actually makes sense if you really want to use
flowWithLifecycle
.
m

Matti MK

02/03/2022, 2:44 AM
Ah, right thanks! Somehow I missed that. I'm still pretty fresh with this, so considering I am exposing a StateFlow that I would like to collect safely (i.e. respecting lifecycles), would it then make sense to use the viewState.value or go another route? I had a bit of a hard time finding any samples or decent docs that'd shed light on this
a

Albert Chang

02/03/2022, 7:28 AM
It makes sense.
s

Stylianos Gakis

02/03/2022, 12:24 PM
Why are you doing all this ceremony inside the composable when it could all just be
val viewState by viewModel.viewState.collectAsState()
? Your VM viewState has a loading = true as a default anyway inside the
stateIn
and it by itself can launch the coroutine and change the _internalState which will automatically change the
viewState
. I must be misunderstanding something here.
m

Matti MK

02/03/2022, 1:03 PM
Thanks 👍 To be honest, I don’t quite remember where this came from, but I seem to remember here: https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda That is, converting the
StateFlow
to
Flow
in order to have lifecycle-based consuming of the flow. With that in mind, I don’t think the
val viewState by viewModel.viewState.collectAsState()
respects the consumer’s lifecycle, however, using
SharingStarted.WhileSubscribed
might have the same effect?
s

Stylianos Gakis

02/03/2022, 1:08 PM
As I understand it yes, it should respect it due to the WhileSubscribed. Under the hood it uses a
produceState
which says that it is launched when entering composition and stops when exiting composition, this is what you want to achieve is it not?
m

Matti MK

02/03/2022, 1:26 PM
Yes, it is exactly what I want 👍 thank you. I’ll give it a go
s

Stylianos Gakis

02/03/2022, 1:30 PM
And you may want to look into this article and where it mentions “Tip for Android apps!” about why you may want to add a parameter to your
WhileSubscribed
👍 1
m

Matti MK

02/03/2022, 1:42 PM
Much appreciated, thank you 👍