Question
Why is my size variable not updating correctly in Jetpack Compose?
I'm learning Jetpack Compose and created a simple example to understand state management. I have a button that updates a width
variable, and I want to calculate a size
based on the width
and height
variables. However, the size
variable is not updating as expected when width
changes.
Here is the simplified code to demonstrate the issue:
Example 1 (Not Working):
@Composable
@Preview
fun App() {
var width by remember { mutableStateOf(0f) }
var height by remember { mutableStateOf(0f) }
// Issue in this line. When width or height changes, size is not recalculated.
// size remains the same as initial state Size(0, 0).
// I expect that the size to be always sync with the width and height
var size = Size(width, height)
Button(
onClick = { width++ },
modifier = Modifier.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
println(size)
println(width)
}
}
) {
Text("OK")
}
}
When I click the button, width
increments correctly, but size
always prints Size(0.0, 0.0)
when I drag the button. I tried using mutableStateOf
for size
, but it still doesn't work.
Example 2 (Using derivedStateOf, Working):
@Composable
@Preview
fun App() {
var width by remember { mutableStateOf(0f) }
var height by remember { mutableStateOf(0f) }
val size by derivedStateOf { Size(width, height) }
Button(
onClick = { width++ },
modifier = Modifier.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
println(size)
println(width)
}
}
) {
Text("OK")
}
}
The only solution that works is using derivedStateOf
, but the documentation states that derivedStateOf
should be used when inputs are changing more often than needed for recomposition, which is not the case here.
You should use the
derivedStateOf
function when your inputs to a composable are changing more often than you need to recompose. This often occurs when something is frequently changing, such as a scroll position, but the composable only needs to react to it once it crosses a certain threshold.derivedStateOf
creates a new Compose state object you can observe that only updates as much as you need. In this way, it acts similarly to the Kotlin FlowsdistinctUntilChanged()
.Caution:
derivedStateOf
is expensive, and you should only use it to avoid unnecessary recomposition when a result hasn't changed.
Example 3 (Seems to Work, But Why?):
@Composable
@Preview
fun App() {
var width by remember { mutableStateOf(0f) }
var height by remember { mutableStateOf(0f) }
val size by mutableStateOf(Size(width, height))
println(width)
println(size)
Button(
onClick = { width++ }
) {
Text("OK")
}
}
In this example, size
appears to update correctly, but I don't understand why.
Question:
I understand that derivedStateOf
is often used for performance optimizations, but I'm trying to understand why size
doesn't update correctly with mutableStateOf
or as a regular variable in the first example. Also, why does the third example seem to work?