Question on converting flow to liveData. I have a ...
# compose
d
Question on converting flow to liveData. I have a class which I am using as a ViewModel (not android viewModel) which contains a flow. I get excessive collection if I pass this viewModel into the Composable view and observe the flow. I notice that if I convert to liveData in the view model instead that the excessive emission doesn't occur. Am I missing something on how this should be done?
Copy code
class MyViewModel(model: Model) {
    val userLoggedInFlow = model.usernameFlow.map { it != null } // returnes if there is a username
}

@Composable
fun myView(viewModel: MyViewModel){
    val state = viewModel.userLoggedInFlow.asLiveData().observeAsState()
    
    if(state.value != null){
        //Show stuff here
    }
    else {
        //navigate to the login screen
    }
}
1
a
.asLiveData()
is creating a new
LiveData
from
viewModel.userLoggedInFlow
every time
myView
recomposes, and since you read
state.value
in the same recompose scope, that means that every time
state.value
changes you create a new LiveData, remove the old LiveData observer from the old LiveData, then add a new observer to the new LiveData, which may then emit a new value when it hits onActive after recomposition completes.
😮 1
You want to make sure that you don't get a new LiveData for as long as you have the same viewModel.userLoggedInFlow
you want
Copy code
val state = remember(viewModel) {
  viewModel.userLoggedInFlow.asLiveData()
}.observeAsState()
You could choose to use
viewModel.userLoggedInFlow
as the remember key instead of
viewModel
, but since they're 1-1 here it'll have the same results and it's not uncommon for ViewModel methods to return new observable object instances on access depending on some implementation details, so it's not a bad habit to get used to
i
I'm not entirely sure why you're using
asLiveData()
at all here given that Compose supports Flow directly with
collectAsState()
?
👍 3
a
LiveData.observeAsState uses the LocalLifecycleOwner to observe with, if you want to match that while-started behavior with Flow you can use
.flowWithLifecycle
and
remember
as described above
👍 1
j
Found this helper function to be handy. Was having the same issue too.
Copy code
@Composable
fun <T> rememberFlowWithLifecycle(
    flow: Flow<T>,
    lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED
): Flow<T> = remember(flow, lifecycle) {
    flow.flowWithLifecycle(
        lifecycle = lifecycle,
        minActiveState = minActiveState
    )
}
👍 1
a
yep. Once you start combining a few operators you can also generalize it to something like
Copy code
val lifecycleOwner = LocalLifecycleOwner.current
val current by produceState(initial, flow, lifecycleOwner) {
  flow.flowWithLifecycle(lifecycleOwner.lifecycle)
    .otherOperators(...)
    .collect { value = it }
}
👍🏾 1
so that you don't end up with a dozen different chained
remember
-ing operators in use cases where you want a filter/map/something or other
that's all
.collectAsState
is doing 🙂
n
Where can I find this
rememberFlowWithLifecycle
function…? I wrote one by myself to my current project 😅
Copy code
@Composable
fun <T> Flow<T>.flowInLifecycle(): Flow<T> {
    val lifecycleOwner = LocalLifecycleOwner.current
    return remember(this, lifecycleOwner) {
        this.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
    }
}
a
There isn't a stock one; we've avoided adding one to keep from creating too much of a customization cliff as you add more operators and produceState becomes a better option
The discussions I've been seeing around it make me slightly regret collectAsState for the same reasons 🙂
n
Kind of hijacking this a little bit — What’s the best way to figure out if I’m doing something wrong like having excessive collections?
f
@Adam Powell Instead of all these flow lifecycle shenanigans I can also just turn the Flow into LiveData in the ViewModell, right?
Then I get this lifecycle behavior for free
a
it works for simple cases but it adds some overhead, and it's a lot harder to apply other operators to LiveDatas than it is to Flows. This will encourage you to do even more of that adaptation in the ViewModel instead of in place, which can make your code harder to read and make your ViewModel more complex
f
@Adam Powell Makes sense, thank you