votopec
votopec

Reputation: 73

Best way to stop and repeat coroutine, kotlin

I have an android app that draws time consuming graphs when slider moves. If slider moves fast I want to stop current drawing of graph and restart it. Once slider stops I finish the last job. I am using coroutine for drawing it. What is the best way to stop a coroutine and then start it over?

Upvotes: 1

Views: 2028

Answers (1)

broot
broot

Reputation: 28452

There is no builtin restart operation for coroutines, but you can just cancel() already running one and invoke launch() (or any other coroutine builder) again to start it from the beginning.

But... for your specific case I suggest reading about reactive programming and especially about: RxJava or Kotlin flows. They were invented specifically to solve these kinds of problems.

See this example using flows:

suspend fun main() {
    // simulate slider position changes
    val sliderFlow = flow {
        emit(1)
        delay(50)
        emit(2)
        delay(50)
        emit(3)
        delay(50)
        emit(4)
        delay(800)
        emit(5)
        delay(1500)
        emit(6)
    }
    sliderFlow.collectLatest { drawGraph(it) }
}

suspend fun drawGraph(input: Int) {
    println("started drawing $input...")
    try {
        delay(1000) // simulate drawing
    } catch (e: CancellationException) {
        println("cancelled drawing $input")
        throw e
    }
    println("ended drawing $input")
}

sliderFlow is a stream of slider positions (created for testing purposes). For each new position drawGraph() is invoked which takes 1000ms to finish. As we are only interested in the latest position, we use collectLatest() which automatically cancels the processing of a value if a new one arrives. It produces this output:

started drawing 1...
cancelled drawing 1
started drawing 2...
cancelled drawing 2
started drawing 3...
cancelled drawing 3
started drawing 4...
cancelled drawing 4
started drawing 5...
ended drawing 5
started drawing 6...
ended drawing 6

As we can see, only 5 and 6 has completed, the rest was cancelled, because new slider value was emitted before drawGraph() was able to finish.

Additionally, if the slider can move really fast, then we probably don't want to start drawing for each new value, because then we would start drawing only to cancel just milliseconds later. In that case we can use debounce() which will wait for the value to "stabilize" for specified amount of time:

sliderFlow
    .debounce(100)
    .collectLatest { drawGraph(it) }

Output:

started drawing 4...
cancelled drawing 4
started drawing 5...
ended drawing 5
started drawing 6...
ended drawing 6

As we can see, we didn't even start drawing 1, 2 and 3, because they were replaced with new values too quickly.

Upvotes: 3

Related Questions