A Journey Migrating an Android App to Kotlin

Gilang Kusuma Jati
14 min readJun 11, 2018

--

Kotlin is the future of Android Development. Source: https://cdn-images-1.medium.com/max/1600/1*99YiKjwB2TliKVA-yGogNQ.png

Leading Android team at the fastest-growing SaaS startup in Indonesia, Moka, pushes me to use all my skills and potentials to handle a lot of responsibilities. As a leading cloud-based POS (point-of-sales) company, Android is the Moka’s core platform. In order to constantly deliver a great experience to our customers, I have to stay up to date with Android development. Staying up to date on the newest technologies, tips and trends help me to ensure that our product is sustainable and have the best quality to serve our valued customers to grow their business to the next level.

Hi. My name is Gilang Kusuma Jati. I am a Lead Software Engineer who works for Moka and this is my first article on medium.

Android development has been growing rapidly especially since Google announced Kotlin as official supported language and give the first class support for building Android apps. A lot of articles, books, and online courses about Kotlin constantly showing up. A lot of libraries and apps were also converted into this language. Countless developers also echo the same excitement and happiness when using Kotlin. I am sure that this is a clear sign that Kotlin is already mature for Android development and there is no more reason for not taking a serious attention to this new programming language.

About Kotlin

Kotlin is a statically typed programming language that runs on the Java virtual machine and also can be compiled to JavaScript source code or use the LLVM. Kotlin is now an official language on Android. It’s expressive, concise, and powerful. Best of all, it’s interoperable with existing Android languages and runtime. — Wikipedia, Android Developer

The idea of Kotlin was conceived in 2010 at JetBrains, creator of amazing IntelliJ, ReSharper and Android Studio. Kotlin is named after an island near St. Petersburg, Russia, where most of the Kotlin development team is located. Kotlin has been around since 2011, open sourced in 2012, reached version 1.0 in 2016 and finally used as official supported language for building Android apps in 2017. Kotlin compiles the code to Java bytecode, JavaScript to enable full-stack web development and soon be able to compile directly to native code and run without any VM at all.

After reading several basic syntax at Kotlin official page, I decided to buy books from Kotlin learning resources to help me building strong foundations about the language and its powerful features. Kotlin in Action and Kotlin for Android Developers are the books that I chose to start learning Kotlin. The more I read, the more I want to use Kotlin in my projects. Not enough with books, I take an online course Kotlin Bootcamp for Programmers.

After completing all books and online course, it’s time to taste the real power and magic in Kotlin. Of course, I will not use Moka Android App as a pilot project to use Kotlin since the apps used by 19K active devices (85% of total users). A tiny mistake in the code may lead to customer’s dissatisfaction. Therefore, to know how Kotlin could improve Android apps developement, I use my side project, Jakarta City Line.

About Jakarta City Line

Jakarta City Line is an application that provides information about commuter train schedule, realtime position and its trip in Jabodetabek, Indonesia.

Jakarta City Line

The first version of Jakarta City Line released on August, 2016. Currently, reaching 13K downloads with rating 4.7 out of 5 which made Jakarta City Line as the best apps among the similar apps.

Without using handy converter to migrate my existing Java code to Kotlin. I rewrite the whole code, yes rewrite the whole code from zero (starting from creating new project wizard) to get full experience of building Android apps with Kotlin.

Here are some amazing things I found when migrating Jakarta City Line from Java to Kotlin.

#1 Java Interoperability

One of Kotlin’s cornerstones is its 100% interoperability with Java. Java and Kotlin can mix and call each other. So that, there is no additional effort when importing existing Android library (that written in Java) to be used in Kotlin. The snippet below shows importing an existing Android library that written in Java.

implementation ‘com.afollestad.material-dialogs:core:0.9.6.0’

and use the libary in Kotlin code

val builder = MaterialDialog.Builder(getContext())
.typeface(FONT_500, FONT_500)
.title(title)
.content(message)
.positiveText(R.string.ok)
val dialog = builder.build()
dialog.titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
dialog.show()

