In the process of migrating my app to Jetpack compose, I've come to a part of my app where a TextField needs autocompletion functionality.
However, as of version 1.0.0-alpha05, I couldn't find any functionality to achieve this using the Compose API. The closest thing I've found is the DropdownMenu
and DropdownMenuItem
composeables, but it seems like it would be a lot of manual plumbing required to create an autocomplete menu out of these.
The obvious thing to do is just wait for future updates to Jetpack Compose, of course. But I'm wondering, has anyone who encountered a this issue in their migrations found a solution?
Android AutoCompleteTextView is an editable text view which shows a list of suggestions when user starts typing text. When a user starts typing, a dropdown menu will be there based on the entered characters, defined in the threshold limit and the user can choose an item from the list to replace the text.
Jetpack Compose is stable, ready for production, and continues to add the features you've been asking us for. We've been thrilled to see tens of thousands of apps start using Jetpack Compose in production already and we can't wait to see what you'll build!
A LazyColumn is a vertically scrolling list that only composes and lays out the currently visible items. It's similar to a Recyclerview in the classic Android View system.
mutableStateOf creates an observable MutableState<T> , which is an observable type integrated with the compose runtime. Any changes to value will schedule recomposition of any composable functions that read value . In the case of ExpandingCard , whenever expanded changes, it causes ExpandingCard to be recomposed.
No at least till v1.0.2
so I implemented a nice working one in compose available in this gist
I also put it here:
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.window.PopupProperties
@Composable
fun TextFieldWithDropdown(
modifier: Modifier = Modifier,
value: TextFieldValue,
setValue: (TextFieldValue) -> Unit,
onDismissRequest: () -> Unit,
dropDownExpanded: Boolean,
list: List<String>,
label: String = ""
) {
Box(modifier) {
TextField(
modifier = Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
if (!focusState.isFocused)
onDismissRequest()
},
value = value,
onValueChange = setValue,
label = { Text(label) },
colors = TextFieldDefaults.outlinedTextFieldColors()
)
DropdownMenu(
expanded = dropDownExpanded,
properties = PopupProperties(
focusable = false,
dismissOnBackPress = true,
dismissOnClickOutside = true
),
onDismissRequest = onDismissRequest
) {
list.forEach { text ->
DropdownMenuItem(onClick = {
setValue(
TextFieldValue(
text,
TextRange(text.length)
)
)
}) {
Text(text = text)
}
}
}
}
}
How to use it
val all = listOf("aaa", "baa", "aab", "abb", "bab")
val dropDownOptions = mutableStateOf(listOf<String>())
val textFieldValue = mutableStateOf(TextFieldValue())
val dropDownExpanded = mutableStateOf(false)
fun onDropdownDismissRequest() {
dropDownExpanded.value = false
}
fun onValueChanged(value: TextFieldValue) {
dropDownExpanded.value = true
textFieldValue.value = value
dropDownOptions.value = all.filter { it.startsWith(value.text) && it != value.text }.take(3)
}
@Composable
fun TextFieldWithDropdownUsage() {
TextFieldWithDropdown(
modifier = Modifier.fillMaxWidth(),
value = textFieldValue.value,
setValue = ::onValueChanged,
onDismissRequest = ::onDropdownDismissRequest,
dropDownExpanded = dropDownExpanded.value,
list = dropDownOptions.value,
label = "Label"
)
As of compose 1.1.0-alpha06, Compose Material now offers an ExposedDropdownMenu
composable, API here, which can be used to implement a dropdown menu which facilitates the autocompletion process. The actual autocompletion logic has to be implemented yourself.
The API docs give the following usage example, for an editable field:
val options = listOf("Option 1", "Option 2", "Option 3", "Option 4", "Option 5")
var exp by remember { mutableStateOf(false) }
var selectedOption by remember { mutableStateOf("") }
ExposedDropdownMenuBox(expanded = exp, onExpandedChange = { exp = !exp }) {
TextField(
value = selectedOptionText,
onValueChange = { selectedOptionText = it },
label = { Text("Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = exp)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
// filter options based on text field value (i.e. crude autocomplete)
val filterOpts = options.filter { it.contains(selectedOption, ignoreCase = true) }
if (filterOpts.isNotEmpty()) {
ExposedDropdownMenu(expanded = exp, onDismissRequest = { exp = false }) {
filterOpts.forEach { option ->
DropdownMenuItem(
onClick = {
selectedOption = option
exp = false
}
) {
Text(text = option)
}
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With