I recently needed to add a debouncing operator on my button clicks in a Compose multiplatform project. I thought I'd share my solution in case it was useful to someone else.
First, we define an EventProcessor
interface. The concrete implementation will be responsible for debouncing our clicks.
internal interface EventProcessor {
fun processEvent(event: () -> Unit)
companion object {
val buttonClickMap = mutableMapOf<String, EventProcessor>()
}
}
The implementation:
private const val DEBOUNCE_TIME_MILLIS = 1000L
private class EventProcessorImpl : EventProcessor {
private val now: Long
// this is being used in Compose multiplatform
// switch out with whatever millisecond provider you want
get() = Clock.System.now().toEpochMilliseconds()
private var lastEventTimeMs: Long = 0
override fun processEvent(event: () -> Unit) {
if (now - lastEventTimeMs >= DEBOUNCE_TIME_MILLIS) {
event.invoke()
}
lastEventTimeMs = now
}
}
The Composable
function where our EventProcessor
will be used and a helper function for getting this button's EventProcessor
internal fun EventProcessor.Companion.get(id: String): EventProcessor {
return buttonClickMap.getOrPut(
id
) {
EventProcessorImpl()
}
}
@Composable
fun debouncedClick(
id: String = randomUUID(),
onClick: () -> Unit,
): () -> Unit {
val multipleEventsCutter = remember { EventProcessor.get(id) }
val newOnClick: () -> Unit = {
multipleEventsCutter.processEvent { onClick() }
}
return newOnClick
}
Here is an example of it being used:
PrimaryButton(
onClick = debouncedClick {
// handle click
},
) {
Text("Button")
}
Full code:
https://gist.github.com/j-roskopf/990baa5beef767fbb2fae8cce33e2529