#2 Null Safety

Like many programming languages, Java will produce NullPointerException (NPE) when accessing a member of a null reference. Android developers are very familiar with the Unfortunately, Process Has Stopped dialog. Another version of the message is Unfortunately, the Jakarta City Line has stopped. This dialog is shown when our application throws an unhandled exception. In my journey working with Java while developing and maintaining Android apps, 99% of unhandled exception is caused byNullPointerException.

I call it my billion-dollar mistake. It was the invention of the null reference in 1965 — Sir Tony Hoare (Computer Scientist)

The nullable objects introduces a fundamental problem with type system. E.g when we declare an object as String, it doesn’t guarantee that the value is real String, it might refer to null. When we skip null checks, the code crashes with NullPointerException.

To remove NullPointerException, Kotlin has approach to convert these problems from runtime errors into compile-time errors by distinguishing between non-null types and nullable types and forbids operations that can lead to a NullPointerException. By default all objects are non-nullable, so we can’t store nulls in them. But if a variable can hold null values, we only have to explicitly mark a type with question mark character ? at the end to indicate it’s nullable. By distinguishing between non-null types and nullable types, the compiler can detect many possible errors during compilation and reduce the possibility of having exceptions thrown at runtime.

val s: String? = null   // May be null
val s2: String = "" // May not be null
var a: String = "abc"
a = null // compile error
var b: String? = "xyz"
b = null // no problem

Once we have a value of a nullable type, the set of operations we can perform on it is restricted. For example, we can no longer call method length on b because b is String? (nullable String).

val strLength = b.length        // compile error: b might be null// compile error: s might be null
fun strLenSafe(s: String?): Int = s.length

When we perform the comparison of a variable with null, the compiler will remember that the variable is non-nullable and treats the value as being non-nullable in the scope where the check has been performed (smart casts which casts nullable types to non-nullable).

if (b == null) return
val strLength = b.length // no problem
// By adding the check for null, the code now compiles.
fun strLenSafe(s: String?): Int = if (s != null) s.length else 0

Kotlin has a safe-call operator: ?., which allows us to combine a null check and a method call into a single operation. In the code snippet below, the safe-call operator will evaluates the statement to null instead of throwing a NullPointerException.

// type of strLength is Int? (nullable Int)
val strLength = b?.length

To avoid nested if-not-null checks, safe-call operator can be chained together and there is an Elvis operator ?: to provide default values instead of null.

val stationNameLength = station?.name?.length ?: 0

#3 Type Inference

In Java, we start a variable declaration with data type, e.g. String url = “www.gilangkusumajati.com";. In Kotlin, data type is optional.

val url = "www.gilangkusumajati.com"   // type inferred to String
val names = ArrayList<String>() // type inferred to ArrayList
val interest : Double = 0.25 // type declared explicitly
val age: Int = 27 // type declared explicitly

#4 Smart Cast

In Java, if we have checked a variable has a certain type and need to access members/methods of that type, we need to add an explicit cast after the instanceofcheck. But in Kotlin, we don’t need to cast it afterward; we can use the variable as having the type we checked for.

In Java

if (obj instanceof String) {
print(((String) o).toLowerCase());
}

While in Kotlin

if (obj is String) {
print(obj.toLowerCase()) // obj is now known to be a String
}

#5 Extension Functions and Extension Properties

Extension function is a function that can be called as a member of a class but defined outside. By using extension functions and properties, we can extend the API of any class, including classes defined in external libraries, without modifying its source code and with no runtime overhead.

Here is an extension properties for class Context called database to access an instance of DatabaseHelper (singleton).

