I have a `Composable` that displays an image, and ...
# compose
s
I have a
Composable
that displays an image, and when you click on it, it prints out the x and y position of the click, and the RGB color of the image. I’m noticing if the image is updated though, I end up getting RGB colors from the first image I clicked, not the latest. What is the correct way to handle this, as clearly I’m doing it wrong. Code in thread.
Copy code
@Composable
private fun GameImage(bot: Bot) {
    val capture by remember { bot.capture }

    var imageWidth by remember { mutableStateOf(0) }
    var imageHeight by remember { mutableStateOf(0) }

    capture?.let { capture ->
        Image(
            capture.imageBitmap20,
            "Screen",
            modifier = Modifier
                .fillMaxWidth()
                .onGloballyPositioned {
                    imageWidth = it.size.width
                    imageHeight = it.size.height
                }
                .pointerInput(Unit) {
                    detectTapGestures(
                        onTap = { offset ->
                            val x = offset.x
                            val y = offset.y
                            val percentWidth = x / imageWidth.toDouble()
                            val percentHeight = y / imageHeight.toDouble()
                            val clickX = (capture.width * percentWidth).roundToInt()
                            val clickY = (capture.height * percentHeight).roundToInt()
                            val color = capture.arrayImage20[clickX, clickY]
                            println("Click ${clickX.toString().ensureSize(4)}, ${clickY.toString().ensureSize(3)} - ${rgbToHexString(color)}")
                        }
                    )
                },
            contentScale = ContentScale.FillWidth
        )
    }
}
By changing
Copy code
capture?.let { capture ->
to
Copy code
capture?.let { nonNullCapture ->
and letting
detectTapGestures
access
capture
directly as the state delegate, it works, but then I have to handle the nullability of
capture
everywhere. It’s quite annoying.
s
It feels like the onTap lambda remains stale from the time that the composable is created and doesn’t “observe” the changes that happen to
capture
I’ve seen this being a problem before with lambdas referencing old stuff, maybe because that one isn’t a
@Composable
? This kind of reminds me of this problem, not sure if it is a 1:1 mapping of the problem though. I would probably try to extract that lambda to the top of the function, and maybe remember it with the key being the
capture
variable? After the
var imageHeight
line. Something like
Copy code
val onTapFunction: (offset: WhateverTypeThatIs) -> Unit by remember(capture) {
    { offset ->
        //your onTap function here
    }
}
is what first comes to mind? And the wrap this like
Copy code
val currentOnTapFunction = rememberUpdatedState(onTapFunction)
and later inside your onTap just call
currentOnTapFunction
. Really not sure about this, but throwing out some ideas, maybe you can figure it out, I hope I’m not leading you down the wrong path.
t
I think you need to pass something other than
Unit
to the
pointerInput
key
A trick I use is to generate a UUID and wrap the bitmap in a data class with the ID, so that recompositions happen as bitmap data changes
s
Oh wow yeah pointerInput takes a key. Would giving it the key of
capture
not make it work? Since it did seem to work as the OP said when the proper latest value of capture was used?
t
Ah yeah, looks like
capture
is basically what I'm talking about then
alternatively, if
bot
is
@Stable
or
@Immutable
, just get rid of
remember { bot.capture }
and the Image will recompose
👍🏻 1
s
I’ve never used @Stable or @Immutable. I’ll have to look into them.
oh cool.
t
Ah yeah, it's not strictly required, it just informs Compose that any member property changes notify the composition through snapshot state, so it can skip an equality check when determining what to recompose (if I understand the system correctly)