Search code examples
kotlinandroid-jetpack-composecompose-desktop

How to prevent unnecessary recompositions in Jetpack Compose


I have a TextField for a search query and a Button that will execute the search and the results are shown in a column. Since the search takes a few seconds to run I want it to be executed on button press and not on text change.

Here is a simplified demonstration:

Column {
    val list = remember { mutableStateListOf<String>() }
    val textFieldValue = remember { mutableStateOf(TextFieldValue("")) }

    TextField(
        value = textFieldValue.value,
        onValueChange = { textFieldValue.value = it }
    )

    Button({
        list.clear()
        list.addAll(textFieldValue.value.text.split(""))
    }) {
        Text("Search")
    }

    list.forEach {
        println("test")
        Text(it)
    }
}

After the first time that the button is pressed, the foreach loop will run on text change. Even clicking on the TextField will rerun the loop. This doesn't run the search on text change, but re-renders the results and that causes glitches while typing in the text field.

How can this be prevented?


Solution

  • The above is true for Jetpack Compose. The author wanted to know about Compose Desktop, and it's not the same there yet, because it's in alpha and is not optimised that much yet.

    Modifying a mutableState value always leads to a recomposition of all views, which its read value.

    Any changes to value will schedule recomposition of any composable functions that read value. documentation

    The way to stop it is moving out all views that reads mutableState value into a separate view. It'll be recomposed for each mutableState value change, but it won't affect the container.

    In your case it's pretty simple: just move TextField and pass textFieldValue into the new function. You can forward all params you need, like modifier, textStyle, etc.

    @Composable
    fun TestView(
    ) {
        Column {
            val textFieldValue = remember { mutableStateOf(TextFieldValue("")) }
            val list = remember { mutableStateListOf<String>("test") }
    
            TextField(textFieldValue)
    
            Button({
                list.clear()
                list.addAll(textFieldValue.value.text.split(""))
            }) {
                Text("Search")
            }
    
            list.forEach {
                println("test $it")
                Text(it)
            }
        }
    }
    
    @Composable
    fun TextField(
        textFieldValue: MutableState<TextFieldValue>,
    ) {
        TextField(
            value = textFieldValue.value,
            onValueChange = { textFieldValue.value = it }
        )
    }
    

    I'm not sure why there's no system function with this semantics, but in compose they prefer State hoisting pattern to match UDF.