In the XML way of building UIs for Android, there is a widget called Spinner that allows user to select one option out of multiple using a dropdown menu.
But there is no equivalent composable for it in Jetpack Compose. We provide this important composable covering multiple use cases.
If you have options: List<String>
and want the user to select one among it, then use the following composable :
@Composable
fun OutlinedSpinner(
modifier: Modifier = Modifier,
// Core props
options: List<String>,
state: MutableState<TextInputState>,
onStateChanged: (String) -> Unit = {},
// Customization
allowInput: Boolean = false,
icon: ImageVector? = null,
parentScrollState: ScrollState? = null,
)
Example :
val state = remember {
mutableStateOf(
TextInputState("Country")
)
}
OutlinedSpinner(
options = listOf("Bharat", "USA", "Russia", "China"),
state = state
)
Button(
modifier = Modifier.align(Alignment.CenterHorizontally),
onClick = {
if (state.hasValidInput()) {
showMessageDialog(
title = "Confirm selection",
message = "You selected : ${state.value()}"
)
}
}
) {
Text(text = "Submit")
}
OutlinedSpinner
usesTextInputLayout
internally, so it requiresTextInputState
as its state. Note thatInputConfig
validations don't work here, because valid options list is provided by you. Still, you can use theInputConfig#optional
parameter.
Suppose you have persons: List<Person>
where Person
class is defined as :
data class Person(
val id: Int,
val name: String
)
Now you want the user to select one among these persons
:
listOf(
Person(1, "Swami Vivekananda"),
Person(2, "Dr. Homi J. Bhabha"),
Person(3, "Dr. Vikram Sarabhai"),
Person(4, "Dr. APJ Abdul Kalam")
)
If you were to use the above explained String based Spinner, mapping
persons: List<Person> -> to -> names: List<String>
would be required. Further, after selection, selected person name had to be looked up in persons list to get the selected Person object. This seems like a long process for such a small task!
To solve for this specific use case, we have a generic version also. You could provide a generic List<T>
and user would be selecting one of these objects, T
.
class SpinnerState<T>(
val selection: MutableState<T?>,
val textInputState: MutableState<TextInputState>,
val labelExtractor: (T) -> String
)
Example :
val state = remember {
SpinnerState<Person>(
selection = mutableStateOf(null), // Perfill value can be defined here
textInputState = mutableStateOf(TextInputState("Person")),
labelExtractor = { it.name } // Specified what label to display for the object T
)
}
@Composable
fun <T> OutlinedSpinner(
modifier: Modifier = Modifier,
// Core props
options: List<T>,
state: SpinnerState<T>,
onStateChanged: (T) -> Unit = { },
// Customization
allowInput: Boolean = false,
leadingIcon: ImageVector? = null,
parentScrollState: ScrollState? = null,
)
Example :
OutlinedSpinner(
options = listOf(
Person(1, "Swami Vivekananda"),
Person(2, "Dr. Homi J. Bhabha"),
Person(3, "Dr. Vikram Sarabhai"),
Person(4, "Dr. APJ Abdul Kalam")
),
state = state,
allowInput = true
)
To validate whether user has selected the value and access the selected value simultaneously, use the ifSelected()
function and pass the lambda to handle the if selected case, wherein you get the selected value - T
:
fun ifSelected(
lambda: (T) -> Unit
)
Example :
Button(
onClick = {
state.ifSelected { person ->
showMessageDialog(
title = "Confirm selection",
message = "You selected : $person"
)
}
}
) {
Text(text = "Submit")
}
By default, the Spinner does not allow keyboard inputs. If you want the auto complete functionality wherein, user can type and list of suggestions will be filtered based on that, like this :
This feature is provided out of the box. Just pass allowInput: Boolean parameter as true
:
OutlinedSpinner(
options = listOf("Bharat", "USA", "Russia", "China"),
state = state,
allowInput = true
)
You can pass a leadingIcon: ImageVector
to be displayed in the OutlinedTextField
of the spinner.
Example :
OutlinedSpinner(
options = listOf("Bharat", "USA", "Russia", "China"),
state = state,
leadingIcon = Icons.Default.Star
)