Search code examples
androidkotlinandroid-jetpack-composestate

Why is the status reset in Compose?


I am experiencing a very strange problem. I'm making a method to handle click events on the screen, as I want to save all the pointers in a map with their id and position, I have a state class in which I have this map and I'm updating it with a method in the ViewModel

data class RandomState(
    val touchedPositions: Map<Long, Pair<Float, Float>> = emptyMap(),
)
fun updateTouchedPositions(
    touchedPositions: Map < Long, Pair < Float, Float >> 
) {
    println("Function from viewModel: $touchedPositions")
    state = state.copy(
        touchedPositions = touchedPositions)
}
.pointerInput(Unit) {
    awaitEachGesture {
        val positions = mutableMapOf < Long, Pair < Float, Float >> ()

        do {
            val event = awaitPointerEvent()
            val canceled = event.changes.fastAny {
                it.isConsumed
            }

            if (event.type == PointerEventType.Press) {
                event.changes.forEach { pointer - >
                    val id = pointer.id.value
                    val x = pointer.position.x
                    val y = pointer.position.y
                    positions[id] = Pair(x, y)
                }
                println("Se van a guardar las posiciones: $positions")
                viewModel.updateTouchedPositions(positions)
                println("Posiciones de dedos en onPress: ${state.touchedPositions}")
            }

            if (event.type == PointerEventType.Release) {
                println("Posiciones de dedos en onRelease: ${state.touchedPositions}")
            }

        } while (!canceled)
    }
}

So I declare in my viewModel my state class which I access in the Composable with a viewModel.state.

// ViewModel class

var state by mutableStateOf(RandomState())
        private set

The problem is that although with the debug everything seems to be fine, when I'm in the second print inside the if of the do block, the state.touchedPositions is empty. It's as if the state class is somehow reset to the default state, but I'm not able to know why.


Solution

  • From the code you provided it is unclear what state is and how it happens to be accessible in pointerInput, so the following includes a bit of guess work.

    pointerInput is a composable function, and composable functions are usually recomponsed when any state changes that they access. pointerInput, however, explicitly defines another behavior: Like remember or LaunchedEffect, it only recomposes when the key parameter(s) change. Since you provided Unit as the key, pointerInput will only be executed once, when the composable enters the composition. It then captures all variables that it needs in a closure. That is like a snapshot of all the objects that the variables currently contain. state is such a variable who's object will be captured, and that object will be used for everything that happens inside pointerInput (your log statements).

    When the state variable changes (possibly due to recompositions), the closure will still only contain the initial object. And since you decided by using Unit to never recompose pointerInput, that closure will also never be refreshed with the current value. So whatever you do to the state in the view model, it will never reach the pointerInput lambda.

    Long story short: You need to either replace Unit by state or move the log statements outside of pointerInput.