0

I have basic composable as in the following:

var timer by remember { mutableStateOf(0) }
        Column {
            Text(text = timer.toString())
            Button(onClick = { timer++ }) {
                Text(text = "Add")
            }
        }

When button is clicked, internal state which is timer will be increased by one and since it has been used in the Text component, recomposition will be occurred. But if I have LazyColumn instead of Column as in the following:

var timer by remember { mutableStateOf(0) }
        LazyColumn {
            item(contentType = "timer") {
                Text(text = timer.toString())
            }
            item {
                Button(onClick = { timer++ }) {
                    Text(text = "Add")
                }
            }
        }

Recomposition does not occur, Do not we expect it to be recomposed since the state has been changed?

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • You may be confused how I check if recomposition is occurred. So, I have created SideEffect and I have another counter which was updated inside the SideEffect – Ahmet Gultekin Jan 23 '23 at 21:23
  • You should explain better what is your issue. Your code works, and when you click the `Button`, the `Text` is updated with the new value.Other functions that don't depend on the value are not recomposed. – Gabriele Mariotti Jan 24 '23 at 11:53
  • I think I found the problem. But before that I can explain the issue again. The code block I provided above is composable function. Furthermore, I have `SideEffect` after `LazyColumn` and `SideEffect` will run after every successful recomposition. When I click the button, recomposition must happen to update the value of `Text` component and which happens. But the code block of `SideEffect` does not trigger. – Ahmet Gultekin Jan 24 '23 at 20:22
  • I think I know what it is happening. The definition of `SideEffect` is not clear. It says it is called after every successful recomposition but in my case recomposition happens on the scope of the first item of `LazyColumn` but my `SideEffect` is on the scope of my composable function. That's why in that scope, recomposition does not happen and SideEffect will not be triggered – Ahmet Gultekin Jan 24 '23 at 20:28

2 Answers2

2

When a State is read it triggers recomposition in nearest scope.

First example:

    var timer by remember { mutableStateOf(0) }
    SideEffect {
        Log.i("info", "xxxx")
    }

    LogCompositions("JetpackCompose.app", "Composable scope")

    LazyColumn {

        item(contentType = "timer") {
            LogCompositions("JetpackCompose.app", "Item Text Scope")
            Text(text = timer.toString())
        }
        item {
            LogCompositions("JetpackCompose.app", "Item Button Scope")
            Button(onClick = { timer++ }) {
                LogCompositions("JetpackCompose.app", "Button Scope")
                Text(text = "Add")
            }
        }
    }

When the screen is displayed you can find:

Compositions: Composable scope 0
Compositions: Item Text Scope 0
Compositions: Item Button Scope 0
Compositions: Button Scope 0
I  xxxx

When the Button "Add" is clicked only the item with the Text is recomposed.

Compositions: Item Text Scope 1

It happens because it recompose the scope that are reading the values that changes: Text(text = timer.toString()).

Using a Column instead of a LazyColumn:

    var timer by remember { mutableStateOf(0) }

    SideEffect {
        Log.i("info", "xxxx")
    }
    LogCompositions("JetpackCompose.app", "Composable scope")
    Column {

        LogCompositions("JetpackCompose.app", "Column Scope")
        Text(text = timer.toString())

        Button(onClick = { timer++ }) {
            LogCompositions("JetpackCompose.app", "Button Scope")
            Text(text = "Add")
        }
    }

When the screen is diplayed:

Compositions: Composable scope 0
Compositions: Column Scope 0
Compositions: Button Scope 0
I  xxxx

When the Button "Add" is clicked all the composable is recomposed:

Compositions: Composable scope 1
Compositions: Column Scope 1
I  xxxx

Also in this case it happens because it recompose the scope that are reading the values that changes: Text(text = timer.toString()).
The scope is a function that is not marked with inline and returns Unit. The Column is an inline function and it means that Column doesn't have an own recompose scopes.


To log composition use this composable:

class Ref(var value: Int)

// Note the inline function below which ensures that this function is essentially
// copied at the call site to ensure that its logging only recompositions from the
// original call site.
@Composable
inline fun LogCompositions(tag: String, msg: String) {
    if (BuildConfig.DEBUG) {
        val ref = remember { Ref(0) }
        SideEffect { ref.value++ }
        Log.d(tag, "Compositions: $msg ${ref.value}")
    }
}
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
0

I do not know what do you mean by saying Recomposition does not occur. The text is updating in both peace of code you have in the question

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 24 '23 at 09:04