Look Counter app graphics

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.

interface CalendarContract {
    interface Presenter : BaseMvpPresenter<CalendarContract.View> {
        fun populateList(context: Context?, date: Calendar, screenLookCategory: ScreenLookCategory)

    interface View : BaseView {
        fun setViewTitle(titleMonth: Calendar)

        fun setWeekLabels(weekLabels: List<String>)

        fun setDatesToCalendar(dates: List<Pair<Calendar, Int?>>)
I wanted to move setContentView() from Activity or Fragment’s  onCreate(), so added it to the BaseView, like this:
interface BaseView {
    fun getContentResource(): Int

    fun init(view: View, @Nullable state: Bundle?)
And in the fragment it looks like this:
class FragmentAbout : BaseFragment(), AboutContract.View {

    private lateinit var presenter: AboutPresenter

    override fun getContentResource() = R.layout.fragment_about

    override fun init(view: View, state: Bundle?) {
        presenter = AboutPresenter()

        view.tv_about_text.text = (getString(R.string.about_text)).formatHtmlCompat()
        view.tv_about_looks.text = (getString(R.string.about_looks)).formatHtmlCompat()
        view.tv_about_unlocks.text = (getString(R.string.about_unlocks)).formatHtmlCompat()
        view.tv_about_me.text = (getString(R.string.about_me)).formatHtmlCompat()
        view.tv_about_source_code.movementMethod = LinkMovementMethod.getInstance()
        view.tv_about_source_code.text = (getString(R.string.about_source_code)).formatHtmlCompat()
        view.b_contact_me.setOnClickListener {
            context?.let {
                if (presenter.isAttached()) {

    override fun onDestroyView() {
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:

@Entity(tableName = "DAY_LOOK")
data class DayLookEntity(
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = "_id")
        var id: Long? = null,

        @ColumnInfo(name = "DATE")
        var date: String,

        @ColumnInfo(name = "SCREENON")
        var screenon: Int? = null,

        @ColumnInfo(name = "SCREENOFF")
        var screenoff: Int? = null,

        @ColumnInfo(name = "SCREENUNLOCK")
        var screenunlock: Int? = null

) {
    constructor() : this(
            id = null,
            date = "",
            screenon = null,
            screenoff = null,
            screenunlock = null
Only date field is non-nullable and ids are generated automatically.

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.

interface DayLookDbDao {

    @Query("SELECT * FROM DAY_LOOK WHERE DATE = :dateToSearch LIMIT 1")
    fun getDayLookByDate(dateToSearch: String?): Maybe<DayLookEntity>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(dayLook: DayLookEntity)

    @Query("SELECT * FROM DAY_LOOK")
    fun getAllDayLooks(): Flowable<List<DayLookEntity>>

    fun deleteAll()
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.
@Database(entities = [DayLookEntity::class], version = 2)
abstract class ScreenCounterDb : RoomDatabase() {

    private var dbClearFlag = false

    abstract fun dayLookDao(): DayLookDbDao

    companion object {

        private var INSTANCE: ScreenCounterDb? = null
        private const val DB_NAME = "lookcount-db"

        private val FROM_1_TO_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                // tables haven't changed, we're just moving from
                // greenDAO impl to Room

        fun getDatabase(context: Context): ScreenCounterDb =
                INSTANCE ?: synchronized(this) {
                    INSTANCE ?: buildDatabase(context).also { INSTANCE = it }

        private fun buildDatabase(context: Context) =
                        ScreenCounterDb::class.java, DB_NAME)

    fun getDayLook(date: String): Maybe<DayLookEntity> {
        return if (dbClearFlag) {
            Maybe.just(DayLookEntity(null, date, null, null, null))
        } else {

    fun addDayLook(dayLook: DayLookEntity): Disposable {
        Log.d(C.TAG, "Insert/update daylook into the database.")

        return Completable.fromAction {
                        onComplete = {
                            Log.d(C.TAG, "addDayLook() insert() completed.")
                        }, onError = {
                            Log.e(C.TAG, "addDayLook() error: ${it.printStackTrace()}")

    fun dropDb() {
        this.dbClearFlag = false

        Completable.fromAction {
                        onError = {
                            Log.e(C.TAG, "dropDb() error: ${it.printStackTrace()}")

    fun setDbClearFlag(dbClearFlag: Boolean) {
        this.dbClearFlag = dbClearFlag
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.

LookCounter screenshots

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.

 * Service which starts a receiver for catching SCREEN_ON, SCREEN_OFF, and USER_PRESENT events.
 * @author Antonina
class LookCounterService : Service() {

    private val SERVICE_ID = 1
    private lateinit var screenLookReceiver: ScreenLookReceiver

    override fun onBind(intent: Intent?): IBinder? {
        return null

    override fun onCreate() {

    override fun onDestroy() {

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        return super.onStartCommand(intent, flags, startId)

    private fun registerScreenLookReceiver() {
        val filter = IntentFilter(Intent.ACTION_SCREEN_ON)
        with(filter) {

        screenLookReceiver = ScreenLookReceiver()
        registerReceiver(screenLookReceiver, filter)

    private fun startForegroundServiceWithNotification() {
        val intent = Intent(this, ActivityMain::class.java)
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)

        val builder: NotificationCompat.Builder
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channelName = getString(R.string.notification_channel_screen_looks)
            val importance = NotificationManager.IMPORTANCE_LOW
            val channelId = "LOOK_COUNTS_CHANNEL_ID"
            val channel = NotificationChannel(channelId, channelName, importance)

            builder = NotificationCompat.Builder(this, channelId)

            val notificatioManager = getSystemService(NotificationManager::class.java)
        } else {
            builder = NotificationCompat.Builder(this)

        val icon = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) R.mipmap.ic_notification else R.mipmap.ic_launcher

        with(builder) {
            priority = NotificationCompat.PRIORITY_DEFAULT

        val notification = builder.build()

        startForeground(SERVICE_ID, notification)
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.

Source code:

Like and share:

Published by

Tonia Tkachuk

I'm an Android Developer, writing code for living and for fun. Love beautiful apps with a clean code inside. Enjoy travelling and reading.

148 thoughts on “Look Counter app is now in Kotlin and stores data in Room DB”

  1. Pingback: buy levitra
  2. Pingback: tadalafil 20mg
  3. Pingback: cialis miami
  4. Pingback: cialis miami
  5. Pingback: cialis 5mg tablet
  6. Pingback: buy cialis 36 hour
  7. Pingback: buy cialis toronto
  8. Pingback: cialis 5mg tablet
  9. Pingback: Zakhar Berkut hd
  10. Pingback: buy cialis now
  11. Pingback: in europe
  12. Pingback: stromectol 3 mg
  13. Pingback: Anonymous
  14. Pingback: Anonymous
  15. Pingback: Anonymous
  16. Pingback: cost of ivermectin
  17. Pingback: ivermectin tablets
  18. Pingback: kamagra vs cialis
  19. Pingback: order tadalafil
  20. Pingback: ivermectin cost uk
  21. Pingback: ratiopharm
  22. Pingback: tadalafil 20mg otc
  23. Pingback: us generic cialis
  24. Pingback: ivermectin 200
  25. Pingback: cheap tadalafil
  26. Pingback: peptide tadalafil
  27. Pingback: cialis medication
  28. Pingback: tadalafil walmart
  29. Pingback: stromectol merck
  30. Pingback: buy viagra online
  31. Pingback: buy cialis online
  32. Pingback: cialis coupon
  33. Pingback: what is cialis
  34. Pingback: ivermectin gold
  35. Pingback: order stromectol
  36. Pingback: buy ivermectin 6mg
  37. Pingback: stromectol merck
  38. Pingback: goodrx tadalafil
  39. Pingback: furosemide 30
  40. Pingback: stromectol lotion
  41. Pingback: ivermectin 0.08%
  42. Pingback: hydroxychloroquine
  43. Pingback: stromectol price
  44. Pingback: ivermectin 1 cream
  45. Pingback: mectin ivermectin
  46. Pingback: mazhor4sezon
  47. Pingback: stromectol msd
  48. Pingback: madridbet
  49. Pingback: meritroyalbet
  50. Pingback: meritroyalbet
  51. Pingback: eurocasino
  52. Pingback: liquid tadalafil
  53. Pingback: meritroyalbet
  54. Pingback: psy-
  55. Pingback: stromectol liquid
  56. Pingback: Gz92uNNH
  57. Pingback: do-posle-psihologa
  58. Pingback: stromectol covid
  59. Pingback: bahis siteleri
  60. Pingback: ivermectin tablet
  61. Pingback: 3irregularity
  62. Pingback: 2undertakings
  63. Pingback: meritking
  64. Pingback: psikholog
  65. Pingback: cialis walmart
  66. Pingback: confeitofilm
  67. Pingback: A片
  68. Pingback: buy ivermectin

Leave a Reply

Your email address will not be published.