Converting a TODO app to Kotlin (part 3 - the Model Layer)

In part 2 I wrote about converting the Task class from Java to Kotlin. The number of constructors went from four down to two thanks to default parameters. The boring assignments in the constructors are gone thanks to properties. I also mentioned data classes and using if as an expression.

Before I start with the main topic for this article I just want to mention I am not the only one curious about a Kotlin version of this project. A few days after I started with my fork this issue was opened on Github. The author of the issue completed the conversion and you can check out the code here. The reason I am continuing with my fork are these articles I am writing. I also plan to send a few pull requests to the fork of Shyish with a few suggestions I have like this one.

TasksDataSource

For this interface the automatic conversion was perfect.

TasksRemoteDataSource

The automatic conversion for this class results in build errors in the getInstance method. This happens because INSTANCE is mutable and the compiler can’t guarantee that the value doesn’t change between the null check and the return.

1
2
3
4
5
6
7
8
9
companion object {
    val instance: TasksRemoteDataSource
        get() {
            if (INSTANCE == null) {
                INSTANCE = TasksRemoteDataSource()
            }
            return INSTANCE
        }
    }

Kotlin does have a nice feature to solve this: Lazy initialization. The lazy function delays the execution of the lambda until the first time get() is called. Subsequent calls to get() simply return the remembered result.

1
2
3
    companion object {
        val INSTANCE: TasksRemoteDataSource by lazy { TasksRemoteDataSource() }
    }

The TasksRemoteDataSource class uses the Singleton pattern. Kotlin does not have the static keyword. In Kotlin you can declare a singleton using object declaration.

Another noteworthy change here is the use of let. It executes the code only if task is not null. It also does a smart cast from Task? to Task (from nullable to non-nullable type). This can also be replaced with a simple if.

1
2
3
4
5
6
7
8
        val task = TASKS_SERVICE_DATA[taskId]

        // Simulate network by delaying the execution.
        val handler = Handler()
        task?.let {
            handler.postDelayed({ callback.onTaskLoaded(task) }, 
            	SERVICE_LATENCY_IN_MILLIS.toLong())
        }

The benefits of making Task a data class show here. When activating or completing a task we make a new copy with a change in a single field isCompleted. Instead of using a constructor we can use the copy function.

1
2
3
4
5
//using a constructor
val completedTask = Task(task.title, task.description, task.id, true)

//using the copy function
val completedTask = task.copy(isCompleted = true)

TasksPersistenceContract

The TasksPersistenceContract class becomes an object in Kotlin. The TaskEntry static class becomes a class with a companion object holding the constants. The TaskEntry java class implements BaseColumns to use the _ID static field. In Kotlin this does not happen, TaskEntry doesn’t get the _ID field. My solution is not to implement BaseColumns and manually include an _ID field in the companion object of TaskEntry.

1
2
3
4
5
6
7
8
9
10
11
    /* Inner class that defines the table contents */
    abstract class TaskEntry {
        companion object {
            val _ID = BaseColumns._ID
            val TABLE_NAME = "task"
            val COLUMN_NAME_ENTRY_ID = "entryid"
            val COLUMN_NAME_TITLE = "title"
            val COLUMN_NAME_DESCRIPTION = "description"
            val COLUMN_NAME_COMPLETED = "completed"
        }
    }

The benefit in this approach is that the client code will remain the same, you can still use TaskEntry._ID in the client code (e.g. TasksDbHelper).

TasksDbHelper

Another straight forward automatic conversion. The static constants go into a companion object, there is no need for a constructor and null checks.

TasksLocalDataSource

This is very similar to TasksRemoteDataSource with a few differences. First the constructor here actually does some work instead of just assigning arguments. Kotlin offers initializer blocks to achieve this.

1
2
3
    init {
        mDbHelper = TasksDbHelper(context)
    }

The rest of the code is pretty much the same with null checks replaced with the ? operator and no null checks for parameters.

TasksRepository

Another singleton class which translates to a companion object in Kotlin. Some noteworthy changes here are mCachedTasks and mChacheIsDirty become public properties (no visibility modifier) to be accessible from the test written in Java and the use of copy for activating and completing tasks.

mCachedTasks is converted to a non-nullable variable and initialized as empty map. In getTasks the isNotEmpty function is used instead of a null check. I am not sure this is the correct behavior but all the unit tests pass.

Final thoughts

I am not sure how I feel about the lack of a static modifier. On one hand it’s a little bit weird when defining a singleton or a private static final LOG_TAG in your classes. On the other hand I started using dependency injection (Dagger) for my singletons and Timber for logging so I don’t mind. There is also a Kotlin library for dependency injection called KODEIN.

The code for this article can be found here.

comments powered by Disqus