Count down timer with Kotlin Coroutines Flow
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:
- 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.
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.
“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.
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
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:
- Check that we have something to count down. If not, then we can just directly call
- Create a deadline (field
mStopTimeInFuture) which will be the finish landmark for us
- 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:
- The time is taken between
sendMessagewas executed and
- The time is taken by
- The case, when
onTicktakes 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.
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
SharedFlowis not for us)
Giving all that we had before we can have a pretty straightforward solution:
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
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:
Top <Put_your_number> Kotlin utils we use all over our project
Over the years I’m faced here and there with posts and articles dedicated to useful extensions or utils. “Top 5 useful…