tariqebadi
tariqebadi

Reputation: 270

Editable Dynamic ExposedDropDownMenuBox in Jetpack Compose

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)

Upvotes: 8

Views: 5616

Answers (2)

Mateus Batista
Mateus Batista

Reputation: 11

Just a complement:

If you want to show the dropdown menu always when the user inputs some text, just Add an expanded = true inside de onValueChange in the TextField

onValueChange = { 
        selectedOptionText = it
        expanded = true
}

Upvotes: 1

tariqebadi
tariqebadi

Reputation: 270

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,
                    )
                }
            }
        }
    }
}

Upvotes: 11

Related Questions