Steffen Funke
11/04/2021, 6:56 AM@Immutable
to work correctly for me - why do my list items recompose, when changing only a isLoading
flag of my UI-State?
👉 Code in 🧵typealias Items = List<String>
// ViewModel
class RecomposeDemoViewModel(repo: DummyRepo = DummyRepo()) {
@Immutable
data class UiState(
val items: Items = emptyList(),
val isLoading: Boolean = false,
val error: Error? = null
)
var uiState by mutableStateOf(
UiState(items = repo.sampleData)
)
fun changeSomeUiState() {
uiState = uiState.copy(isLoading = !uiState.isLoading)
}
}
// My Root View
@Composable
fun RecomposeDemo() {
val viewModel by remember { mutableStateOf(RecomposeDemoViewModel()) }
LogCompositions("Root")
Column {
Text("This demo uses a monolithic state data class.")
val uiState = viewModel.uiState
// Header
Row {
if (uiState.isLoading) {
CircularProgressIndicator()
}
Button(onClick = viewModel::changeSomeUiState) {
Text(text = "Toggle Loading State")
}
}
// Why do list items in this LazyColumn recompose??
MyColumn(items = viewModel.uiState.items)
}
}
// Custom Lazycolumn to "narrow" recomposition scope -premature optimization?
@Composable
fun MyColumn(
items: Items,
modifier: Modifier = Modifier
) {
LogCompositions("\tMy LazyColumn")
LazyColumn(
modifier = modifier.fillMaxWidth()
) {
items(items) { item ->
LogCompositions("\t\t${item}")
Text(item, modifier = Modifier.padding(horizontal = 16.dp, vertical = 36.dp))
Divider()
}
}
}
LogCompositions
composable, taken from this blog: https://www.jetpackcompose.app/articles/donut-hole-skipping-in-jetpack-composeisLoading
- items
stay the same):
Root >> Compositions: 1
My LazyColumn >> Compositions: 1
Item 0 >> Compositions: 1
Item 1 >> Compositions: 1
Item 2 >> Compositions: 1
Item 3 >> Compositions: 1
Item 4 >> Compositions: 1
Item 5 >> Compositions: 1
Item 6 >> Compositions: 1
Item 7 >> Compositions: 1
>> Button click
Root >> Compositions: 2
My LazyColumn >> Compositions: 2
Item 0 >> Compositions: 2
Item 1 >> Compositions: 2
Item 2 >> Compositions: 2
Item 3 >> Compositions: 2
Item 4 >> Compositions: 2
Item 5 >> Compositions: 2
Item 6 >> Compositions: 2
Item 7 >> Compositions: 2
...
Albert Chang
11/04/2021, 7:21 AM@Immutable
. If the class of a composable function parameter is marked with @Immutable
(or @Stable
) and it equals()
the previous parameter, then the function can be skipped. You are not using UiState
as the parameter of any composable functions and you are changing it at the first place, so its stability is irrelevant here. Recomposition is because Items
is not stable.Steffen Funke
11/04/2021, 7:29 AMItems
(which btw. is only a typealias to List<String>
should have made it clear) with @Stable
or @Immutable
, and no change. There is an ongoing discussion in the Twitter Thread about this experiences. Probably we are something missing. Basically it boils down to - why does the list recompose, if the inputs have not changed?Albert Chang
11/04/2021, 7:32 AMSteffen Funke
11/04/2021, 7:35 AMval
of type List<T>
as @Immutable
, for that reason. Which has not had any effect.Albert Chang
11/04/2021, 7:42 AMSteffen Funke
11/04/2021, 7:48 AM@Immutable data class
works indeed:
@Immutable
data class Items(
val items: List<String>
)
...
class RecomposeDemoViewModel(val repo: DummyRepo = DummyRepo()) {
data class UiState(
val items: Items = Items(emptyList()),
val isLoading: Boolean = false,
val error: Error? = null
)
var uiState by mutableStateOf(
UiState(items = Items(repo.sampleData))
)
...
Now I only get the recompositions I expect.
Root >> Compositions: 0
My LazyColumn >> Compositions: 0
Item 0 >> Compositions: 0
Item 1 >> Compositions: 0
Item 2 >> Compositions: 0
Item 3 >> Compositions: 0
Item 4 >> Compositions: 0
Item 5 >> Compositions: 0
Item 6 >> Compositions: 0
Item 7 >> Compositions: 0
>> Button Click
Root >> Compositions: 1
>> Button Click
Root >> Compositions: 2
>> Button Click
Root >> Compositions: 3
Yay!
Thank you @Albert ChangList<T>
should not behave immutable as well, wenn annotated with @Immutable
Albert Chang
11/04/2021, 8:00 AMList<T>
is not immutable by nature, it is read-only. And you can’t annotate a class which is not in your control (typealias won’t work because it’s not a class).Steffen Funke
11/04/2021, 8:11 AMOrhan Tozan
11/04/2021, 9:39 AMAlbert Chang
11/04/2021, 9:41 AMZach Klippenstein (he/him) [MOD]
11/04/2021, 4:22 PM