Skip to content

Building on a Coroutine Timer #2171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
OlleEkberg opened this issue Jul 29, 2020 · 0 comments
Closed

Building on a Coroutine Timer #2171

OlleEkberg opened this issue Jul 29, 2020 · 0 comments

Comments

@OlleEkberg
Copy link

OlleEkberg commented Jul 29, 2020

After reading this: issues/1186
I decided to keep adding to it on a project I'm working on.

interface CoroutineTimerListener {
    fun onTick(timeLeft: Long?, error: Exception? = null)
    fun onStop(error: Exception? = null) {}
    fun onContinue() {}
    fun onPause(remainingTime: Long) {}
    fun onDestroy() {}
}

enum class CurrentTimerState {
    RUNNING, PAUSED, STOPPED, DESTROYED
}

enum class TimerErrorTypes(val message: String) {
    ALREADY_RUNNING("This instance of the timer is already running, create a new instance or stop your current one"),
    CURRENTLY_PAUSED("This timer is currently paused. Choose to continue or stop to start over"),
    NO_TIMER_RUNNING("You are trying to stop or pause a timer that isn't running"),
    DESTROYED("This timer is destroyed and can't be used anymore")
}

private class TimerException(val type: TimerErrorTypes): Exception(type.message)

class CoroutineTimer(private val listener: CoroutineTimerListener, dispatcher: CoroutineDispatcher = Dispatchers.Unconfined) {

    companion object {
        const val TAG = "CoroutineTimer"
    }

    private val scope: CoroutineScope by lazy { CoroutineScope(Job() + dispatcher) }
    var state: CurrentTimerState = CurrentTimerState.STOPPED
        private set(value) {
            if (field == CurrentTimerState.DESTROYED) {
                return
            }
            field = value
        }

    fun startTimer(countDownTime: Long, delayMillis: Long = 1000) {
        when (state) {
            CurrentTimerState.RUNNING -> {
                listener.onTick(null, TimerException(TimerErrorTypes.ALREADY_RUNNING))
            }
            CurrentTimerState.PAUSED -> {
                listener.onTick(null, TimerException(TimerErrorTypes.CURRENTLY_PAUSED))
            }
            CurrentTimerState.DESTROYED -> {
                listener.onTick(null, TimerException(TimerErrorTypes.DESTROYED))
            }
            else -> {
                timerCanStart(countDownTime, delayMillis)
            }
        }
    }

    fun stopTimer() {
        val error = if (state == CurrentTimerState.STOPPED) {
            TimerException(TimerErrorTypes.NO_TIMER_RUNNING)
        } else { null }
        state = CurrentTimerState.STOPPED
        listener.onStop(error)
    }

    fun pauseTimer() {
        if (state == CurrentTimerState.PAUSED) {
            Log.e(TAG, "Already paused, check your code for multiple callers")
        }
        state = CurrentTimerState.PAUSED
    }

    fun continueTimer() {
        if (state == CurrentTimerState.RUNNING) {
            Log.e(TAG, "Already running, check your code for multiple callers")
        }
        state = CurrentTimerState.RUNNING
        listener.onContinue()
    }

    fun destroyTimer() {
        scope.cancel("Timer was now destroyed. Need a new instance to work")
        listener.onDestroy()
        state = CurrentTimerState.DESTROYED
    }

    private fun timerCanStart(countDownTime: Long, delayMillis: Long = 1000) {
        scope.launch {
            state = CurrentTimerState.RUNNING
            var timeLeft = countDownTime

            timerLoop@ while (true) {
                when {
                    timeLeft < 1 -> {
                        state = CurrentTimerState.STOPPED
                        listener.onStop()
                        break@timerLoop
                    }
                    timeLeft > 0 && state == CurrentTimerState.RUNNING -> {
                        listener.onTick(timeLeft)
                        delay(delayMillis)
                        timeLeft -= 1
                    }
                    state == CurrentTimerState.PAUSED -> {
                        listener.onPause(timeLeft)
                    }
                    state == CurrentTimerState.STOPPED -> {
                        listener.onStop()
                        break@timerLoop
                    }
                }
            }
        }
    }
}

Usage:

val timer = CoroutineTimer(object: CoroutineTimerListener {
            override fun onTick(timeLeft: Long?, error: Exception?) {
                TODO("Not yet implemented")
            }

            /**
             * Optional methods
             */
            override fun onStop(error: Exception?) {}
            override fun onPause(remainingTime: Long) {}
            override fun onContinue() {}
            override fun onDestroy() {}
        })

timer.startTimer(20)

Could something like this work like it is or maybe be improved on to work?

@OlleEkberg OlleEkberg changed the title Building on a Coroutines Timer Building on a Coroutine Timer Jul 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant