Skip to content

Instantly share code, notes, and snippets.

@tpoisson
Last active April 1, 2023 10:36
Show Gist options
  • Save tpoisson/15835d1df5a9f10f0baea838cea1dc1e to your computer and use it in GitHub Desktop.
Save tpoisson/15835d1df5a9f10f0baea838cea1dc1e to your computer and use it in GitHub Desktop.
Android Jetpack Compose Dropdown
/**
* Parameterized Dropdown for Jetpack Compose and Material3
* @param options list of options to display
* @param selectedValue the selected value for this field
* @param itemValue method called to determine the selected value matching options
* @param textValue the method called on object to display its label
* @param placeholder the placeholder displayed in the textfield
* @param enabled enable or disable the field
* @param onChange called when option is selected
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun <T> Dropdown(
options: List<T>,
modifier: Modifier = Modifier,
selectedValue: T?,
itemValue: (T?) -> Any? = { it },
textValue: (T) -> String = { it.toString() },
placeholder: @Composable (() -> Unit)? = null,
enabled: Boolean = true,
loading: Boolean = false,
onChange: (T) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
// We want to react on tap/press on TextField to show menu
ExposedDropdownMenuBox(
modifier = modifier,
expanded = expanded,
onExpandedChange = {
expanded = !expanded
},
) {
Column {
TextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
modifier = Modifier.menuAnchor(),
readOnly = true,
enabled = enabled,
value = selectedValue?.let { textValue(it) } ?: "",
onValueChange = {},
label = placeholder,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.textFieldColors(),
)
if (loading) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
}
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
options.forEach { selectionOption ->
val currentIsSelectedValue = itemValue(selectionOption) == itemValue(selectedValue)
val backgroundColor = if (currentIsSelectedValue) {
Modifier.background(MaterialTheme.colorScheme.secondary)
} else {
Modifier
}
val menuItemColors = if (currentIsSelectedValue) {
MenuDefaults.itemColors(textColor = MaterialTheme.colorScheme.onSecondary)
} else {
MenuDefaults.itemColors()
}
DropdownMenuItem(
modifier = backgroundColor,
text = { Text(textValue(selectionOption)) },
colors = menuItemColors,
onClick = {
onChange(selectionOption)
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
}
@Composable
@Preview(showSystemUi = true)
private fun DropdownPreviewSelectedValue() {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Dropdown(
options = listOf("A", "B"),
selectedValue = "A",
placeholder = { Text("Pick a value") }
) {
}
Dropdown(
options = listOf("A", "B"),
selectedValue = "A",
) {
}
Dropdown(
enabled = false,
options = listOf("A", "B"),
selectedValue = null,
placeholder = { Text("This dropdown is disabled") }
) {
}
Dropdown(
modifier = Modifier.width(200.dp),
enabled = true,
options = listOf("A", "B"),
selectedValue = null,
placeholder = { Text("This dropdown has a width of 200.dp") }
) {
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment