Hi, I'm learning compose and I come from a backgro...
# compose
f
Hi, I'm learning compose and I come from a background where I like the redux way of doing things. So I normally would have an immutable data class describing the state of the whole screen, and this up-to-date state would be exposed through StateFlow to the view side. So I've tried to keep doing that here and I have something like this
Copy code
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
    val state: HelloState by helloViewModel.stateFlow.collectAsState(initialState)
    Column {
        Text(state.title)
        SomeStatelessComponent(state.componentValue)
        SomeOtherStatelessComponent(state.otherComponentValue)
        ...
    }
}
The idea is that every composable used in HelloScreen are simply stateless composables. My worry is that this won't work out great if compose simply does a reference check (===) instead of an equals to check if it should recompose these stateless composables. If compose simply does a reference check, I see three ways out of this: • Wrapping every object in this state class in mutableStateOf and adjust the reducer on the viewmodel side -> seems terrible, does this even work? • Having a stateflow per state value -> seems clunky if your screen has a lot of state properties. • Orrr find a way to selectively "subscribe" to the stateFlow properties, like
Copy code
stateFlow.map { state -> state.componentValue }.distinctUntilChanged()
Is this okay? I'm up for other ideas
a
So long as your types are stable (inferred by compose's compiler plugin or explicitly marked as either
@Stable
or
@Immutable
as appropriate) compose will skip a child composable function call if all parameters are `==
/
.equals.`
f
@Adam Powell Does this mean that if I mark my state class with @Immutable, the code above will simply work as expected and everything is fine? (read: I don't have to go for any of the other solutions I presented?)
a
"fine" can be relative depending on how deep that object graph goes and how expensive checking all of it for equality might get if there isn't some structural sharing going on as things change incrementally so that you can skip comparing whole subtrees if the parent is ===, but it will behave as you describe, yes
f
I see, that's great to know and to keep in mind, thank you!
👍 1
j
Does @Stable work all the way down? As in, can i have nested properties of state which are also marked @Stable
a
Yes
f
@Adam Powell Sorry to tag, but just a final follow up, I noticed that every time stateFlow emits, HelloScreen is recomposed (checked with SideEffect). Even though it will skip every other child composable that depends on values that didn't change, I fear that the constant recomposition of HelloScreen for each emission could be costly and makes me reevaluate my approach. Do you think this HelloScreen recomposition overhead is minimal (assuming skipping works well and there's good structural sharing on state object) and is my approach acceptable even for constant emissions and complex composable trees?
a
I'd measure it. https://ui.perfetto.dev is a good place to get some visual results.
If you know this is going to be a hot code path you'll want to keep as much complexity out of those spine composables as you can
f
Makes sense, I'll try to measure it once I get there! Thanks!