Okay I am sure this has been asked before in some ...
# compose
s
Okay I am sure this has been asked before in some form, but I am still being confused about how to properly collect flows from a VM inside my composables. More in Thread 🧵
☝️ 1
Say I have a composable, starting with these
Copy code
val viewModel: MyViewModel = hiltViewModel()
val lifecycleOwner = LocalLifecycleOwner.current
val stateFlow: Flow<ViewState> = viewModel.state
I can see all the following ways being viable, but I am not sure which one is best/preferable and which ones are straight up wrong in some scenarios, meaning there might be errors/crashes, or even simply inefficiencies. Way #1
Copy code
val viewState: ViewState by stateFlow.collectAsState(ViewState.Initial)
Way #2
Copy code
val viewState: ViewState by remember(stateFlow, lifecycleOwner) {
    stateFlow.flowWithLifecycle(lifecycleOwner.lifecycle)
}.collectAsState(initial = ViewState.Initial)
Way #3
Copy code
val viewState: ViewState by produceState(
    initialValue = ViewState.Initial,
    stateFlow,
    lifecycleOwner
) {
    stateFlow.flowWithLifecycle(lifecycleOwner.lifecycle)
        .collect { value = it }
}
Way #4
Copy code
val rememberedFlow: Flow<ViewState> = remember(stateFlow, lifecycleOwner) {
    stateFlow.flowWithLifecycle(lifecycleOwner.lifecycle)
}
val viewState3: ViewState by produceState(ViewState.Initial, rememberedFlow) {
  rememberedFlow.flowWithLifecycle(lifecycleOwner.lifecycle)
        .collect { value = it }
}
And I’m sure I’ve seen more than that of various complexities. I am still not super comfortable with what I would be missing out on if I went with #3 instead of #4 for example, by not explicitly remembering the flow itself, am I missing out on performance? In case of #1, the simplest one, is this totally wrong? If yes, why is it wrong? Does it have bugs? Is it simply inefficient? So many questions, I would love to see an article or some docs on a definitive way to weigh those decisions and know what you are doing while doing so.
Because recently, I even read this comment by @Adam Powell about how
collectAsState
maybe was not a super good idea, and I would love to hear more about why and how we can make these informed decisions about what to do/not do. Especially as long as lint checks are not there fully to help us avoid shooting ourselves in the foot (This lint check is a good example of one of them that is nice, but only available on the alpha compose channel as of right now)
a
flowWithLifecycle
is used mainly for safeness (to avoid modifying UI in the background). In compose it's totally ok to modifying states in the background, so as long as your flow doesn't emit frequently in the background I don't think there is much benefit really to use
flowWithLifecycle
.
s
So #1 already is good enough? And if we do want to use some flow operators on the collected flow, is #3 the way to go?
a
#3 is ok but #2 is simpler so why not #2?
s
Okay so #3 and #2 are functionally equivalent then, good to know. And I assume #4 is just unnecessary to remember the flow by itself too, doesn’t help in any way. And sorry, could you confirm for me that in the case that we do not need any flow operators #1 is perfectly fine without any drawbacks?
a
I gravitate to 1 in the simple cases and 3 for cases that need operator assembly. 2 is just fine, it's just marginally less efficient/more concepts involved vs 3 since it's storing the final flow pre-collect in the composition via remember and the produceState version doesn't have to. (You probably really don't need to care about this.) The tradeoff is that produceState is a different and potentially unfamiliar API when you already know collectAsState. This is why I was vaguely musing about a "regret" (and it is a very mild regret) around collectAsState; it becomes familiar so the first instinct is to stick with what you know and understand, and you may not be comfortable with the lower level API that it's using to function.
💯 1
4 is unnecessary in all cases I can think of prior to my morning coffee here 🙂
😄 1
n
But as mentioned in your prev thread, would that create a new flow upon every recomposition?
a
Would which one create a new flow each recomposition?
Would that apply to #1 or am I way off? (i’m also way pre coffee and still deep at learning compose)
a
The other reason to
flowWithLifecycle
is that it stops cold upstream flows from performing expensive work when your app is in the background. If you had a flow of GPS coordinates that turned the GPS on while collecting it, you don't want to leave that running for battery and privacy reasons.
The simple call to
collectAsState
only starts a new collect on recomposition if the receiver is a different flow instance on recomposition. The most common reason for this is operator assembly
👍 1