The answer to this may be “it depends” but I want ...
# compose
d
The answer to this may be “it depends” but I want to know which is generally better. From the
ViewModel
is it better to expose a single state object or multiple objects that make up the state? For example let’s say I have two properties
title: String
and
isComplete: String
that could change over time. The first option would be to wrap them up like so:
Copy code
data class State(
  val title: String = ",
  val isComplete: Boolean = false
)
And then exposing it like this:
Copy code
// MyViewModel.kt
val state: StateFlow<State>
And consuming it like this:
Copy code
@Composable
fun ToDoUI(viewModel: MyViewModel) {
  val state by viewModel.state.collectAsState()
  ...
}
OR is it better to decompose the state and expose it like so:
Copy code
// MyViewModel.kt
val title: StateFlow<String>
val isComplete: StateFlow<Boolean>
And then consume like this:
Copy code
@Composable
fun ToDoUI(viewModel: MyViewModel) {
  val title by viewModel.title.collectAsState()
  val isComplete by viewModel.isComplete.collectAsState()
  ...
}
What are the ramifications for my composables between the two? Is there any performance issues or “gotchas” I should be aware of here?
@julioromano Here is your thread
j
I mean to post answers inside the questions’s thread
d
I am not about to answer my own question?
Sorry I am confused, isn’t it common practice to answer questions in a thread anyway?
👌 2
j
Oh my apologies, it looked like this was an answer rather than a question.
👍 1
I remember I had a similar discussion some time ago, posting it here hoping it may help you: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1611232921023400
A similar question was also posted a few minutes ago: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1616596090042700 To which I’ve answered with the same link.
Summing it up: there is no “best practice” here, you should do what suits you best without thinking too much about performance to avoid falling in the “premature optimization pit”. Optimizations at the compose compiler level should take care of any different usage styles. If you find a specific case where performance is impacted file a bug.
d
Thanks this is helpful. Let me see if I understand all this correctly. Given my example code, and
isComplete
changes in the
State
object. Only the composables that reference
isComplete
will get recomposed?
Because the compiler is doing the memoization at the line level of granularity. @Adam Powell
j
Given my example code, and 
isComplete
 changes in
the 
State
 object. Only the composables that reference 
isComplete
 will get recomposed?
More or less yes but perhaps not in the exact way you think, check out this chat message from that thread in particular: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1611248248029100?thread_ts=1611232921.023400&amp;cid=CJLTWPH7S
d
So this :
Copy code
val state by stateFlow.collectAsState()
    Column {
        // Scope A
        Button(onClick = onClick1) {
            // Scope B
            Text(text = "One ${state.firstCounter}")
        }
        Button(onClick = onClick2) {
            // Scope C
            Text(text = "Two ${state.secondCounter}")
        }
    }
Is better then this:
Copy code
val state by stateFlow.collectAsState()
    Column {
        // Scope A
        MyButton(
            text = "One ${state.firstCounter}",
            onClick = onClick1
        )
        MyButton(
            text = "Two ${state.secondCounter}",
            onClick = onClick2
        )
    }
j
But I’d like to emphasize a bit more that trying to guess at what the compose runtime will do during recomposition is potentially a waste of time. What we might be seeing now when it comes to which composable recomposes in a given code example might not hold true in the future. Updates to the compose compiler and runtime might change what happens during recomposition: they might introduce further optimizations so that the actual runtime calls might change.
d
True
j
d
Long story short is represent your state how you like and let the compiler take care of things for now. In the future we could receive more guidance.
👍 1
j
So perhaps yes, now we could say that in theory the first code snippet is “better” than the second. But what is “better” in this context? Do you really wanna spare one composable function call that will probably not do much?
d
Thanks a lot @julioromano
s
Seconding, don't design this for performance unless you have a known performance problem. As an optimization, you may find that splitting the state and passing smaller parts down may lead to performance gains, and it may not. Profiling your actual code is the way to go there. A rule that I follow when designing state is "state that updates together lives together." I find it tends to become hard to reason about read/edit/write operations in state that's partially written by numerous async processes. And multiple states written by a single event tend to become hard to keep track of.
For the optimization, you shouldn't need to modify your ViewModel. You can split the state in a composable with no change in performance vs. multiple states in the ViewModel. Do what works best for your code 🙂
👍 2
d
@Sean McQuillan [G] Fantastic! Thanks so much for chiming in on the matter.