3

I am doing a Google Codelab and have the following code (drawable changes when button is pressed):

@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    var result by remember { mutableStateOf(1) }
    val imageResource = when (result) {
        1 -> R.drawable.dice_1
        2 -> R.drawable.dice_2
        3 -> R.drawable.dice_3
        4 -> R.drawable.dice_4
        5 -> R.drawable.dice_5
        else -> R.drawable.dice_6
    }

    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(painter = painterResource(imageResource), contentDescription = result.toString())

        Button(onClick = { result = (1..6).random() }) {
            Text(text = stringResource(R.string.roll_text))
        }
    }
}

How does Compose know that result has changed when it triggers recomposition? Does it detect that through Image's painter parameter? Or maybe through remember or mutableStateOf()?

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
Vamoos
  • 327
  • 4
  • 10

2 Answers2

3

The answer is in the documentation:

mutableStateOf creates an observable MutableState<T>, which is an observable type integrated with the compose runtime. Any changes to this value schedules recomposition of any composable functions that read the value.

In your example when you click the button the result changes. imageResource reads this value and it triggers recomposition in nearest scope. Since the Column is an inline function (it means that Column doesn't have an own recompose scopes) the content of the Column is recomposed.

You can check it visually using this function:

fun randomColor() = Color(
    Random.nextInt(256),
    Random.nextInt(256),
    Random.nextInt(256),
    alpha = 255
)

Apply the same function to the background color of the Button and the Column:

    Column(
        modifier = Modifier.background(getRandomColor()),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            modifier = Modifier.background(getRandomColor()),
            painter = painterResource(R.drawable.ic_xxx), contentDescription = result.toString())

        Button(onClick = { result = (1..6).random() },
                colors= ButtonDefaults.buttonColors(containerColor = getRandomColor()),) {
            Text(text = "Button")
        }
    }

enter image description here

Note that in this example the Image uses a static resource and not the imageResource.

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
1

It is quite difficult to answer this question briefly, in a simplified form - Compose uses the Snapshot System to detect changes. It uses an observer pattern to observe changes to state. The parts of the code that depend on the state register change listeners, when the value changes, all registered listeners are notified.

So in your example mutableStateOf(1) automatically creates listener in Composer, and DiceWithButtonAndImage function gets notified on every sate change.

bylazy
  • 1,055
  • 1
  • 5
  • 16