// Access property for Context
val Context.database: DatabaseHelper
get() = DatabaseHelper.getInstance(this.applicationContext)
class DatabaseHelper(ctx: Context) : ManagedSQLiteOpenHelper(ctx, DATABASE_NAME, null, DATABASE_VERSION) {
companion object {
private var instance: DatabaseHelper? = null
private const val DATABASE_NAME = "mydb.db"
private const val DATABASE_VERSION = 3
@Synchronized
fun getInstance(ctx: Context): DatabaseHelper {
if (instance == null) {
instance = DatabaseHelper(ctx.applicationContext)
}
return instance!!
}
}
...
}

So, whenever we have a context, an instance from class Context or its subclasses (Application, Activity, etc.), we can call property database

context.database.use {
...
}

Here is an extension function for class Collection and applied to its subclasses to check whether the collection is not empty. These collection functions provided by Kotlin library, Collections.kt

public inline fun <T> Collection<T>.isNotEmpty(): Boolean = !isEmpty()

thus we can use

val arrayList = ArrayList<String>()
if (arrayList.isNotEmpty()) {
// do something
}
val treeSet = TreeSet<Train>()
if (treeSet.isNotEmpty()) {
// do something
}
val linkedHashMap = LinkedHashMap<Item>()
if (linkedHashMap.isNotEmpty()) {
// do something
}

Here is another sample of extension functions called capitalize added to class String to returns a copy of string having its first letter uppercased. This extension function also provided by Kotlin library StringsJVM.kt.

public fun String.capitalize(): String {
return if (isNotEmpty() && this[0].isLowerCase())
substring(0, 1).toUpperCase() + substring(1)
else this
}

Whenever we have String, we can use

println("moka".capitalize())

We can declare extension functions and extension properties anywhere, but following a common practice is to create files that hold a set of related functions and properties. Take a note that extension functions and properties do not modify the original class and do not allow us to break encapsulation. Unlike methods defined in the class, extension functions and properties do not have any access to private or protected members of the class. The code won’t compile, if we call private of protected attributes or methods in existing class within our extension functions or properties.

#6 String Templates

Like the most scripting languages, Kotlin provides us a string templates that allows us to refer to local variables in string literals by putting the $character in front of variable name.

This string templates is equivalent to Java’s string concatenation(“Hi, “ + name + “!”)or String.format().

String name = "Moka";                        
println("Hi, " + name + "!"); // print Hi, Moka!
println(String.format("Hi %s!", name)); // print Hi, Moka!

While in Kotlin, interpolating String is more compact, readable and efficient.

val name = "Moka"                            
println("Hi, $name!") // print Hi, Moka!
println("Length: ${name.length}") // print Length: 4
val x = 3
val y = 5
print("sum of $x and $y is ${x + y}") // print sum of 3 and 5 is 8

Because Kotlin is statically type programming language, the string templates expressions are statically checked, and the code won’t compile if we try to refer to a variable that doesn’t exist.

#7 Native Support

Surprisingly, Kotlin has a native support which makes us to be able to hide a private key/string from APK decompilation. In Java, to make code to be able call a function that is implemented in native (C or C++) code, we need to create a function and mark it with the native modifier. This ability is adopted in Kotlin by using external modifier.

Here is the sample of Native code

#include <jni.h>
#include <string>
extern "C" {
JNIEXPORT
JNICALL
jstring Java_com_jakartacityline_android_Native_accessToken(
JNIEnv *env,
jobject instance,
jobject ctx) {
validateAccess(env, instance, ctx);
std::string accessToken = "access_token";
return env->NewStringUTF(accessToken.c_str());
}
}

Then declare external function in Kotlin

package com.jakartacityline.androidimport android.content.Contextobject Native {// Used to load the 'native-lib' library on application startup.
init {
System.loadLibrary("native-lib")
}
external fun accessToken(context: Context): String
}
// calling native code
println(Native.accessToken(context)) // print access_token

#8 Default Arguments

In Kotlin, we can put default value of arguments when declaring a function to avoid write overloaded function/constructor:

