Reputation: 71
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")
}
}
}
Upvotes: 6
Views: 4809
Reputation: 854
The answer provided by @Thracian is correct, but I want to clarify this section:
Compose recomposes only the "nearest" scope. A scope is non-inline Composable function that returns Unit.
In your example, Column
is an inline function, therefore the nearest scope non-inline Composable function is HelloContent
and this is the reason why it gets recomposed.
Upvotes: 0
Reputation: 66674
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.
Upvotes: 13
Reputation: 6835
Don't extract a val
outside the HelloContent
's calling site. Move the initialization logic to the lambda.
Upvotes: 0