Joris Wit
Joris Wit

Reputation: 593

Why is Jetpack Compose using the old state in the onTap event

I'm new with Kotlin and Jetpack Compose, and I have an issue in my Android app. When I run the code below, I can press the "Increase" button, and the Canvas shows the increased number. However, in the pointerInput onTap event, the number is always 1. Only when I force a recomposition by rotating the device, the new number is used.

It seems as if the state is used when it was first composited instead of the current state. How can I use the most recent state in the onTap event?

Some background: I use the Canvas to draw a grid with a dynamic number of columns and rows. The number in my example determines the number of columns or rows. In the onTap event I want to calculate which cell was clicked, to raise my own onCellTap event. I do this by using the formula to calculate the position of the cells used in onDraw, but in reverse. This all works fine, until a column or row is added dynamically, because I can't calculate with the "old" column count.

Instead of the mutableIntStateOf in this example, I use a ViewModel in my app. I removed this from the example for simplicity, I hope this does not matter.

@Composable
fun TopLevelComposable(modifier: Modifier = Modifier) {

    var number by rememberSaveable { mutableIntStateOf(0) }

    Column {
        TestCanvasComposable(number)
        Button(onClick = {

            number += 1

        }) {
            Text(
                text = "Increase",
                modifier = modifier
            )
        }
    }
}

@Composable
fun TestCanvasComposable(
    number: Int,
    modifier: Modifier = Modifier
) {
    val textMeasurer = rememberTextMeasurer()
    Canvas(modifier = modifier
        .fillMaxWidth()
        .height(30.dp)
        .pointerInput(Unit) {
            detectTapGestures(
                onTap = {
                    // This does not show the correct number:
                    Log.i("Test", "The number is $number")
                }
            )
        }, onDraw = {
        drawRect(Color.Yellow, Offset.Zero, this.size)

        drawText(
            textMeasurer = textMeasurer,
            text = buildAnnotatedString {
                withStyle(ParagraphStyle(textAlign = TextAlign.Start)) {
                    // This works perfectly:
                    append("The number is: $number")
                }
            }
        )
    }
    )
}

I tried to remove the TestCanvasComposable Composable, and just put the Canvas code in the TopLevelComposable. After this change it works fine. But I'd like to keep them apart for potential re-use.

Upvotes: 2

Views: 353

Answers (1)

Chirag Thummar
Chirag Thummar

Reputation: 3232

You are losing the state because when @Composable recomposes it resets the value of the number.

You can use rememberUpdatedState to track changes in number and remember last updated number.

@Preview
@Composable
fun TopLevelComposable(modifier: Modifier = Modifier) {

    var number by rememberSaveable { mutableIntStateOf(0) }

    Column (modifier = Modifier.fillMaxSize()){
        TestCanvasComposable(number)
        Button(onClick = {

            number += 1

        }) {
            Text(
                text = "Increase",
                modifier = modifier
            )
        }
    }
}

@Composable
fun TestCanvasComposable(
    number: Int,
    modifier: Modifier = Modifier
) {
    val num by rememberUpdatedState(newValue = number)
    val textMeasurer = rememberTextMeasurer()
    Canvas(modifier = modifier
        .fillMaxWidth()
        .height(30.dp)
        .pointerInput(Unit) {
            detectTapGestures(
                onTap = {
                    // This does not show the correct number:
                    Log.i("Test", "The number is $num")
                }
            )
        }, onDraw = {
        drawRect(Color.Yellow, Offset.Zero, this.size)

        drawText(
            textMeasurer = textMeasurer,
            text = buildAnnotatedString {
                withStyle(ParagraphStyle(textAlign = TextAlign.Start)) {
                    // This works perfectly:
                    append("The number is: $num")
                }
            }
        )
    }
    )
}

It's working Check this code

Upvotes: 2

Related Questions