Why Android Development Is Better With Kotlin

Introduction

Kotlin may be a relatively new language but it's gaining traction fast and unlike some other languages (e.g. Swift) it feels mature with new major versions introducing features whilst always maintaining backwards compatibility. Perhaps this is because the language was built primarily to boost JetBrains' internal teams' productivity or maybe JetBrains are just really good at what they do.

Kotlin offers a lot of improvements over Java such as more concise syntax, null safety, smart casts, operator overloading, extension methods, data classes, etc. all while maintaining seamless interoperability with Java. This rich feature set combined with incredibly simple integration into new or existing projects targeting the JVM means it's really easy to start using Kotlin in your projects.

In the following sections, we're going to focus on just a few of the features of Kotlin which make it our language of choice for Android development and how it makes our applications more robust.

Target Bytecode Version

When incorporated into an Android project, the Kotlin compiler will generate Java 6 compatible bytecode meaning we can target virtually any version of Android. This allows us to use all of the features and APIs of a modern language while still being able to maintain compatibility and support for our users with older devices.

At the time of writing it's possible to use most Java 8 language features (lambdas, method references, try-with-resources, etc.) on any version of Android but it is not possible to use Java 8 APIs such as java.util.stream or java.util.function as these require a minimum of API level 24 meaning we lose out on some functional goodness when using the default toolchain.

Extension Methods

One of our favourite features in languages like Objective-C, Swift, C#, etc. is support for extension methods. Extension methods allow us to extend the platform and third party APIs to maximise the readability of our code and abstract away boilerplate. One downside of extension methods in Kotlin is that we can only add instance methods and not class methods. The following example demonstrates how we can add a method to android.content.Context instances to make conversion from dp units to pixels much simpler anywhere we need to do this programmatically.

fun Context.dpToPixels(dp: Float): Float =
    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics)

The next example highlights how we can use generics in extension methods to restrict how our extensions can be called. Here we're creating a validate method which is available on any List of EditText objects which simply ensures that all views in the list are non empty. Note we would usually use type inference rather than specify the return type for similar functions but here it's been left in for clarity.

fun List<EditText>.validateAllFieldsAreNotEmpty(): Boolean = all { it.text.isNotEmpty() }

In the Java world, it's common practice to create classes containing static methods to partially emulate extension methods. To ease Java interoperability the Kotlin compiler does this for us when declaring extension methods meaning we can still use our extensions in Java code. Assuming the previous example was in a file named ListExtensions.kt, the Kotlin compiler creates a Java method with a signature similar to the following.

public class ListExtensionsKt {
    public static boolean validate(List<EditText> list) {
        ...
    }
}

Kotlin Android Extensions

Most Android developers will be familiar with findViewById as a way to inspect and manipulate the view hierarchy and Kotlin provides a helpful set of extensions which allows us to reference the view hierarchy with compile time safety. To enable the Kotlin Android Extensions, it's as simple as enabling the plugin in build.gradle.

apply plugin: "kotlin-android-extensions"

Now, let's assume we have a layout named hello_world_activity.xml which contains the following text view somewhere in the view hierarchy.

<TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello"
    />

From the corresponding activity we can then reference our text view and its properties like below. The important part here is the kotlinx.android.synthentic import which allows us to reference the view with ID textView from our layout hello_world_activity.xml.

import kotlinx.android.synthetic.main.hello_world_activity.textView

class HelloWorldActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        textView.text = "Hello Kotlin!"
    }
}

This may not remove much boilerplate when compared with findViewById but being able to catch invalid references to view hierarchy objects and verify type references at compile time eliminates a whole class of runtime errors from your Android applications.

Coroutines

A coroutine is basically a block within which we can suspend execution at certain points without blocking the thread where the coroutine is executing. This allows us to write code which handles the result of one or more asynchronous operations sequentially similar to async/await in C#. Suspending a coroutine is almost free with no context switching or OS involvement required and can almost be thought of as a lightweight thread. The following example shows how we can update the UI at a fixed rate delay without blocking the UI thread - compared with an AsyncTask, this is certainly much simpler. Here we pass a lambda to the coroutine builder function launch which schedules the block for execution on the UI thread. The delay function is a suspending function and the point at which the coroutine execution is suspended and the UI thread is released until the delay has passed.

fun countdown(textView: TextView) {
    launch(UI) {
        for (i in 10 downTo 1) {
            textView.text = "Countdown $i ..."
            delay(500)
        }
        textView.text = "Done!"
    }
}

The coroutines library also provides support for a variety of APIs for working with asynchronous operations/computations (reactive streams, futures, etc.). One of the supported libraries is RxJava and pairing Rx with coroutines allows for much more flexible combining of observables whilst making conditional logic on the stream of values much simpler. The example below demonstrates how we could use coroutines and Rx to return a Completable once we've fetched and cached a list of user accounts asynchronously.

fun fetchUserAccounts(): Completable {
    return rxCompletable(CommonPool) {
        val accounts = api.fetchAllAccounts().await()
        cache.addAll(accounts)
    }
}

interface Api {
    @GET("/accounts")
    fun fetchAllAccounts(): Single<List<Account>>
}

There's not a load of code here but there's quite a lot going on, a few interesting points to note:

  • The rxCompletable(CommonPool) function takes a lambda argument which is the coroutine body and returns an Rx Completable. If execution of the lambda completes without error, the onComplete event will be called on the returned completable, if any exception is thrown the onError event will be called.
  • api is an instance of a simple Retrofit interface which fetches a list of user accounts asynchronously and returns an Rx Single with the result. The coroutine library uses an extension method on Single here to provide an await() function which performs the Rx subscription and allows us to wait for completion inside the coroutine body without blocking a thread. Once the call to fetch accounts has completed, we can continue execution in a sequential manner without the need for any callbacks making our code much easier to reason about.

Kotlin Cross-Platform

While still in its infancy, the Kotlin Native project allows us to build native binaries which do not require a JVM to run. Given that iOS is one of the target platforms, this presents some exciting opportunities for cross-platform mobile development similar to Xamarin. Whilst only a preview at the minute, the KotlinConf iOS application was built using this technology so hopefully it's just a matter of time before a stable release is available.

Kotlin 1.2 also introduces support for multi platform projects which goes even further and allows creation of cross-platform projects with support for shared and platform specific modules. Right now platform support is limited to the JVM, JS and Android but once Native/iOS support is available, the feature set should be on a par with Xamarin.

Conclusion

Kotlin is very quickly becoming our language of choice for server side and Android development - the functional toolkit, seamless Java interoperability, fast build times and increased productivity make it our first choice over Java.

If you'd like to learn more, you can view all our Kotlin related blog posts here. We also run several Kotlin training courses for developer teams, some of which are certified by JetBrains.

Article By
blog author

Chris van Es

Head of Engineering