'Why recomposition happens when call ViewModel in a callback?
I completely confused with compose conception. I have a code
@Composable
fun HomeScreen(viewModel: HomeViewModel = getViewModel()) {
Scaffold {
val isTimeEnable by viewModel.isTimerEnable.observeAsState()
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.background(Color.Black),
) {
Switch(
checked = isTimeEnable ?: false,
onCheckedChange = {
viewModel.setTimerEnable(it)
},
)
Clock(viewModel.timeSelected.value!!) {
viewModel.setTime(it)
}
}
}
}
@Composable
fun Clock(date: Long, selectTime: (date: Date) -> Unit) {
NumberClock(Date(date)) {
val time = SimpleDateFormat("HH:mm", Locale.ROOT).format(it)
Timber.d("Selected time: time")
selectTime(it)
}
}
Why Clock
widget recomposes when I tap switch. If I remove line selectTime(it)
from Clock
widget callback recomposition doesn't happen.
Compose version: 1.0.2
Solution 1:[1]
This is because in terms of compose, you are creating a new selectTime
lambda every time, so recomposition is necessary. If you pass setTime
function as a reference, compose will know that it is the same function, so no recomposition is needed:
Clock(viewModel.timeSelected.value!!, viewModel::setTime)
Alternatively if you have more complex handler, you can remember
it. Double brackets ({{ }}
) are critical here, because you need to remember the lambda.
Clock(
date = viewModel.timeSelected.value!!,
selectTime = remember(viewModel) {
{
viewModel.setTimerEnable(it)
}
}
)
I know it looks kind of strange, you can use rememberLambda
which will make your code more readable:
selectTime = rememberLambda(viewModel) {
viewModel.setTimerEnable(it)
}
Note that you need to pass all values that may change as keys, so remember
will be recalculated on demand.
In general, recomposition is not a bad thing. Of course, if you can decrease it, you should do that, but your code should work fine even if it is recomposed many times. For example, you should not do heavy calculations right inside composable to do this, but instead use side effects.
So if recomposing Clock
causes weird UI effects, there is probably something wrong with your NumberClock
that cannot survive the recomposition. If so, please add the NumberClock
code to your question for advice on how to improve it.
Solution 2:[2]
This is the intended behaviour. You are clearly modifying the isTimeEnabled
field inside your viewmodel when the user toggles the switch (by calling vm.setTimeenabled). Now, it is apparent that the isTimeEnabled
in your viewmodel is a LiveData
instance, and you are referring to that instance from within your Composable by calling observeAsState
on it. Hence, when you modify the value from the switch's onValueChange, you are essentially modifying the state that the Composable depends on. Hence, to render the updated state, a recomposition is triggered
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Richard Onslow Roper |