Is passing the variable through the parameter of a composable considered as reading the value?

Viewed 58

Let's imagine we have the next 5 lines of code:

@Composable
fun MyParentComposable() {
    var myVariable by remember { mutableStateOf(0) } 
    MyChildComposable(myVariable)
}
  • I know whenever we change the myVariable we're going to recompose MyParentComposable and MyChildComposable and any other children inside the MyChildComposable() reading the value.

  • I know that if I don't use the myVariable in any composable inside MyChildComposable it will be still be recomposed when changing it because we're reading it somewhere (I'd guess in the parameter even though it's unused)

  • I know that if we pass a lambda and defer the read, then only the component that invokes the value and the parent scope will be recomposed MyChildComposable.

The question is, when passing myVariable to MyChildComposable, am I reading it or is there something else?

I wanted to see some decompiled code or something like that to understand it a bit deeper but I don't know where should I go. Hopefully someone can throw some light here to have something that I can say 'yeah it's because of that'

Same goes for this example

@Composable
fun MyParentComposable() {
    val myVariable = remember { mutableStateOf(0) } 
    MyChildComposable(myVariable.value)
}

I'm reading the value in the MyParentComposable() scope and passing it down MyChildComposable()

Edit:

Example without Lambda: ParentComposable and child get recomposed without any composable inside reading it, only a composable with a parameter

@Composable
fun MyParentComposable() {
    var myVariable by remember { mutableStateOf(0) }
    MyChildComposable(myVariable)

    Button(onClick = { myVariable += 1}) {
        Text(text = "Click")
    }
}

@Composable
fun MyChildComposable(myVariable: Int) {
// Without text, only the parameter there
}

Example With Lambda: Only ChildComposable gets recomposed after reading it inside.

@Composable
fun MyParentComposable() {
    var myVariable by remember { mutableStateOf(0) }
    var myLambdaVariable = { myVariable }
    MyChildComposable(myLambdaVariable)

    Button(onClick = { myVariable += 1}) {
        Text(text = "Click")
    }
}

@Composable
fun MyChildComposable(myLambdaVariable: () -> Int) {
    Text(text = "${myLambdaVariable()}")
}

Now the question is, why does the example WITHOUT lambda recomposes the child: Is it because passing the parameter is considered as reading? Is it because you're already reading it before passing by just the fact of doing: MyChildComposable(anyVariableHere) <-- Considered as reading in ParentComposableScope

I know using the by will cause a trigger read. But I need to understand what's triggering the recomposition, if reading it in ParentComposable plus setting it in the parameter of the ChildComposable. Does compose detects automatically this function is reading a property, is it considered reading as having settled in the parameter.

I want fine-grained information to understand what's happening when we set a parameter a parameter to the ChildComposable and even though "we're not reading it" the ChildComposable gets recomposed

2 Answers

Your question touches on 2 separate concepts, and has a succinct answer:

  1. Passing a parameter implies first reading it (in the caller). This is a basic concept of imperative programming and is modeled no differently in the case of Compose. The caller reads a (state) variable value from a memory location to put it on the stack, from which the called function retrieves it. Because the caller has to read the variable to pass its value, a change in the value must trigger caller recomposition.
  2. Skipping if the inputs haven't changed: when the input (parameter) of the child composable changes it will recompose. This is independent of whether the child composable actually uses the parameter value: https://developer.android.com/jetpack/compose/lifecycle#skipping

This is the basic thought model to use in this case to explain recomposition behavior; there may be optimizations under the hood, but it does not change the modeled behavior you see.

Keep in mind that the rules of recomposition are not set in stone, and may change; ideally you would not rely on recomposition necessarily acting this way, the model is useful mainly for optimizations that are valid at the present time.

It's important to note that reading is triggered because the getter of the local property is called. This is due the fact you used by to delegate the keyword and any usage (even passing as parameter) will be considered as call to the getter.

If you used assignment, passing it as paramere will not trigger the getter, but call to the .value

Related