How can I debounce a setOnClickListener for 1 second using Kotlin Coroutines?
Couroutines are overkill for something as trivial as debounce:
class DebounceOnClickListener(
private val interval: Long,
private val listenerBlock: (View) -> Unit
): View.OnClickListener {
private var lastClickTime = 0L
override fun onClick(v: View) {
val time = System.currentTimeMillis()
if (time - lastClickTime >= interval) {
lastClickTime = time
listenerBlock(v)
}
}
}
fun View.setOnClickListener(debounceInterval: Long, listenerBlock: (View) -> Unit) =
setOnClickListener(DebounceOnClickListener(debounceInterval, listenerBlock))
Usage:
myButton.setOnClickListener(1000L) { doSomething() }
It's better to use a simple Flag for that instead of delay as it's not a good user experience.
But if you want to use Coroutines, You can simply use Kotlin Coroutine's Flow to apply this:
First I created an Extension Function for the click event that returns a Coroutine's Flow. like this:
fun View.clicks(): Flow<Unit> = callbackFlow {
setOnClickListener {
offer(Unit)
}
awaitClose { setOnClickListener(null) }
}
Now, All you need is Calling your Function in onCreate like this:
button.clicks().debounce(1000).onEach { println("clicked") }.launchIn(GlobalScope)
Don't forget to add these lines in build.gradle file:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
Edit:
The Flow analogue of throttleFirst operator is not implemented yet in kotlin coroutines. however, can be implemented with the help of Extension Functions:
@FlowPreview
@ExperimentalCoroutinesApi
fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {
var lastEmissionTime = 0L
collect { upstream ->
val currentTime = System.currentTimeMillis()
val mayEmit = currentTime - lastEmissionTime > windowDuration
if (mayEmit)
{
lastEmissionTime = currentTime
emit(upstream)
}
}
}
The changes are as follows:
binding.button.clicks().throttleFirst(1250)
.onEach {
//delay(100)
showDialog()
}.launchIn(GlobalScope)
Also, you can use a delay() to handle this. Take it easy to change value of these parameters according to your needs.
I honestly recommend corbind
With this great library you can forget about setOnClickListener
and just handle flows like
binding.myButton.clicks()
.debounce(500)
.onEach { doSomethingImportant() }
.launchIn(viewLifecycleOwner.lifecycleScope)
It's really easy to use, and with view binding making an app becomes super simple. I hope it helps, happy coding!