I created a Dropdown composable that I want to use for different lists of enums. To do this, the Dropdown accepts an onValueChange parameter that is a lambda function with an Any type.
I first tried having onValueChange
with a type of (Any) -> Unit
and I got a complaint of Type mismatch. Required: (Any) -> Unit. Found: (Color) -> Unit
.
I then used typealias Callback<T> = (T) -> Unit
and get the same thing with Type mismatch. Required: Callback<Any>. Found: Callback<Color>
.
If I cast the lambda to (Any) -> Unit
or Callback<Any>
, I get an Unchecked cast
warning. I can suppress this warning, but I'd rather fix it the "correct" way.
If I set the type of onValueChange
to Callback<*>
, I no longer get a type error when calling Dropdown, but Dropdown's internal onValueChange
call now has Type mismatch with Callback<*>. Required: Nothing. Found: Any
Example Uses
@Composable
fun ColorDropdown(color: Color, onColorChange: (Color) -> Unit) {
val colorOptions = listOf(Color.BLACK, Color.GRAYSCALE, Color.WHITE, Color.MONOCHROME)
// Type mismatch
Dropdown(color, options = colorOptions, onValueChange = onColorChange)
}
fun AlignDropdown(color: Align, onAlignChange: Callback<Align>) {
val alignOptions = listOf(Align.LEFT, Align.CENTER, Align.RIGHT)
// Unchecked cast
Dropdown(color, options = colorOptions, onValueChange = onAlignChange as Callback<Any>)
}
Dropdown file
typealias Callback<T> = (T) -> Unit
@Composable
fun Dropdown(value: Any, options: List<Any>, onValueChange: Callback<Any>) {
var expanded by remember { mutableStateOf(false) }
Box {
Button( onClick = { expanded = !expanded } ) { Text("Color: $value") }
DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false } ) {
options.forEach { option ->
DropdownMenuItem(
text = { Text(option.toString()) },
onClick = {
// Type mismatch with Callback<*>
// Required: Nothing. Found: Any.
onValueChange(option)
expanded = false
}
)
}
}
}
}
Your code is conceptually wrong, it is not type-safe - this is why Kotlin disallows it.
In ColorDropdown
you have a lambda which receives a Color
and it can work with colors only. Then you want to cast it to (Any) -> Unit
, so to a lambda which can receive any arbitrary object. What if we then invoke it passing a String or an Int?
If your idea is that the lambda always receives one of items passed in options
, then you have to make your function generic:
fun <T> Dropdown(value: T, options: List<T>, onValueChange: Callback<T>) {