Rick Regan
01/03/2023, 8:26 PMCrossfade
is leaking its state holder. I have a Slider that I can interact with normally (no crossfades) but that will crossfade to its default value when a “reset” Button is pressed. I do this using a MutableState within a MutableState, the outer one being the target for Crossfade, and the inner one being the slider value (code in 🧵.)
In the heap dump (after forcing garbage collection) I can see that the number of allocated SliderStateHolder/SliderState objects increases with each reset. Inasmuch as I understand heap dumps, the references look like they’re coming from “SnapshotStateList”, which would seem to indicate Compose continues to track the old states. I’d like to understand why (and what I could do to prevent it). Thanks.Rick Regan
01/03/2023, 8:26 PMdata class SliderState(
val value: Int = 5
)
class SliderStateHolder(
state: SliderState = SliderState()
) {
var state by mutableStateOf(state)
fun onValueChange(value: Int) {
this.state = SliderState(value)
}
}
var sliderStateHolder by mutableStateOf(SliderStateHolder())
@Composable
fun SliderWithResetCrossfade() {
Column {
Crossfade(
targetState = sliderStateHolder,
animationSpec = tween(1000)
) { sliderStateHolderX ->
Slider(
value = sliderStateHolderX.state.value.toFloat(),
onValueChange = { sliderStateHolderX.onValueChange(it.roundToInt()) },
valueRange = 0f..10f,
steps = 9
)
}
Button(
modifier = Modifier.size(100.dp),
onClick = { sliderStateHolder = SliderStateHolder() },
) {
Text(text = "Reset")
}
}
}
Doris Liu
01/04/2023, 2:14 AMMutableStateList
in Crossfade tracking all the states that are still active from the animation's perspective. That means during the crossfade, the old state(s) will continue to be in the MutableStateList until the content for those states has been faded out completely.Doris Liu
01/04/2023, 2:17 AMRick Regan
01/04/2023, 3:02 AMRick Regan
01/04/2023, 3:30 AMRick Regan
01/04/2023, 7:14 PMCrossfade
and see the same problem. I took the example from the docs and modified it to put the string in an object (can you track primitives in a heap dump?). In this example, Screen
leaks.
data class Screen(
val name: String
)
@Composable
fun CrossfadeLeaks() {
var currentPage by remember { mutableStateOf(Screen("A")) }
Column(modifier = Modifier.padding(top = 20.dp, start = 20.dp)) {
Crossfade(targetState = currentPage) { screen ->
when (screen) {
Screen("A") -> Text("Page A")
Screen("B") -> Text("Page B")
}
}
Button(onClick = { currentPage = if (currentPage == Screen("A")) Screen("B") else Screen("A") }
) {
Text(text = "Toggle")
}
}
}
Rick Regan
01/06/2023, 2:23 AMRick Regan
01/07/2023, 4:19 AMSmallPersistentVector
, but I’ll assume it’s something indirectly related to Crossfade
. I will post the issue here when I write it up.Rick Regan
01/07/2023, 11:58 AM