Hello here! I was reading the Compose documentatio...
# compose
g
Hello here! I was reading the Compose documentation about Managing states and I found this section: https://developer.android.com/jetpack/compose/state#viewmodels-source-of-truth. I’m pretty surprised about this. 1. The official recommendation is to link my ViewModel to Compose and no more emit my UiState with a StateFlow (or a LiveData)? 2. If you have a data class (here
ExampleUiState
) as single source of truth for the UI and because you can’t pass a state as parameter inside a Composable (otherwise, lint error), does that means I’m forced to redraw my entire screen at each change of my state? Thanks in advance for your time! :)
a
for 1) using
mutableStateOf
is no more linked to composition and UI than using
suspendCancellableContinuation
is linked to flows. For 2) this situation isn't any different from using
val uiState by viewModel.uiStateFlow.collectAsState()
at the same point, do you have some code to share that you're concerned about?
👍 1
g
Thanks for your response Adam!
1. Ok interesting but it requires Compose Runtime, right? Just, we aren’t forced to use Compose to emit UI.
2. Yes, if you are using a StateFlow, you use
collectAsState()
and get the full model which force the recomposition of the screen (maybe there is a performance issue with this function?) but if you are using
mutableStateOf
inside your ViewModel, you get a state and you will force the recomposition only when you will call
state.value
property. It seems interesting to pass state as parameter to our composable functions to apply a recomposition to a subset of composables rather than the entire screen.
I’m aware that this brings others problems. This seems a bad idea to share a state between multiple Composable but that could help performance. 🤔
a
Using snapshot state requires a dependency on the compose-runtime artifact but it does not require using the compose-compiler plugin.
👍 1
As for where invalidations occur in the composition by reading snapshot state, to a first order of approximation, don't worry about it either way, you can treat
Copy code
val uiState by myViewModel.uiState.collectAsState()

SomeComposable(uiState)
and
Copy code
class MyViewModel : ViewModel() {
  var uiState by mutableStateOf(UiState.Initial)
    private set
}
// ...

SomeComposable(myViewModel.uiState)
as identical. They both invalidate when determining the parameter values to pass.
But this isn't always common usage; in many cases you'll be pulling apart
uiState
in different places;
Copy code
MyScaffold(
  titleBar = { AppBar(..., uiState.title) },
  itemContent = { Item(uiState.items[it]) },
)
etc. It might not be abstracted in a composable this way, instead this might just be the result of placing different things in a combination of rows, columns, cards, and your other custom containers in a single composable function. You might combine different parts of
uiState
for some things, and each combined piece might change in its own way over time as well.
If you want evaluation of some part of snapshot state to be lazy and invalidate only the part of the composition where it's used, 1) measure that this is indeed causing a performance bottleneck for you first; compose has a number of optimizations to try to make "spine" invalidation from the top cheaper. It may have to recompose "from the top" but different sub-branches will be able to skip if the parameters are stable and unchanged.
2) avoid using
[Mutable]State<T>
as the type of a parameter or public class property. This is an implementation detail, not API contract. If you want to afford deeper invalidation, use a lambda instead as it affords better flexibility for the caller:
Copy code
// Don't
@Composable
fun FancyItem(state: State<SomeData>) {
  SomeDeeplyNestedThing {
    Text(state.value.text)
  }
}
Instead do this:
Copy code
// Do
@Composable
fun FancyItem(getState: () -> SomeData) {
  SomeDeeplyNestedThing {
    Text(getState().text)
  }
}
(kind of a rough example but you get the idea)
I need to get the above into the public API guidelines 🙂 it's becoming a more common question
👍 1
g
Definitely more clear now! Thanks for all these explanations. Yes, I think this should be added in the official documentation.
Today, I don’t have any performance issue with a state used by a lot of sub components from a screen component but I try to understand how it works to be sure it is okay.
Thanks again for your response!
👍 1
s
I would love to see some official guidelines around doing this. To me as I’ve never used it so far it feels non-obvious so some quality documentation would be amazing!