Code improvements with Kotlin

Code improvements with Kotlin

Nicolas Frankel

This week, I tried to improve my pet Android application developed in Kotlin. As I was very new to Kotlin when I started it, most of the code just looks like Java written in Kotlin.

Starting simple

Here’s one such snippet, that needs to initialize both a template message and its argument:

val messageTemplate: String
val params: Array <any>when (shownCount) {
    0 -> {
        messageTemplate = noItem
        params = arrayOf<any>()
    }
    1 -> {
        messageTemplate = itemShowSingle
        params = arrayOf<any>(totalCount)
    }
    else -> {
        messageTemplate = itemShowCount
        params = arrayOf(shownCount, totalCount)
    }
}

Pair

Kotlin versions after M3 don’t offer Tuple anymore, but specialized versions like Pair and Triple in the stdlib. Also, the to() extension function can create such pairs on the fly, without resorting to the constructor.

Destructuring declarations

Kotlin allows to initialize multiple variables at once when the function returns a Pair, a Triple or any data class for that matter.

This is the new improved code:

val (messageTemplate, params) = when (shownCount) {
    0 -> noItem to arrayOf()
    1 -> itemShowSingle to arrayOf(totalCount)
    else -> itemShowCount to arrayOf(shownCount, totalCount)
}

stdlib also known as Kotlin’s toolbelt

A common task in Android is to send data to the SQLite database through ContentValues instances. Naive Java ports look like the following snippet:

val taskValues = ContentValues()
taskValues.put(T_NAME_COL, task.name)
taskValues.put(T_DESC_COL, task.description)
taskValues.put(T_PRIO_COL, task.priority)
taskValues.put(T_DONE_COL, if (task.done) 1 else 0)
taskValues.put(T_LIST_COL, list.id)
if (task.imagePath != null) {
    taskValues.put(T_IMG_COL, task.imagePath.toString())
}

Kotlin’s stdlib provides a number of interesting functions.

apply()

apply() is an extension function set on Any type. It accepts a null-returning function as a parameter, applies it to the receiver and return the later. Note that in the scope of the lambda, this is the receiver.

let()

let() is another extension function set on Any type. It accepts a transforming function as a parameter and calls it with the receiver as the parameter.

Null-safe call operator

The .? operator will only called the right-hand operand if the left-hand operand is not null. It’s Kotlin’s idiomatic way for null checking

Using them in combination gives the following code:

val taskValues = ContentValues().apply {
    put(T_NAME_COL, task.name)
    put(T_DESC_COL, task.description)
    put(T_PRIO_COL, task.priority)
    put(T_DONE_COL, if (task.done) 1 else 0)
    put(T_LIST_COL, list.id)
    task.imagePath?.let { put(T_IMG_COL, it.toString()) }
}

Going further

Another common Android task it to read data stored in UI components:

val id = nameView.tag as Long?
if (id != null) {
    task.id = id
}

Safe cast

Casts are handled in Kotlin with the as operator. However, nullable and non-nullable types don’t belong to the same hierarchy. Hence, if one casts null to a non-nullable type, a ClassCastException will be thrown at runtime. To avoid that, us the as? smart cast operator.

Using let() and the .? operator in conjunction with smart cast produces the next improvement:

(nameView.tag as? Long)?.let { task.id = it }

The whole shebang

The final common snippet is related to querying SQLite databases. Basically, the usual flow is to create the object, create the cursor, iterate over it to read values and set object’s attributes from them. It looks like this:

fun findById(id: Long): Task {
    val cursor = readableDatabase.rawQuery("SELECT A LOT FROM TABLE", arrayOf(id.toString()))
    cursor.moveToFirst()
    val name = cursor.getString(1)
    val description = cursor.getString(2)
    val imagePath = cursor.getString(3)
    val task = Task(name, description)
    if (!cursor.isNull(4)) {
        val date = cursor.getLong(4)
        task.alarm = Date(date)
    }
    cursor.close()
    task.id = id
    if (imagePath != null) {
        task.imagePath = Uri.parse(imagePath)
    return task
}

Local functions

Methods in Java are about visibility and scoping. Basically, if one wants a method not reused in other classes, one sets the private visibility. If this method is used only in another method, it just pollutes the class namespace. Embedded functions are a way to declare a function inside another one to avoid this pollution. Kotlin (as well as Scala) allows that.

with()

Another useful function from stdlib is with(). Available on any type, it takes 2 parameters: the first is the receiver, the second a transforming function and calls the later on the former.

Combining those with some of the above features can improve the code a lot:

fun findById(id: Long): Task {

    fun toTask(cursor: Cursor): Task {
        with(cursor) {
            moveToFirst()
            val name = getString(1)
            val description = getString(2)
            val imagePath = getString(3)
            return Task(name, description).apply {
                if (!isNull(4)) {
                    val date = getLong(4)
                    val alarm = Date(date)
                    this.alarm = alarm
                }
                this.id = id
                imagePath?.let { this.imagePath = Uri.parse(it) }
                close()
            }
        }
     }

    return readableDatabase.rawQuery(
            "SELECTA LOT FROM TABLE",
            arrayOf(id.toString())).let { toTask(it) }
}

Conclusion

Learning a new programming language is easy: many books promise to do that in 21 days. The hard part is how to write idiomatic code in that new language. This is a long and arduous journey, that needs a lot of reading such idiomatic code, writing “bad” code yourself and improving it over the course of many iterations.

To go further: