Count down timer with Kotlin Coroutines Flow

A simple alternative to having android independent code

Michael Spitsin
ProAndroidDev
Published in
5 min readAug 31, 2021

--

Preface

In the last project, we used coroutines as a way of doing asynchronous jobs. We started at the beginning of 2019 and back then Kotlin Flow as the whole Channels were experimental feature.

But our goal was to move onto coroutines and minimize places without it:

  • Callbacks
  • Rx streams (or pipelines)
  • Tickers, timers, delays

We wanted to move everything to the coroutines ideally.

As I said back then we didn’t want to use experimental Channels/Flows, so we migrated only the Callbacks section to coroutines, but the places with Rx we replaced with our own Rx-Lite Super Awesome library (maybe I will tell about it in the next article if you want please let me know in the comments). And our version of Rx as turned out was really similar to Flows in some ways which gave us a possibility of easy migration in the future.

We started to inject flows into our project in 2020. At this point, we already had an agreement about unit tests and about code structure and style. By our agreements, we wanted to avoid using Android in our unit tests and we wanted to minimize using Robolectric library for them, we wanted to have pure Kotlin logic tests.

The Problem

We had places that used timers. Specifically, count down timer. Those places we part of either business or presentation logic and, thus, should be tested. But most importantly, Android’s count-down timer provided the callback way of reacting to the timer’s updates. We wanted to have coroutines. In this scenario, Flow was the most attractive possibility.

Of course, we could build some wrappers. But to convert callback-approach to the flow approach you can spend some time and this conversion can be by the complexity be the same as if we will create just our own CountDownFlow. Pure kotlin (well, almost), pure coroutines, no Android timer callbacks at all.

Stop!

“What about built-in mechanisms in coroutines-flow?” you say. Indeed, if we will look into the library we will find an interesting method ticker which seems to do what we need. The only tiny silly problem is that it marked as @ObsoleteCoroutinesApi which means that the API will be adapted and changed in the future possibly (and also it uses System time instead of SystemClock which is also important, since the first can be altered by the time settings on your device).

Since this task was not so hard for us (we building the solution for only one project, not for the entire programming community), we decided to build something custom and in the future just replace it with what the Kotlin team will offer. Fair deal, I think.

Solution

First step

Lets first build the interface and the general usage structure, which will be available as API for other classes or entities:

Simple enough. Now we have a question what’s the CountDownFlow?

How Android Count Down Timer Works?

I think that this is essential here, we are visitors here and they (Android devs) are creators, so in order to respect the system and imitate everything with minimum errors and deviations, we should respectfully transfer the logic, that Android SDK already contains. So let’s first analyze it.

Every time when we start() the timer, we do the next things:

  1. Check that we have something to count down. If not, then we can just directly call onFinish and done.
  2. Create a deadline (field mStopTimeInFuture) which will be the finish landmark for us
  3. Send the message to the internal Handler. Yes, the Android count-down timer uses the Handler concept and sends messages over some period of time.

Then we a handleMessage method, which calls onTick and make some adjustments to the timer before sending a new message again. There are three things to consider here:

  1. The time is taken between sendMessage was executed and handleMessage was called
  2. The time is taken by onTick method
  3. The case, when onTick takes too long (skipping ticks)

The meat of the solution

As I said, instead of inventing our logic, let’s just transfer it to our class.

Note: in the following snippet just pay attention to tick method only. Rest methods are just util methods for the syntactic sugar.

Can you notice the difference between this ticking logic and Android’s one?

For instance, if you will have left = 15, interval = 3 and tickDuration = 7, then both ticking methods will provide delay = 2. But if suppose left = 8, then you will see the difference. You can try yourself with a pen.

In the latter example, Android’s solution will give you delay = 2and thus, you will wait not 8 milliseconds but 9 in total. Whereas my solution will give you delay = 1 and you will wait 8 milliseconds in total as it is supposed to be.

I’m not saying that this is the bug of Android’s timer. It is just what I didn’t want for our tasks when I provided my version of the timer, that’s why I made such a change. If you need to imitate the Android timer behavior, you will need to change line 9 in the provided snippet.

Overall

Now let’s look at how we can use our core part in the conjunction with our desires which are:

  • Having coroutines flow and coroutines solutions instead of callbacks
  • We don’t want to use ticker. And it is not only about obsolete API, but because it uses System.nanoTime, which is dependent on your phone settings of time. We want to use SystemClock.elapsedTime. So we could be independent.
  • We want to have completable flow (thus, any SharedFlow is not for us)

Giving all that we had before we can have a pretty straightforward solution:

Afterwords

Now we have simple completable flow, which we can run with our logic whatever we like. Moreover, this flow is a testable and pure kotlin solution (the only Android thing we use is SystemClock which is wrapped in Time interface).

If you liked that article, don’t forget to support me by clapping. If you want to support me more, just subscribe to my blog and keep an eye on new articles. Also, if you have any questions, comment me, and let’s have a discussion. Happy coding!

Also, there are other articles, that can be interesting:

--

--

Love being creative to solve some problems with an simple and elegant ways