7

I've been playing with Jetpack Compose Desktop. I noticed something I really don't understand:

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application

@Composable
@Preview
fun App() {
    var text by mutableStateOf("Hello, World!")

    MaterialTheme {
        TextField(text, onValueChange = { text = it })
        Button(onClick = {
            text = "Hello, Desktop!"
        }) {
            Text(text)
        }
    }
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        App()
    }
}

Why am I able to change the text in the TextField? I thought, that on every recompose the mutable state get reinstanciated with the initial Value: so the Text should not be able to change

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application

@Composable
@Preview
fun App() {
    var text by mutableStateOf("Hello, World!")

    Column {
        TextField(text, onValueChange = { text = it })
        Button(onClick = {
            text = "Hello, Desktop!"
        }) {
            Text(text)
        }
    }
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        App()
    }
}

However, if you replace the MaterialTheme with an Column it suddendly works as expected and you aren't able to change the text in the TextField.

Why is that? Is that a bug or a feature?

BierDav
  • 1,219
  • 1
  • 10
  • 27

2 Answers2

11

It's a feature of Compose about scoping and smart recomposition. You can check my detailed answer here.

What really makes your whole Composable to recompose is Column having inline keyword.

@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) {
    val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
    Layout(
        content = { ColumnScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

Let's say you have a Composable sets background color initially at composition and changes at each recomposition which creates its own scope. Lambda without inline is considered a scope.

@Composable
fun RandomColorColumn(content: @Composable () -> Unit) {

    Column(
        modifier = Modifier
            .padding(4.dp)
            .shadow(1.dp, shape = CutCornerShape(topEnd = 8.dp))
            .background(getRandomColor())
            .padding(4.dp)
    ) {
        content()
    }
}

With

@Composable
fun App2() {
    var text by mutableStateOf("Hello, World!")

    RandomColorColumn {
        TextField(text, onValueChange = { text = it })
        Button(onClick = {
            text = "Hello, Desktop!"
        }) {
            Text(text)
        }
    }
}

And one with Column

@Composable
fun App() {
    var text by mutableStateOf("Hello, World!")

    Column(modifier = Modifier.background(getRandomColor())) {
        TextField(text, onValueChange = { text = it })
        Button(onClick = {
            text = "Hello, Desktop!"
        }) {
            Text(text)
        }
    }
}

Random color function

fun getRandomColor() =  Color(
    red = Random.nextInt(256),
    green = Random.nextInt(256),
    blue = Random.nextInt(256),
    alpha = 255
)

You will see that Column background color will change every time you change text but not with App2()

enter image description here

Thracian
  • 43,021
  • 16
  • 133
  • 222
-1

I thought, that on every recompose the mutable state get reinstanciated with the initial Value: so the Text should not be able to change

The point of having a state when recomposing is to prevent a variable from being re-initialized. The initial state is used only once when the first composition occurs and isn't used again when further recompositions occur. If however the composable is removed from the UI tree and then reinstated later on, the state of the variable will be destroyed and re-initialized as this is not really a recomposition but a composition (as if it were happening for the first time).

Johann
  • 27,536
  • 39
  • 165
  • 279