fun showError(message: String, title: String = "Error",
positiveText: String = "OK") {

val builder = MaterialDialog.Builder(getContext())
.typeface(FONT_500, FONT_500)
.title(title)
.content(message)
.positiveText(positiveText)
showDialog(builder)
}
showError("Connection reset!")
showError("Connection timeout!", "Warning", "Close")

If the params title and positiveText do not provided, Kotlin will use Error and OK respectively as default value. So we don’t have to define similar method with same name and different parameters anymore.

#9 Named Arguments

When calling a function written in Kotlin, we can specify the names of some arguments that we’re passing to the function. Together with default arguments, this eliminates the need for builders.

showError(message = "Failed", title = "Info", positiveText = "OK")  
showError(title = "Error", message = "Email failed to sent")

#10 Properties

With properties, now we can stop behaviour bloating our code with mindless getters & setters like we did in Java.

class BankAccount {
var balance: Double = 9000.0
var monthlyFee: Double = 1000.0
private set //can’t change this property outside the class.
val availableBalance: Double
get() = balance - monthlyFee
}
class AdvancedSharedPreferences (val context: Context) {

private val prefs =
PreferenceManager.getDefaultSharedPreferences(context)
companion object {
private const val VERSION = "pref_version"
}
var version: Int
get() = prefs.getInt(VERSION, 1)
set(value) = prefs.edit().putInt(VERSION, value).apply()
}

#11 Data Class

By declaring a class as a data class, it will instructs the Kotlin compiler to generate methodtoString(), equals(), hashCode(), and copy() for the class.

data class Station(private val id: Int, 
private val name: String)
val station = Station(10, "Depok")
val anotherStation = station.copy()
val manggaraiStation = station.copy(name = "Manggarai")

By using data class, now we have a class that overrides all the standard Java methods:

  1. equals() to compare two instances
  2. hashCode() for using them as keys in hash-based containers such as HashMap and LinkedHashMap
  3. toString() to generate string representations showing all the fields in declaration order
  4. copy() to allows us to copy the instances of our classes, and changing the values of some properties.

#12 Equality Operators

In Kotlin, the == operator is translated into a call of the equals method. Using the != operator is also translated into a call of equals, with the obvious difference that the result is inverted. We also can use the identity equals operator (===) to check whether the parameter to equals is the same object as the one on which equals is called.

class Station(private val id: Int, 
private val name: String) {
override fun equals(obj: Any?): Boolean {
if (obj === this) return true
if (obj !is Station) return false
return obj.id == id && obj.name == name
}
}
println(Station(10, "Depok") == Station(10, "Depok")) // true
println(Station(10, "Depok") === Station(10, "Depok")) // false
println(Station(10, "Depok") != Station(5, "Bogor")) // true
println(null == Station(7, "Bekasi")) // false

Even when using data class where the implementation of equals is automatically generated by the compiler, it will give us the same behaviour.

data class Station(private val id: Int, 
private val name: String)
println(Station(10, "Depok") == Station(10, "Depok")) // true
println(Station(10, "Depok") === Station(10, "Depok")) // false
println(Station(10, "Depok") != Station(5, "Bogor")) // true
println(null == Station(7, "Bekasi")) // false

From now, we can stop calling equals() and compareTo() explicitly like we did in Java.

#13 Operator Overloading

Like C++, Kotlin allows us to overload some of the standard mathematical operations by defining functions with the corresponding names to improve readability:

data class Station(val id: Int, val name: String) {
operator fun plus(other: Station) =
Station(id + other.id, "$name-${other.name}")
}
val station = Station(10, "Depok") + Station(5, "Bogor")

#14 Destructuring Declarations

Destructuring declarations let us initialize multiple variables by unpacking a single object. This is very useful for iterating the key and value pair inside amap.

fun printEntries(map: Map<String, String>) {
for ((key, value) in map) {
println("$key -> $value")
}
}
val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
printEntries(map)
// print:
// Oracle -> Java
// JetBrains -> Kotlin

#15 Ranges

Kotlin support ranges to improve code readability.

