2

I have an offset value that is calculated or reset as

var offset by remember(key1 = contentScale, key2 = imageBitmap) {
    mutableStateOf(Offset.Unspecified)
}

This is basically a color detector from an Image but when ContentScale of the custom Image I wrote or ImageBitmap changes I want to reset it to Offset.Unspecified so it doesn't draw markers outside of area that is image being drawn, the grey area in gif.

enter image description here

This is simplified but reproducible code, I use Jetpack Compose 1.2.0-beta01

@Composable
fun ImageColorDetector(
    modifier: Modifier = Modifier,
    imageBitmap: ImageBitmap,
    contentScale: ContentScale = ContentScale.FillBounds,
    onColorChange: (ColorData) -> Unit
) {

    var offset by remember(key1 = contentScale, key2 = imageBitmap) {
        mutableStateOf(Offset.Unspecified)
    }

    println(
        "✊ImageColorDetector() imageBitmap: $imageBitmap\n" +
                "bitmap: ${imageBitmap.asAndroidBitmap()}\n" +
                "width: ${imageBitmap.width}, height: ${imageBitmap.height}\n" +
                "offset: $offset\n" +
                "contentScale: $contentScale\n\n"
    )

    Box(
        modifier = modifier
            .pointerInput(Unit) {
                detectDragGestures { change, dragAmount ->
                    offset = change.position
                    println("onTouchEvent:  offset: $offset")
                }
            }
    ) {
        Image(
            bitmap = imageBitmap,
            contentScale = contentScale,
            contentDescription = null
        )
    }
    Text("Offset: $offset")
}

In detectDragGestures I see that offset is updated on drag gesture and when ImageBitmap or ContentScale changes, I saw in parent and this composable that they are not the same instances, so remember should be recalculate with new keys but it doesn't seem to work.

I/System.out: ⛺️ ImageColorDetectionDemo  imageBitmap: androidx.compose.ui.graphics.AndroidImageBitmap@5552c68
I/System.out: bitmap: android.graphics.Bitmap@93bc881
I/System.out: width: 236, height: 394
I/System.out: contentScale: androidx.compose.ui.layout.ContentScale$Companion$Crop$1@3226649
I/System.out: 
I/System.out: ✊ImageColorDetector() imageBitmap: androidx.compose.ui.graphics.AndroidImageBitmap@5552c68
I/System.out: bitmap: android.graphics.Bitmap@93bc881
I/System.out: width: 236, height: 394
I/System.out: offset: Offset(0.0, 0.0)
I/System.out: contentScale: androidx.compose.ui.layout.ContentScale$Companion$Crop$1@3226649
I/System.out: 
I/System.out: ⛺️ ImageColorDetectionDemo  imageBitmap: androidx.compose.ui.graphics.AndroidImageBitmap@7607f98
I/System.out: bitmap: android.graphics.Bitmap@286d3f1
I/System.out: width: 736, height: 920
I/System.out: contentScale: androidx.compose.ui.layout.ContentScale$Companion$Crop$1@3226649
I/System.out: 
I/System.out: ✊ImageColorDetector() imageBitmap: androidx.compose.ui.graphics.AndroidImageBitmap@7607f98
I/System.out: bitmap: android.graphics.Bitmap@286d3f1
I/System.out: width: 736, height: 920
I/System.out: offset: Offset(0.0, 0.0)
I/System.out: contentScale: androidx.compose.ui.layout.ContentScale$Companion$Crop$1@3226649

ContentScale is from default image code; each is an Object, so changing from Crop to Fit means a key is set.

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • I assume you are using a `MutableState` object to pass as the `contentScale` parameter, yes? – Richard Onslow Roper May 19 '22 at 14:36
  • If so, have you tried passing `contentScale.hashCode()` as the key instead of the object itself? I've seen Compose struggle with non-primitives in its early days. – Richard Onslow Roper May 19 '22 at 14:37
  • Yes, i checked the `contentScale.hashCode()` each change they are actually new ones and figure out the solution. It's related to `pointerInput ` creating a block with the initial values and not updating it but i can't figure out why it needs them only when remember has keys. Adding exact keys makes sure that offset triggers a recomposition. – Thracian May 19 '22 at 14:50
  • Well you haven't posted the `pointerInput` code to be fair, so it's a bit hard to assist/clarify on that. – Richard Onslow Roper May 19 '22 at 15:04
  • `pointerInput` is standard compose gesture code. I posted in question but i am still trying to figure few things out that's why i didn't posted an answer. – Thracian May 19 '22 at 15:07
  • Oh I'm sorry I didn't realise you actually had posted the code for the input. Sorry for the confusion. I do know what `pointerInput` is, of course, you shared all those tutorials in a question I asked. – Richard Onslow Roper May 19 '22 at 15:15

1 Answers1

2

I changed

modifier
    .pointerInput(Unit) {
        detectDragGestures { change, dragAmount ->
            offset = change.position
            println("onTouchEvent:  offset: $offset")
        }
    }

to

modifier
    .pointerInput(contentScale, imageBitmap) {
        detectDragGestures { change, dragAmount ->
            offset = change.position
            println("onTouchEvent:  offset: $offset")
        }
    }

When a [pointerInput] modifier is created by composition, if [block] captures any local variables to operate on, two patterns are common for working with changes to those variables depending on the desired behavior.
Specifying the captured value as a [key][key1] parameter will cause [block] to cancel and restart from the beginning if the value changes:

Modifier.pointerInput holds to previous MutableState instance while new instance of MutableState is created when any remember keys change and Modifier.pointerInput has no way accessing. It still hold previous instance and gesture loop runs with old MutableState with no updates.Restarting pointerInput with keys makes sure of the latest MutableState is read.

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • What do you mean when "size changes, it resets". You mean `remember` is dependent on `size`? But that doesn't make sense because size is `wrapped` inside it, it is the one job `remember` does, is to preserve the wrapped value in memory. Was that a typing error @Thracian? – Richard Onslow Roper May 19 '22 at 15:29