Skip to content

Instantly share code, notes, and snippets.

@xxhdpi
Forked from gmk57/CoroutineTimer.kt
Created April 11, 2021 11:16
Show Gist options
  • Select an option

  • Save xxhdpi/928c34debd6535cd1692ff83c079e1d9 to your computer and use it in GitHub Desktop.

Select an option

Save xxhdpi/928c34debd6535cd1692ff83c079e1d9 to your computer and use it in GitHub Desktop.
Coroutine-based solution for delayed and periodic work
/**
* Coroutine-based solution for delayed and periodic work. May fire once (if [interval] omitted)
* or periodically ([startDelay] defaults to [interval] in this case), replacing both
* `Observable.timer()` & `Observable.interval()` from RxJava.
*
* In contrast to RxJava, intervals are calculated since previous run completion; this is more
* convenient for potentially long work (prevents overlapping) and does not suffer from queueing
* multiple invocations in Doze mode on Android.
*
* Dispatcher is inherited from scope, may be overridden via [context] parameter.
*
* Inspired by [https://github.com/Kotlin/kotlinx.coroutines/issues/1186#issue-443483801]
*/
@ExperimentalTime
inline fun CoroutineScope.timer(
interval: Duration = Duration.ZERO,
startDelay: Duration = interval,
context: CoroutineContext = EmptyCoroutineContext,
crossinline block: suspend () -> Unit
): Job = launch(context) {
delay(startDelay)
do {
block()
delay(interval)
} while (interval > Duration.ZERO)
}
/** Variant of [timer] with intervals (re)calculated since the beginning (like in RxJava), for cases
* where accumulating time shift due to [delay] non-exactness & time spent in [block] is undesirable */
@ExperimentalTime
inline fun CoroutineScope.timerExact(
interval: Duration = Duration.ZERO,
startDelay: Duration = interval,
context: CoroutineContext = EmptyCoroutineContext,
crossinline block: suspend () -> Unit
): Job = launch(context) {
val startTime = TimeSource.Monotonic.markNow()
var count: Long = 0
delay(startDelay)
do {
block()
// Long to Double conversion is generally lossy, but values up to 2^53 (285 million years
// for 1-second intervals) will be represented exactly, see https://stackoverflow.com/a/1848762
val nextTime = startTime + startDelay + interval * (++count).toDouble()
delay(nextTime.remaining())
} while (interval > Duration.ZERO)
}
/** Returns the amount of time remaining until this mark (opposite of [TimeMark.elapsedNow]) */
@ExperimentalTime
fun TimeMark.remaining(): Duration = -elapsedNow()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment