Created
November 5, 2017 19:38
-
-
Save vishna/88650add425aaf0220775592eb09f05b to your computer and use it in GitHub Desktop.
Debounce input from an EditText and relay to a TextView with a timeout.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package me.vishna.kdebounce | |
import android.support.v7.app.AppCompatActivity | |
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.android.UI | |
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?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
inputText = findViewById(R.id.input_text) | |
outputText = findViewById(R.id.output_text) | |
launch(CommonPool) { | |
broadcastChannel = inputText.textChanged.consumeDebounced(800, TimeUnit.MILLISECONDS) { | |
launch(UI) { outputText.text = it } | |
} | |
} | |
} | |
override fun onDestroy() { | |
broadcastChannel?.close() | |
super.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) { | |
editText.addTextChangedListener(textWatcher) | |
} | |
} | |
val textWatcher = object : TextWatcher { | |
override fun afterTextChanged(editable: Editable?) { | |
if (!isClosedForSend) { | |
offer(editable.toString()) | |
} | |
} | |
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 { | |
editText.removeTextChangedListener(textWatcher) | |
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 | |
job?.cancel() | |
if (!channel.isClosedForReceive) { | |
job = launch { | |
if (diff < timeout) { | |
delay(Math.max(diff, 0), unit) | |
} | |
action(x) | |
last = now | |
} | |
} | |
} | |
} | |
return this | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ternyata mengalami hal serupa haha