diff options
author | Allan Wang <me@allanwang.ca> | 2017-08-05 23:10:28 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-05 23:10:28 -0700 |
commit | 187d8e64dc7189f63707d154166867084662dbe3 (patch) | |
tree | 372503ac381f12a905a0608519228f9792bb1c0b /core/src | |
parent | caaa5653deda0640a475d0ccad6daeb7852502f7 (diff) | |
download | kau-187d8e64dc7189f63707d154166867084662dbe3.tar.gz kau-187d8e64dc7189f63707d154166867084662dbe3.tar.bz2 kau-187d8e64dc7189f63707d154166867084662dbe3.zip |
Create debounce and update searchview (#27)
* Prepare version
* Create debounce base
* Add debouncer and fix transition crash
* Add debounce docs
* Update links
* Update searchview docs
* Test without a ref
* Add links to core components
* Update links
* Update to bullet points
* Test core md
* Test slash
* Test slash
* Specify implemented dependencies
Diffstat (limited to 'core/src')
4 files changed, 181 insertions, 1 deletions
diff --git a/core/src/main/kotlin/ca/allanwang/kau/kotlin/Debouncer.kt b/core/src/main/kotlin/ca/allanwang/kau/kotlin/Debouncer.kt new file mode 100644 index 0000000..4fba2c8 --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/kotlin/Debouncer.kt @@ -0,0 +1,124 @@ +package ca.allanwang.kau.kotlin + +import ca.allanwang.kau.logging.KL +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +/** + * Created by Allan Wang on 2017-08-05. + * + * Thread safe function wrapper to allow for debouncing + * With reference to <a href="https://stackoverflow.com/a/20978973/4407321">Stack Overflow</a> + */ + +/** + * The debouncer base + * Implements everything except for the callback, + * as the number of variables is different between implementations + * You may still use this without extending it, but you'll have to pass a callback each time + */ +open class Debouncer(var interval: Long) { + private val sched = Executors.newScheduledThreadPool(1) + private var task: DebounceTask? = null + + /** + * Generic invocation to pass a callback to the new task + * Pass a new callback for the task + * If another task is pending, it will be invalidated + */ + operator fun invoke(callback: () -> Unit) { + synchronized(this) { + task?.invalidate() + val newTask = DebounceTask(callback) + KL.v("Debouncer task created: $newTask in $this") + sched.schedule(newTask, interval, TimeUnit.MILLISECONDS) + task = newTask + } + } + + /** + * Call to cancel all pending requests and shutdown the thread pool + * The debouncer cannot be used after this + */ + fun terminate() = sched.shutdownNow() + + /** + * Invalidate any pending tasks + */ + fun cancel() { + synchronized(this) { + if (task != null) KL.v("Debouncer cancelled for $task in $this") + task?.invalidate() + task = null + } + } + +} + +/* + * Helper extensions for functions with 0 to 3 arguments + */ + +/** + * The debounced task + * Holds a callback to execute if the time has come and it is still valid + * All methods can be viewed as synchronous as the invocation is synchronous + */ +private class DebounceTask(inline val callback: () -> Unit) : Runnable { + private var valid = true + + fun invalidate() { + valid = false + } + + override fun run() { + if (!valid) return + valid = false + KL.v("Debouncer task executed $this") + try { + callback() + } catch (e: Exception) { + KL.e(e, "DebouncerTask exception") + } + } +} + +/** + * A zero input debouncer + */ +class Debouncer0 internal constructor(interval: Long, val callback: () -> Unit) : Debouncer(interval) { + operator fun invoke() = invoke(callback) +} + +fun debounce(interval: Long, callback: () -> Unit) = Debouncer0(interval, callback) +fun (() -> Unit).debounce(interval: Long) = debounce(interval, this) + +/** + * A one argument input debouncer + */ +class Debouncer1<T> internal constructor(interval: Long, val callback: (T) -> Unit) : Debouncer(interval) { + operator fun invoke(key: T) = invoke { callback(key) } +} + +fun <T> debounce(interval: Long, callback: (T) -> Unit) = Debouncer1(interval, callback) +fun <T> ((T) -> Unit).debounce(interval: Long) = debounce(interval, this) + +/** + * A two argument input debouncer + */ +class Debouncer2<T, V> internal constructor(interval: Long, val callback: (T, V) -> Unit) : Debouncer(interval) { + operator fun invoke(arg0: T, arg1: V) = invoke { callback(arg0, arg1) } +} + +fun <T, V> debounce(interval: Long, callback: (T, V) -> Unit) = Debouncer2(interval, callback) +fun <T, V> ((T, V) -> Unit).debounce(interval: Long) = debounce(interval, this) + +/** + * A three argument input debouncer + */ +class Debouncer3<T, U, V> internal constructor(interval: Long, val callback: (T, U, V) -> Unit) : Debouncer(interval) { + operator fun invoke(arg0: T, arg1: U, arg2: V) = invoke { callback(arg0, arg1, arg2) } +} + +fun <T, U, V> debounce(interval: Long, callback: ((T, U, V) -> Unit)) = Debouncer3(interval, callback) +fun <T, U, V> ((T, U, V) -> Unit).debounce(interval: Long) = debounce(interval, this) diff --git a/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt b/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt index 2ac5d2f..ceeaa30 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/kotlin/LazyResettable.kt @@ -13,7 +13,7 @@ internal object UNINITIALIZED fun <T : Any> lazyResettable(initializer: () -> T): LazyResettable<T> = LazyResettable<T>(initializer) -open class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = null) : ILazyResettable<T>, Serializable { +class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = null) : ILazyResettable<T>, Serializable { @Volatile private var _value: Any = UNINITIALIZED private val lock = lock ?: this diff --git a/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt b/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt index 4c6d655..4cf566a 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt @@ -18,4 +18,7 @@ open class TimberLogger(tag: String) { inline fun i(s: String) = Timber.i(TAG, s) inline fun v(s: String) = Timber.v(TAG, s) inline fun eThrow(s: String) = e(Throwable(s)) +// fun plant() { +// Timber.plant(Timber.Tree()) +// } }
\ No newline at end of file diff --git a/core/src/test/kotlin/ca/allanwang/kau/kotlin/DebounceTest.kt b/core/src/test/kotlin/ca/allanwang/kau/kotlin/DebounceTest.kt new file mode 100644 index 0000000..12bc5a4 --- /dev/null +++ b/core/src/test/kotlin/ca/allanwang/kau/kotlin/DebounceTest.kt @@ -0,0 +1,53 @@ +package ca.allanwang.kau.kotlin + +import org.jetbrains.anko.doAsync +import org.junit.Test +import kotlin.test.assertEquals + +/** + * Created by Allan Wang on 2017-08-05. + */ +class DebounceTest { + + @Test + fun basic() { + var i = 0 + val debounce = debounce(20) { i++ } + assertEquals(0, i, "i should start as 0") + (1..5).forEach { debounce() } + Thread.sleep(50) + assertEquals(1, i, "Debouncing did not cancel previous requests") + } + + @Test + fun basicExtension() { + var i = 0 + val increment: () -> Unit = { i++ } + (1..5).forEach { increment() } + assertEquals(5, i, "i should be 5") + val debounce = increment.debounce(50) + (6..10).forEach { debounce() } + assertEquals(5, i, "i should not have changed") + Thread.sleep(100) + assertEquals(6, i, "i should increment to 6") + } + + @Test + fun multipleDebounces() { + var i = 0 + val debounce = debounce<Int>(10) { i += it } + debounce(1) //ignore -> i = 0 + Thread.sleep(5) + assertEquals(0, i) + debounce(2) //accept -> i = 2 + Thread.sleep(15) + assertEquals(2, i) + debounce(4) //ignore -> i = 2 + Thread.sleep(5) + assertEquals(2, i) + debounce(8) //accept -> i = 10 + Thread.sleep(15) + assertEquals(10, i) + } + +}
\ No newline at end of file |