for (i in 1..9) { print(i) }             // print 123456789 
for (i in 0 until 5) { print(i) } // print 01234
for (i in 2..10 step 2) { print(i) } // print 246810
for (i in 9 downTo 1) { print(i) } // print 987654321
if (x in 1..10) { // true if i is in range 1 to 10}

Ranges are not restricted only to integer, but also to characters and any class that supports comparing instances (class that implements interface java.lang.Comparable).

fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know…"
}
println(recognize('7')) // print It's a digit!
println("Kotlin" in "Java".."Scala") // print trueprintln("Kotlin" in setOf("Java", "Scala")) // print false

#16 When Expression

Kotlin has when expression to replace Java switch-case to makes code much more readable and flexible. The when expression works by matching its argument against all branches in order until some branch condition is satisfied.

override fun getItemViewType(position: Int): Int {
return when (data[position]) {
is TrainSummary -> headerViewType
is Train -> trainViewType
is TrainFooterSummary -> footerViewType
is Ads -> adsViewType
else -> throw IllegalArgumentException()
}
}

It works both as an expression or a statement, and with or without an argument:

val res: Boolean = when {
obj == null -> false
obj is String -> true
else -> throw IllegalArgumentException()
}

It also works with enum

fun getDestination(color: Color) =
when (color) {
Color.RED -> "Jakarta"
Color.ORANGE -> "Bogor"
Color.YELLOW -> "Depok"
Color.GREEN -> "Jatinegara"
Color.BLUE -> "Manggarai"
Color.INDIGO -> "Duri"
Color.VIOLET -> "Serpong"
}
println(getDestination(Color.BLUE)) // print Manggarai

Additionally, beside converting from Java to Kotlin, here are the list of improvements while doing migration of Jakarta City Line.

  1. Use ConstraintLayout to improve rendering performance
  2. Makes SharedPreferencess accessible globally without context
  3. Use native code to hide private key/string from decompilation
  4. Use Anko-Sqlite to have an auto closed cursor while querying to the database.

The details of Jakarta City Line’s improvements will be available in the next article. So stay tune in my Medium.

Jakarta City Line has 100% crash-free users.

Regularly, I prefer Java for all my projects. But after completing migration of my side project (Jakarta City Line), I found my self happy while coding in Kotlin and I believe that Kotlin would be a new future for Android development. According to my experience, here are the reason why you should also move from Java to Kotlin:

  1. Kotlin is an expressive programming language with a strong type system, featuring type inference (the ability of the compiler to determine types from its context so we don’t need to declare types or variable explicitly). It has lambdas, co-routines, properties, and many other features that let us write less code with fewer bugs.
  2. Kotlin makes our life as developers more enjoyable and safer by distinguishes nullable and non-nullable data types. By declaring a variable as nullable and non-nullable, it eliminates many errors at compile time and safe us from NullPointerException, one of runtime exception that well known as a root cause of an application crashes, huge financial loses, and uncountable hours of debugging madness. My Crashlytics confirm that Jakarta City Line now has 100% crash-free users.
  3. Kotlin is very concise and with less boilerplate code (a code that we have to type and doesn’t actually affect the way our app works). In Java, we often write trivial setter getter in classes (especially in data model class). In Kotlin, we don’t need to write setter getter anymore, the compiler does it for us without errors. More concise code takes less time to write and less time to read. It also improves our productivity and lets us get things done faster.
  4. Kotlin is designed for readability. Experienced Java programmer can read Kotlin code easily.
  5. Kotlin is compiled into Java byte code so we can use Java and Kotlin side by side. Kotlin code can call Java code and vice versa, which means we can continue to use our favourite Java libraries in our projects. It also means that we can use start using Kotlin in an existing project that is written in Java without having to migrate everything. If we choose to migrate an existing Android project written in Java, Android studio provide a handy converter.

For all who has not moved to Koltin, do migrate your existing project or create a new project by using Kotlin. I hope you will get the same excitement as I got. Thank you for reading.

--

--