Debouncing Clicks in Compose

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
About the author
- Hey! I’m Joe! I’m an Android developer who is passionate about the human side of software development.
Comments
Join the discussionNo comments yet. Be the first to comment.




