I'm checking a flag value passed by a higher order component to my composable but the value returned is always the initial one...
This is my component:
@Composable
fun Test(enabled: Boolean) {
var expanded by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
if (enabled) expanded = !expanded
println("enabled is $enabled") //always prints the initial value of enabled
}
) {
OutlinedTextField(
value = "value",
onValueChange = { },
modifier = Modifier.fillMaxWidth(),
enabled = enabled,
readOnly = true,
label = { Text("Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
DropdownMenuItem(
onClick = {
expanded = false
},
text = {
Text("Content")
}
)
}
}
}
The code in onExpandedChange
ignores the current value of enabled
.
For example, if I run the code below, I can see that the OutlinedTextField
changes to enabled state after 2 seconds but onExpandedChange
will use the initial (old) value of enabled.
Surface(
color = MaterialTheme.colorScheme.background
) {
var flag by remember { mutableStateOf(false) }
println("flag is $flag")
LaunchedEffect(Unit) {
delay(2000)
flag = !flag
println("flag is now $flag")
}
Test(flag)
}
What am I doing wrong?
edit: It works fine if I do this:
@Composable
fun Test(enabled: Boolean) {
var expanded by remember { mutableStateOf(false) }
var enabledState by remember {
mutableStateOf(enabled)
}
LaunchedEffect(enabled) {
enabledState = enabled
}
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
if (enabledState) expanded = !expanded
}
) {
OutlinedTextField(
value = "value",
onValueChange = { },
modifier = Modifier.fillMaxWidth(),
enabled = enabled,
readOnly = true,
label = { Text("Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
DropdownMenuItem(
onClick = {
expanded = false
},
text = {
Text("Content")
}
)
}
}
}
but it does not work if I do this:
@Composable
fun Test(enabled: Boolean) {
var expanded by remember { mutableStateOf(false) }
val enabledState by remember(enabled) {
mutableStateOf(enabled)
}
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
if (enabledState) expanded = !expanded
}
) {
OutlinedTextField(
value = "value",
onValueChange = { },
modifier = Modifier.fillMaxWidth(),
enabled = enabled,
readOnly = true,
label = { Text("Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
DropdownMenuItem(
onClick = {
expanded = false
},
text = {
Text("Content")
}
)
}
}
}
and I have no idea why...
Recomposition is not ignoring updated function parameter but closure of onExpandedChange
is. It retains previous instance of MutableState
even if you re-instantiate it by changing remember
key. So what you should be doing is updating value of current instance of MutableState
instead of
val enabledState = remember(enabled) {
mutableStateOf(enabled)
}
you should either use rememberUpdatedState as Bruno suggested or update value with
val enabledState by remember {
mutableStateOf(enabled)
}.apply {
value = enabled
}
which is what rememberUpdatedState
implementation is
Difference between remember and rememberUpdatedState in Jetpack Compose?