Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Jetpack Compose - Composable Function get recomposed each time Text-field value changes

I'm using TextField to get user input and using stateflow to handle the text state/value in viewmodel.

The thing is every time TextField's value changes the HomeContent() function get recomposed.
Layout inspector output image My question is, is it ok that the whole HomeContent() function is getting recomposed just because the TextField value changes or is there a way of avoiding function recomposition?

ViewModel

class MyViewModel() : ViewModel() {
    private val _nameFlow = MutableStateFlow("")
    val nameFlow = _nameFlow.asStateFlow()

    fun updateName(name: String) {
        _nameFlow.value = name
    }
}

MainActivity

class MainActivity : ComponentActivity() {
    private val myViewModel by viewModels<MyViewModel>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppArchitectureTheme {
                HelloScreen(myViewModel)
            }
        }
    }
}

HomeScreen

@Composable
fun HelloScreen(viewModel: MyViewModel) {
    val name = viewModel.nameFlow.collectAsState()
    HelloContent(
        provideName = { name.value },
        onNameChange = { viewModel.updateName(it) })
}

@Composable
fun HelloContent(
    provideName: () -> String,
    onNameChange: (String) -> Unit
) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello,",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = provideName(),
            onValueChange = { onNameChange(it) },
            label = { Text("Name") }
        )
        Button(
            onClick = {}
        ) {
            Text(text = "Dummy Button")
        }
    }
}
like image 317
Harsh Avatar asked Sep 01 '26 00:09

Harsh


1 Answers

This is indeed the expected behavior. Compose recomposes only the "nearest" scope. A scope is non-inline Composable function that returns Unit.

You can read answers about this in the links below. However, the difference between the question and answers in the link(s) is you also defer reading change which keeps Composables in between from being recomposed when the value inside the lambda changes.

Jetpack Compose Smart Recomposition

Why does mutableStateOf without remember work sometimes?

How can I launch recomposition when a specified Flow changed in Jetpack Compose?

If you change OutlinedTextField to

@Composable
private fun MyOutlinedTextField(
    provideName: () -> String,
    onNameChange: (String) -> Unit
) {
    OutlinedTextField(
        value = provideName(),
        onValueChange = { onNameChange(it) },
        label = { Text("Name") }
    )
}

this function will only be recomposed when the parameter it reads changes. If there were multiple MyOutlinedTextField, only the one that reads the respective value would change.

However, a subtle and very important difference between this and the answers in links that I provided, is deferring state-read by passing

provideName: () -> String instead of provideName: String

This defers state-reads from descendent Composables to only the one that reads this lambda. That's how you trigger recompositions only for MyOutlinedTextField scope.

If you update your function as below, you will see that it will again recompose the whole HelloContent scope including the Text inside the Column here,

@Composable
fun HelloContent(
    provideName: String,
    onNameChange: (String) -> Unit
) {
    Column(
        modifier = Modifier
            .background(getRandomColor())
            .padding(16.dp)
    ) {

        Column( Modifier
            .background(getRandomColor())) {
            Text(
                text = "Hello,",
                modifier = Modifier.padding(bottom = 8.dp),
            )
        }

        MyOutlinedTextField(provideName = provideName, onNameChange = onNameChange)

        Button(
            onClick = {}
        ) {
            Text(
                text = "Dummy Button", modifier = Modifier
                    .background(getRandomColor())
            )
        }
    }
}

This function recomposes the Column where the random color function is

fun getRandomColor(): Color {
    return Color(
        Random.nextInt(256),
        Random.nextInt(256),
        Random.nextInt(256),
        255
    )
}

Also, this is the tutorial I prepared which covers scoped recomposition and deferring reads. You can check out this example for reading offset, and padding changes.

https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/blob/master/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter4_state/Tutorial4_7_3ComposePhases3.kt

enter image description here

like image 196
Thracian Avatar answered Sep 02 '26 15:09

Thracian



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!