Share & Follow

Get new posts when they come!

Thank you for subscribing.

Something went wrong.

Look Counter app is now in Kotlin and stores data in Room DB

In 2016 (which seems like a whole eternity from now :), I wrote an Android app called Look Counter for counting how many times you have switched the screen on, as well as unlocked it. Up until recently I haven’t updated it. The app used such libs as greenDAO , ButterKnife , GoogleAnalytics, and RecyclerView. After the update, only the last one remained 🙂

I’ve decided to completely re-write Look Counter, using the modern tools. The first step was to update all of the dependencies and set project’s target API to 27. It’s a good idea to set yours to 26 as a min, because of Google Play’s new requirements — “Google Play will require that new apps target at least Android 8.0 (API level 26) from August 1, 2018, and that app updates target Android 8.0 from November 1, 2018.”

As the second step I chose to refactor layouts and use ConstraintLayout in order to flatten the hierarchy. It allowed me to make both Calendar and About screens completely flat, which I think is awesome 🙂

Next I wanted to convert Java to Kotlin. I used Android Studio’s Convert Java file to Kotlin file option, and then cleaned up the code. I got rid of !! and used vals instead of vars whenever possible. Also, objects instead of classes, extension methods, etc. In general, simply converting Java to Kotlin is easy, but to make Kotlin code look good and not like some Java-adaption, requires an extra effort.

I also wanted to use some kind of a architecture, to organize logic and views separately. I just have three fragments — Main, Calendar and About in the app, but they had too much unorganized code inside of them. I’ve decided to go with an MVP — simple yet neat. Below is the example of Calendar contract class. You can immediately tell what to expect from the screen, just by looking at the list of methods. All the logic goes to the presenter, and UI rendering goes to the view.

In case of a database, I decided to give Google’s Room persistence library a try. It’s new and it works well with both RxJava and LiveData, and I wanted to use one of those as well, so the choice was obvious to me. Also, I wanted a lightweight library because of the app’s small size (~ 1.5 MB for a release version). And automatic handling of SQLiteOpenHelper was a plus.

Comparing to greenDAO, the integration was fast and simple. The only thing I had problems with was a correct database migration, without losing all the data. Because I had to re-created the same SQL code with Kotlin’s data class and Room’s annotations, as it was with Java and greenDAO. Finally, it worked and the data is persisted between app updates. This is how the data class for DAY_LOOK table looks like:

Below is the DAO interface with the list of queries that I use. All of them operate on DAY_LOOK table. I wanted inserting to work as a replace/update when the day look entry already exists, hence OnConflictStrategy.REPLACE.

getDayLookByDate() returns Maybe<DayLookEntity> because the entry may or may not exist, so an empty response is acceptable. getAllDayLooks() is the list of all entries in the table, so seemed logical to return a Flowable.

Last, but not least, is the main DB class itself. I used a companion object in order to have a static equivalent of Java’s getInstance() method. ScreenCounterDb INSTANCE is a volatile one and is used within a double-locking mechanism (just 2 lines in Kotlin!). The migration method, though necessary, is empty, because I have’t changed the data’s integrity, only want to move it to version 2 of my DB (which happens to be a different library altogether 🙂 Without it, the whole data would be wiped out.

By default Room’s queries are executed on the main thread, so you have to either add allowMainThreadQueries() to the database builder, use LiveData or RxJava/RxKotlin, otherwise an exception will be thrown. I chose the last option just for the purpose of learning RxKotlin 🙂 And it took me a while to make the app work as intended.

After all the above changes, what’s left was the counting service. I had to re-write it to become a foreground one, because really it’s the only way to make it work at all times and not get killed, when the resources are low, like a regular one is. I also had to add a few if’s, because of API differences (remember, there are now notification channels in Oreo? :). Other than that, the service just starts and stops the broadcast receiver which collects screen on/off/user present events and displays a notification in the system bar. Pressing the notification opens up the app and enables the user to check counter values, as well as stop the service manually.

Just to sum up my 2 year old Android app re-writing experience, this is what I learned:

Conclusions on re-writing the app

Using a ConstraintLayout auto-convert option on a complex layout usually does’t work as intended. You often have to fix things manually and set constraints by yourself, as they’re all messed up.

Applying MVP architecture made the code easier to understand and navigate. It decoupled logic from UI and made things more clear in general.

Converting Java project to Kotlin is time consuming if you want to do it properly (without !! and by using objects, lateinits, extension functions 🙂 It is very pleasant though — to see how beautiful (and shorter) your code became!

greenDAO to Room migration went smoothly, but required writing all DB’s code from the scratch. Although classes look differently, underneath is the same SQL as before. Worth mentioning is that all queries are executed on the main thread by default, so I wrapped them into RxKotlin types.

Changing synchronous calls to asynchronous and using a push data approach instead of a pull one was really hard for me. I knew RxKotlin on a basic level and wasn’t sure what types to use and how to achieve what I wanted. Also, how to wrap DB calls and how to chain a few calls together? Unfortunately, there aren’t many tutorials on RxKotlin yet and those with RxJava demonstrate the usage of the older lib versions. Fortunately, after lots of attempts, I have worked my way through 🙂 I can recommend this project with a good set Rx exercises.