Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Debounce input from an EditText and relay to a TextView with a timeout.
package me.vishna.kdebounce
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
import android.widget.TextView
import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.*
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
lateinit var inputText: EditText
lateinit var outputText: TextView
var broadcastChannel: BroadcastChannel<String>? = null
override fun onCreate(savedInstanceState: Bundle?) {
inputText = findViewById(
outputText = findViewById(
launch(CommonPool) {
broadcastChannel = inputText.textChanged.consumeDebounced(800, TimeUnit.MILLISECONDS) {
launch(UI) { outputText.text = it }
override fun onDestroy() {
public val EditText.textChanged: BroadcastChannel<String>
get() = EditTextBroadcastChannel(this)
private class EditTextBroadcastChannel(
val editText: EditText,
val broadcastChannel: BroadcastChannel<String> = ConflatedBroadcastChannel())
: BroadcastChannel<String> by broadcastChannel {
init {
launch(UI) {
val textWatcher = object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
if (!isClosedForSend) {
override fun beforeTextChanged(sequence: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(sequence: CharSequence?, start: Int, before: Int, count: Int) {}
override fun close(cause: Throwable?): Boolean {
return broadcastChannel.close(cause)
* Subscribes to this [BroadcastChannel] and performs the specified action for received elements omitting those
* elements that are published too quickly in succession. This is done by dropping elements which are
* followed up by other elements before a specified timer has expired. If the timer expires and no follow up element was received (yet)
* the last received element is passed to the specified action.
public suspend fun <E> BroadcastChannel<E>.consumeDebounced(timeout: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, action: suspend (E) -> Unit): BroadcastChannel<E> {
openSubscription().use { channel ->
var job: Job? = null
var last = System.currentTimeMillis()
for (x in channel) {
val now = System.currentTimeMillis()
val diff = now - last
if (!channel.isClosedForReceive) {
job = launch {
if (diff < timeout) {
delay(Math.max(diff, 0), unit)
last = now
return this

This comment has been minimized.

Copy link

commented Jun 3, 2019

Awesome!!! thank you very much @vishna

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.