I'm trying to get this drop down menu to be dynamic. When I type into the textfield, I expect the list to be updated (filtered for what the user types in) and the user can select from the filtered drop down list.
I've checked here, the sample code from the dev docs https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#ExposedDropdownMenuBox(kotlin.Boolean,kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)
I found that material 3 drop down menu will block user input if it is in an expanded state. If a user starts to type and we say
onValueChange = {
selectedOptionText = it
expanded = true
}
user is blocked from typing further because the ExposedDropdownMenu expanded blocks user input
How can I make a textfield dynamically open a drop down menu, and the list is updated based on user input? And the list items are selectable
To add some more context, I understand that DropdownMenu does not block user input if we say properties = PopupProperties(focusable = false)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
properties = PopupProperties(focusable = false)
)
But the UI behavior is not the same. I'm looking for DropdownMenuBox behavior with DropdownMenu properties = PopupProperties(focusable = false)
This problem is solved by taking the code from the docs
val options = listOf("Option 1", "Option 2", "Option 3", "Option 4", "Option 5")
var expanded by remember { mutableStateOf(false) }
var selectedOptionText by remember { mutableStateOf("") }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded },
) {
TextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
modifier = Modifier.menuAnchor(),
value = selectedOptionText,
onValueChange = { selectedOptionText = it },
label = { Text("Label") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.textFieldColors(),
)
// filter options based on text field value
val filteringOptions = options.filter { it.contains(selectedOptionText, ignoreCase = true) }
if (filteringOptions.isNotEmpty()) {
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
filteringOptions.forEach { selectionOption ->
DropdownMenuItem(
text = { Text(selectionOption) },
onClick = {
selectedOptionText = selectionOption
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
}
And we replace the nested ExposedDropdownMenu with
DropdownMenu(
modifier = Modifier
.background(Color.White)
.exposedDropdownSize(true),
properties = PopupProperties(focusable = false),
expanded = expanded,
onDismissRequest = { expanded = false },
)
the final code will look and behave as expected. When you type, the suggestions will follow dynamically and user input will not be blocked
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun docs() {
val options = listOf("Option 1", "comment", "Afghanistan", "Albania", "Algeria", "Andorra", "Egypt")
var expanded by remember { mutableStateOf(false) }
var selectedOptionText by remember { mutableStateOf("") }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded },
) {
TextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
modifier = Modifier.menuAnchor(),
value = selectedOptionText,
onValueChange = { selectedOptionText = it },
label = { Text("Label") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.textFieldColors(
focusedContainerColor = Color.White,
unfocusedContainerColor = Color.White
),
)
// filter options based on text field value
val filteringOptions = options.filter { it.contains(selectedOptionText, ignoreCase = true) }
if (filteringOptions.isNotEmpty()) {
DropdownMenu(
modifier = Modifier
.background(Color.White)
.exposedDropdownSize(true)
,
properties = PopupProperties(focusable = false),
expanded = expanded,
onDismissRequest = { expanded = false },
) {
filteringOptions.forEach { selectionOption ->
DropdownMenuItem(
text = { Text(selectionOption) },
onClick = {
selectedOptionText = selectionOption
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
}
}