I have created a "Spinner" Composable (I'm working in Compose-Desktop, but the control is the same i...
r
I have created a "Spinner" Composable (I'm working in Compose-Desktop, but the control is the same idea as a Spinner), which is comprised of an
OutlinedTextField
and a
DropdownMenu
. Here is the definition for it:
Copy code
fun <T : Any> Spinner(viewModel: SpinnerViewModel<T>, modifier: Modifier = Modifier)
And here is the
SpinnerViewModel
interface:
Copy code
interface SpinnerViewModel<T : Any> {
    val items: Flow<List<T>>
    var selectedItem: T?
}
The Composable is stateful and does the following:
Copy code
val items = viewModel.items.collectAsState(emptyList())
val windowValue = derivedStateOf { viewModel.selectedItem?.toString() ?: "Select" }
val expanded = remember { mutableStateOf(false) }
and then uses
items
and
expanded
as state for the
DropdownMenu
and
windowValue
for the
OutlinedTextField
. The items themselves are produced by a periodic job upstream that runs
adb devices
every second, maps the output to a collection of models, and then emits that collection downstream:
Copy code
private val timer = flow {
    println("Timer flow starting")
    while (currentCoroutineContext().isActive) {
        emit(Unit)
        delay(1000L)
    }
}

private val adbWatcherContext = newSingleThreadContext("adb-devices-watcher")
val devices: Flow<Set<Device>> = timer.mapNotNull { refreshDevices() }.flowOn(adbWatcherContext)
The weird thing is that every time I click the "Spinner" to expand it the whole Flow seems to be restarted since I see the "Timer flow starting" console message showing up with every click. Presumably I'm just piling new Jobs on to my coroutine context without canceling previous ones (or something even worse). I appreciate any insight you can offer as to what I'm doing wrong here or what a better approach might be! And sorry for what I'm certain is an extra amount of dumb sprinkled in to my code snippets while I'm still figuring things out.
🧵 2