Jetpack Compose - ExposedDropdownMenu - Autocomplete

685 views Asked by At

I'm trying to implement an autocomplete textfield (I sure hope Compose has a built-in one at some point, like XML did) using the ExposedDropdownMenu and while I've gotten it to basically work, it's acting very strange.

If I use this code below (ignore any bits that have things specific to my app, it should be pretty apparent) then I cannot enter any text in the TextField using my physical keyboard, but using the software keyboard in the emulator allows me to type in it:

val allLocations = listOf("FLSC") // just a hardcoded test list of a single value for now
val dropDownOptions = remember { mutableStateOf(listOf<String>()) }
val dropDownExpanded = remember { mutableStateOf(false) }

ExposedDropdownMenuBox(
            modifier = modifier,
            expanded = dropDownExpanded.value,
            onExpandedChange = {
                dropDownExpanded.value = !dropDownExpanded.value
            }) {
            TextField(
                modifier = Modifier
                    .menuAnchor()
                    .fillMaxWidth()
                    .onFocusChanged { focusState ->
                        if (!focusState.isFocused)
                            dropDownExpanded.value = false
                    },
                value = viewModel.location.value.text, // stored in State in viewmodel
                label = {
                    Text(
                        text = stringResource(id = R.string.location),
                        style = TextStyle(
                            color = ShotTrackerColors.textFieldTextColor,
                            fontSize = MaterialTheme.typography.subtitle1.fontSize
                        )
                    )
                },
                onValueChange = { enteredText ->
                    viewModel.onEvent(AddEditShotsEvent.EnteredLocation(enteredText))
                    
                    dropDownOptions.value =
                        allLocations.filter { it.contains(viewModel.location.value.text, ignoreCase = true) && it.lowercase() != viewModel.location.value.text.lowercase() }
                            .take(3)
                    
                    dropDownExpanded.value = dropDownOptions.value.isNotEmpty()
                }
            )
            ExposedDropdownMenu(
                expanded = dropDownExpanded.value,
                onDismissRequest = {
                    dropDownExpanded.value = false
                },
            ) {
                dropDownOptions.value.forEach { text ->
                    DropdownMenuItem(
                        text = {
                            Text(text = text)
                        },
                        onClick = {
                            viewModel.onEvent(AddEditShotsEvent.EnteredLocation(text))
                        
                            dropDownExpanded.value = false
                        }
                    )
                }
            }
        }

Like I said, it technically works, but only using the emulator software keyboard to type. My computer's physical keyboard does nothing, almost like the TextField is read-only.

I've tried very similar code, from Stack Overflow here somewhere, and it acts about the same except that I can actually type with the physical keyboard, as long as I do not type any characters that would trigger the ExposedDropdownMenu to appear. I can only type a single character if that character is contained in the dropdown options (so like an 'f' in this example). So it seems like the dropdown menu has something to do with blocking text entry but I just don't know how or why. Code for second one:

val allLocations = listOf("FLSC") // hardcoded test list
val dropDownExpanded = remember { mutableStateOf(false) }
// no "dropDownOptions" here as that's basically below as the "filterOpts" var

ExposedDropdownMenuBox(
            expanded = dropDownExpanded.value,
            onExpandedChange = {
                dropDownExpanded.value = !dropDownExpanded.value
            }
        ) {
            TextField(
                modifier = Modifier
                    .menuAnchor()
                    .fillMaxWidth(),
                value = viewModel.location.value.text,
                onValueChange = {
                    viewModel.onEvent(AddEditShotsEvent.EnteredLocation(it))
                },
                label = {
                    Text(stringResource(id = R.string.location))
                }
            )
            // filter options based on text field value (i.e. crude autocomplete)
            val filterOpts = if (viewModel.location.value.text.isNotBlank()) {
                allLocations.filter {
                    it.contains(viewModel.location.value.text, ignoreCase = true)
                }
            }
            else {
                emptyList()
            }

            if (filterOpts.isNotEmpty()) {
                ExposedDropdownMenu(
                    expanded = dropDownExpanded.value,
                    onDismissRequest = {
                        dropDownExpanded.value = false
                    }
                ) {
                    filterOpts.forEach { option ->
                        DropdownMenuItem(
                            text = { Text(text = option) },
                            onClick = {
                                viewModel.onEvent(AddEditShotsEvent.EnteredLocation(option))

                                dropDownExpanded.value = false
                            }
                        )
                    }
                }
            }
        }
0

There are 0 answers