Based on the answer above referring to the gist, I would like to propose a similar solution just in one composable but accepting an object instead of a list. In the sample I just used a Pair<String, String> that could be replaced by any object.
package at.techbee.jtx.ui.compose.elements import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowDropDown import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @Composable fun SampleSpinner( list: List<Pair<String, String>>, preselected: Pair<String, String>, onSelectionChanged: (selection: Pair<String, String>) -> Unit ) { var selected by remember { mutableStateOf(preselected) } var expanded by remember { mutableStateOf(false) } // initial value Box { Column { OutlinedTextField( value = (selected.second), onValueChange = { }, label = { Text(text = "My List") }, modifier = Modifier.fillMaxWidth(), trailingIcon = { Icon(Icons.Outlined.ArrowDropDown, null) }, readOnly = true ) DropdownMenu( modifier = Modifier.fillMaxWidth(), expanded = expanded, onDismissRequest = { expanded = false }, ) { list.forEach { entry -> DropdownMenuItem( modifier = Modifier.fillMaxWidth(), onClick = { selected = entry expanded = false }, text = { Text( text = (entry.second), modifier = Modifier.wrapContentWidth().align(Alignment.Start)) } ) } } } Spacer( modifier = Modifier .matchParentSize() .background(Color.Transparent) .padding(10.dp) .clickable( onClick = { expanded = !expanded } ) ) } } @Preview(showBackground = true) @Composable fun SampleSpinner_Preview() { MaterialTheme { val entry1 = Pair("Key1", "Entry1") val entry2 = Pair("Key2", "Entry2") val entry3 = Pair("Key3", "Entry3") SampleSpinner( listOf(entry1, entry2, entry3), preselected = entry2, onSelectionChanged = { selected -> /* do something with selected */ } ) } }
Hope it helps!
////// EDIT /////
I have been playing around with this and I'm using now a solution that I like more. The Spacer overlay in the previous solution (also as proposed in answers before) is not really good when you would like to put a modifier on the element. So instead of the OutlinedTextField I am using a card in this solution:
import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowDropDown import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @OptIn(ExperimentalMaterial3Api::class) @Composable fun SpinnerSample( list: List<MyData>, preselected: MyData, onSelectionChanged: (myData: MyData) -> Unit, modifier: Modifier = Modifier ) { var selected by remember { mutableStateOf(preselected) } var expanded by remember { mutableStateOf(false) } // initial value OutlinedCard( modifier = modifier.clickable { expanded = !expanded } ) { Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Top, ) { Text( text = selected.name, modifier = Modifier.weight(1f) .padding(horizontal = 16.dp, vertical = 8.dp) ) Icon(Icons.Outlined.ArrowDropDown, null, modifier = Modifier.padding(8.dp)) DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, modifier = Modifier.fillMaxWidth() // delete this modifier and use .wrapContentWidth() if you would like to wrap the dropdown menu around the content ) { list.forEach { listEntry -> DropdownMenuItem( onClick = { selected = listEntry expanded = false onSelectionChanged(selected) }, text = { Text( text = listEntry.name, modifier = Modifier //.wrapContentWidth() //optional instad of fillMaxWidth .fillMaxWidth() .align(Alignment.Start) ) }, ) } } } } } @Preview(showBackground = true) @Composable fun SpinnerSample_Preview() { MaterialTheme { val myData = listOf(MyData(0, "Apples"), MyData(1, "Bananas"), MyData(2, "Kiwis")) SpinnerSample( myData, preselected = myData.first(), onSelectionChanged = { }, modifier = Modifier.fillMaxWidth() ) } } data class MyData ( val id: Int, val name: String )
However, the solution with the OutlinedTextField might still be interesting as an AutoCompletion TextField.