Simon Stahl
01/13/2022, 12:29 AMFlow operator functions should not be invoked within composition
warning is
Say I have following method in my VM:
@Composable
fun getValue() =
flowOf(1, 2, 3)
.map { it.toString() } // warning here
.collectAsState(initial = "")
The naive solution is to just split the method up
fun getValueFlow() =
flowOf(1, 2, 3)
.map { it.toString() }
@Composable
fun getValue() =
getValueFlow()
.collectAsState(initial = "")
But that is not really a functional change and if there is an issue with flow operations, then we would still have it. What would be a real fix for this warning?Ian Lake
01/13/2022, 12:48 AMIan Lake
01/13/2022, 12:48 AMflowOf()
part) and applying operators on it (the map
)Ian Lake
01/13/2022, 12:49 AMcollectAsState()
from the previous composition to be cancelled (as the underlying Flow changed out from underneath it since the last composition)Ian Lake
01/13/2022, 12:54 AMFlow
be the exact same instance on every recomposition. That means you can either make that Flow
a val
in your VM:
private val valueFlow = flowOf(1, 2, 3).map {
it.toString()
}
@Composable
fun getValue() = valueFlow.collectAsState(initial = "")
or remember
your Flow
so that that instance survives across recomposition:
@Composable
fun getValue() {
val valueFlow by remember {
flowOf(1, 2, 3).map {
it.toString()
}
}
return valueFlow.collectAsState(initial = "")
}
Ian Lake
01/13/2022, 12:56 AMflowWithLifecycle
example here: https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda#27a9Ian Lake
01/13/2022, 1:00 AMflowOf(1, 2, 3)
, but a separate val
Flow that doesn't change across recompositions - that's generally the only responsibility of the VM layer (so a @Composable
method in a VM is a bit weird to begin with that really only makes that layer harder to test in isolation)Simon Stahl
01/13/2022, 1:05 AMremember{}
removes the warning. That makes perfect sense of course.Simon Stahl
01/13/2022, 1:07 AM@Composble
methods? So it would just provide the flow and remember{}
as well as collectAsState()
should be located in the composable method itself?Colton Idle
01/13/2022, 1:13 AMColton Idle
01/13/2022, 1:17 AMColton Idle
01/13/2022, 1:18 AMColton Idle
01/13/2022, 1:18 AMSimon Stahl
01/13/2022, 5:43 PMremember
and collectAsState
to the VM. So this is the real question right now, is that ok? because doing so requires me to annotate the VM methods with @Composable
Simon Stahl
01/13/2022, 5:44 PMSimon Stahl
01/13/2022, 5:46 PMfun getMyValueFlow(): FLow<Int> = flowOf(1, 2, 3)
@Composable
fun getMyValueState() =
remember { getMyValueFlow() }.collectAsState(initial = 0)
Colton Idle
01/13/2022, 5:53 PMthat is why i would like to move theRemember is not needed in a VM. Remember is a thing thats only used in COmposables. So there is no need to wrap things in remember. Just leave off the remember {} and everything should work. The reason remember{} is needed is so that if a composable is recomposed 1000 times in 1 second, that whatever you put in the remember { } isn't called 1000 times. A VM doesn't just "recompose" and so you don't need any concept of remembering. CollectAsState should not be called in a VM. All examples from Android docs have it in the composable right before you draw your UI. I have 3 compose only apps, with probably over 80 screens across all three apps. Not one of them uses a @Composable function in a ViewModel. Now... I am definitely not a compose expert, but what you sound like you want to do seems incorrect to me (although you have good intentions where you want to alleviate the need for even a composable to have to call collectAsState)andremember
to the VM.collectAsState
Ian Lake
01/13/2022, 8:10 PMi want to make it as easy as possible for the compose view code though, that is why i would like to move theI think you're just moving complexity into the wrong layer, not removing itandremember
to the VM.collectAsState
Simon Stahl
01/13/2022, 8:27 PM