Programming Android with Kotlin
Recently I finished a small Android project for one of my clients. This article gives an introduction on how to build Kotlin Android applications with Gradle and how to use Kotlin programming languages features in an Android application.
Kotlin - A Statically Compiled Language
Kotlin [0] is a statically typed, compiled JVM-based programming language by JetBrains that merges features found in Scala, Groovy, C# and others, being a modern object-oriented and functional programming language with a much more concise syntax than Java. In practice, coding in Kotlin feels a lot like programming in Scala.
As of time writing, Kotlin comes with a small runtime and stdlib file size (as of SNAPSHOT-0.1): the runtime is about 180KB and the stlib has 378KB, which is a good thing for Android APKs as it allows to write modern and functional-influenced JVM code without getting big APK file sizes.
We'll have a look at some of the language features which are particularly interesting for programming classes found in Android applications. But before, let us start with the build-setup.
The Android Kotlin Gradle Plugin
Just recently the Android project team announced to leave Ant in favor of the Gradle build tool [1]. Android's Gradle model is a bit different to the ordinary Gradle model [2]. In order to enable Kotlin support to the Android build, we have to include the kotlin-android Gradle plugin in our build.gradle script:
apply plugin: 'android'
apply plugin: 'kotlin-android'
We have to be a bit careful to choose the correct plugin version in the buildscript section as the plugin enables a certain version of the programming language. Indifferences can lead to weird compilation errors:
buildscript {
...
repositories {
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
dependencies {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.6.786'
classpath 'com.android.tools.build:gradle:0.6.+'
}
}
In order to include the standard libraries, we have to add another dependency to our application:
compile 'org.jetbrains.kotlin:kotlin-stdlib:0.6.602'
The last step is to add another Gradle source set to the build. A source set is simply a directory for source files. We will use src/main/kotlin for our Kotlin files to let Android Studio know about the additional source files:
main.java.srcDirs += 'src/main/kotlin'
The final build script looks something like that:
buildscript {
repositories {
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
dependencies {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.6.786'
classpath 'com.android.tools.build:gradle:0.6.+'
}
}
apply plugin: 'android'
apply plugin: 'kotlin-android'
repositories {
mavenCentral()
}
android {
compileSdkVersion 19
buildToolsVersion "17.0.0"
defaultConfig {
minSdkVersion 14
targetSdkVersion 19
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}
dependencies {
compile 'org.jetbrains.kotlin:kotlin-stdlib:0.6.602'
compile 'com.android.support:support-v4:19.0.0'
// other deps ...
}
You can also have a look at the example project from JetBrans, the Android Java-Kotlin project at Github [3].
Now that we are ready with the build configuration we can start to hack a Kotlin Android application!
Hacking Android Kotlin Applications
Let us start our journey by introducing a base class for all activities in our application. The class is called BaseActivity and inherits from FragmentActivity.
abstract class BaseActivity : FragmentActivity() {
}
The BaseActivity is abstract by default. As you might notice, it inherits the default constructor from FragmentActivity. Let us add an abstract property that needs to be implemented by all descendant classes: contentViewId. The contentViewId will refer to the actual content view layout XML file that will be used for a concrete activity implementation:
abstract class BaseActivity : FragmentActivity() {
abstract val contentViewId : Int
}
We added a new abstract property called contentViewId of the basic Kotlin type jet.Int. Kotlin comes with a bunch of basic types [4] that are close to their Java equivalents. Be aware that they are not exactly the same, there are differences in the type widening rules and literals as can be found in the documentation. The property is declared using the val keyword. This means it is a read-only property that needs to be initialised once. In our case we would like to leave the initialization code up to the concrete activity classes, so we have to mark the property as abstract.
If we would leave that base class like this it would be rather pointless. So we override a method from the FragementActivity base class: onCreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
requestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(contentViewId)
}
The code above contains a lot of Kotlin specifics. First of all, the override keyword is used to denote that we actually override a method from one of our base classes. If you start to write Kotlin programs you will experience that Kotlin is very strict about inheriting from classes or overriding stuff. For example, Kotlin classes need to be explicitly marked to be open for inheritance. The same goes for properties and so on. When you think about it from the library author perspective this makes a lot of sense as it allows to predefine exact extension points for third parties in your library code.
Another thing you might have noticed is Bundle?. Here the Bundle parameter is declared as a nullable type, it has a postfix question mark attached to the type name. Null-Safety [5] isn't a new programming language feature, but Kotlin is actually on of the first JVM-based languages that is really determined to send null to the programming language graveyard. In Kotlin all types are null-safe per default. A variable needs explicitly be declared as nullable using the nullable type syntax shown above. Declaring a type as nullable has also consequences in its usages. For example, if we wanted to call some method on the savedInstanceState, a null-check would be enforced by the Kotlin compiler:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val lastIndex = savedInstanceState.get("lastIndex") // compile-time error
val lastIndex = savedInstanceState?.get("lastIndex") ?: -1 // works
val lastIndex = savedInstanceState!!.get("lastIndex") // works but throws NPE if savedInstanceState is null
val lastIndex = if (savedInstanceState != null) savedInstanceState.get("lastIndex") else -1 // works
}
The code above shows various ways how the Kotlin compiler check can be satisfied. Check out the various operators and patterns that can be used and you might know from other languages: ?. type safe navigation, ?: elvis, !!force method call operator.
Another question that might be still left is why we didn't declare the savedInstanceState parameter as null-safe type? As null-safety is not enforced by Java we would run into inconsistencies if we would handle third-party Java code like Kotlin null-safe code. Therefore the rule is: whenever Java third party code is called, the types a nullable by default. Therefore, to override the third-party method from FragmentActivity we need to declare the Bundle as nullable.
This rule might seem a bit strict at first time, but it actually forces the programmer to think about all the possible program states that can in theory occur. Let's assume we wanted to call the findViewById method in one of the descendant classes, we would have to deal with a nullable View return type:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(contentViewId)
val view = findViewById(R.id.someView) // would return View?
}
We can use an unsafe cast to force the type to be a null-safe type, but be aware that the nullable type isn't a lie. It could well be that you are referring to an ID that is not present in the current layout:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(contentViewId)
val view = findViewById(R.id.someView) as View // if the return value is null, an exception is thrown!
}
A better implementation would be to actually handle the null value that might be returned from findViewById. We will add this method to our BaseActivity:
fun view(id : Int) : View {
val view : View? = findViewById(id)
if (view == null)
throw IllegalArgumentException("Given ID could not be found in current layout!")
return view
}
The code above is a good example on how the compiler analyses the program flow in order to find out about null-checks. As we have a null-check for our nullable View the compiler knows that the resulting type must be a null-safe View.
The view method also shows some syntactic sugar that comes with Kotlin. The new operator isn't needed to create instances of classes as you can see for the IllegalArgumentException. And Kotlin comes with type inference as you can see on the view declaration. In the previous example, the variable was declared to be of type View?, in the other example no explicit type was declared, thus the type has been inferred by the return type of the method used in the initialisation expression.
The view method above can be adapted to use a generic type parameter [6] with the upper-bound type View. But be aware that we risk the chance of getting a ClassCastException for the beauty of getting the correct type:
fun view(id : Int) : T {
val view : View? = findViewById(id)
if (view == null)
throw IllegalArgumentException("Given ID could not be found in current layout!")
return view as T
}
Don't be afraid of using generics in Kotlin. They have been integrated on a much more pragmatical level than in Java with the possibility of declaring generic parameters and whether they are used to "produce" values or "consume" values, omitting the wildcard concept found in Java.
Let's move on to another very nice Kotlin feature that makes a lot of sense especially for classes forced to fit within a certain framework bound: traits. A class can extend from multiple traits, so traits can be used as a restricted form of multiple inheritance. In my Android application I used traits quite frequently. One use-case was to offer a trait that encapsulates all the logic for implementing a view pager for teaser images that was found in various places of the application:
trait ViewPagerFlavour : FragmentActivity {
val mAdapter : ImageFragmentAdapter
val teaserLoaded : () -> Unit
fun showTeaserImages(imageUrls : List) {
if (imageUrls.isEmpty()) {
return
}
imageUrls.forEach {
if (it.isNotEmpty() && "noimage" != it) {
val imageFragement = ImageFragement.newInstance(this, it)
imageFragement!!.teaserImageLoaderEvents = object : TeaserImageLoaderEvents {
override fun errorOccurred() {
runOnUiThread({ teaserLoaded() })
}
override fun imageLoaded() {
runOnUiThread({ teaserLoaded() })
}
}
mAdapter.addImageFragement(imageFragement)
}
}
val viewPager : ViewPager = view(R.id.pager)
viewPager.setAdapter(mAdapter)
val pageIndicator : PageIndicator = view(R.id.indicator)
pageIndicator.setViewPager(viewPager)
// if we don't have any results, we don't need to show the viewer pager
// and page indicator
if (mAdapter.getCount() == 0) {
teaserLoaded()
val viewPagerContainer = view(R.id.viewPagerContainer)
if (viewPagerContainer != null) viewPagerContainer.setVisibility(View.GONE)
val webViewContainer = view(R.id.webViewContainer)
if (webViewContainer != null) {
val layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
webViewContainer.setLayoutParams(layoutParams)
webViewContainer.invalidate()
}
}
if (mAdapter.getCount() == 1) {
pageIndicator.setVisibility(View.GONE)
}
mAdapter.notifyDataSetChanged()
pageIndicator.notifyDataSetChanged()
}
}
As you can see the trait extends from FragementActivity. This means that our trait can only be used in classes that have FragementActivity in their inheritance path. Another property of Kotlin traits is that they are stateless. The properties must not have initialisation code, instead this code needs to be defined by the trait implementors. In the case above it was decided that the ImageFragmentAdapter and a teaserLoaded callback function needs to be provided. Actually, the teaserLoaded property shows how to declare function types as Kotlin is a functional language too. In this case, the teaserLoaded property must be initialised with a function object taking no parameters and returning nothing, which is the Unit type.
The Kotlin standard library [7] defines various extension functions for collections and other basic types. For example it adds the forEach method that is used above to iterate over the image urls given to showTeaserImages. Kotlin extension functions [8] are not restricted to the standard library. They can be used in every Kotlin program to enrich third predefined library classes. Take a look at the following three extension functions I came to use quite frequently in my Android code:
package com.someapp
fun View.show() {
if (!this.isShown())
this.setVisibility(View.VISIBLE)
}
fun View.hide() {
if (this.isShown())
this.setVisibility(View.GONE)
}
fun TextView.string() : String {
return getText()?.toString() ?: ""
}
It's important to note that the syntax of an extension function forces us to prefix the function name with the type that is the extension target of that function. Once the functions are defined they can be imported with
import com.someapp.*
and are available on all direct or indirect View types.
The trait above also shows how to create anonymous classes or so-called object expressions in Kotlin:
imageFragement!!.teaserImageLoaderEvents = object : TeaserImageLoaderEvents {
override fun errorOccurred() {
runOnUiThread({ teaserLoaded() })
}
override fun imageLoaded() {
runOnUiThread({ teaserLoaded() })
}
}
Object expressions can also be used to implement singletons which I used in the Android application to implement various objects which would have otherwise ended in utility classes. For example, the Network singleton checks the network availability:
object Network {
fun isOnline(ctx : Context) : Boolean {
val cm = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.getActiveNetworkInfo()
return netInfo != null && netInfo.isConnectedOrConnecting()
}
}
or Screen can be used to have access to the display width/height or dp to pixel computation functions:
object Screen {
fun width(ctx : Context) : Int {
return displayMetrics(ctx).widthPixels
}
fun height(ctx : Context) : Int {
return displayMetrics(ctx).heightPixels
}
fun dpToPx(ctx : Context, dp : Int) : Int {
val resources = ctx.getResources()
val displayMetrics = resources!!.getDisplayMetrics()
val scale = displayMetrics!!.density
return ((dp * scale) + 0.5).toInt()
}
fun displayMetrics(ctx : Context) : DisplayMetrics{
val windowManager = ctx.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val metrics = DisplayMetrics()
val display = windowManager.getDefaultDisplay()
if (display == null)
throw IllegalArgumentException("Default display must not be null!")
display.getMetrics(metrics)
return metrics
}
}
Before we end our journey to the Android Kotlin land, I'd like to show another cool feature that has been introduced to the Kotlin programming language: delegated properties.
In a nutshell, the feature can be used to delegate a set or get call to another object. Kotlin provides the Delegates class that already comes with some quite useful stuff. For example, it holds a lazy function that can be used to lazily initialise a property.
In Android applications this could be used to defer the actual view lookup to the point of time where the view is actually needed. Have a look at the following code that is found in a concrete activity class to lookup three TextView widgets:
val txtLocationName : TextView by Delegates.lazy { view(R.id.locationName) }
val txtLocationStreet : TextView by Delegates.lazy { view(R.id.locationStreet) }
val txtLocationCity : TextView by Delegates.lazy { view(R.id.locationCity) }
Whenever one of the views is called at runtime, the first call will execute the function block given to the Delegates singleton. Subsequent calls will retrieve the cached value instead of calling the actual lookup for another time. Actually, what happens behind the scenes is nothing magical or complicated. The Delegates singleton creates an object of type LazyVal that implements a set and a get method.
private class LazyVal(private val initializer: () -> T) : ReadOnlyProperty {
private var value: Any? = null
public override fun get(thisRef: Any?, desc: PropertyMetadata): T {
if (value == null) {
value = escape(initializer())
}
return unescape(value) as T
}
}
Kotlin's by keyword gives the runtime a hint that instead of the actual receiver the method delegation needs to redirect to the delegation object, in this case, the LazyVal. Delegation isn't something that is restricted to properties only in Kotlin. The delegation pattern can also be used on a per-class basis:
trait Base {
fun print()
}
class BaseImpl(val x : Int) : Base {
override fun print() { print(x) }
}
class Derived(b : Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print() // prints 10
}
We will end our journey at this place. I had quite a lot of fun to implement an Android app in Kotlin. However, be aware that Kotlin is a rather young language and the support of other languages in Android is nothing that can be bet on in the future.
Conclusion
Kotlin is a statically typed JVM programming language developed by JetBrains. Kotlin has a very slick runtime and standard library that - with the help of the Gradle build tool - can be used to build Android applications. This article shows some of the Kotlin programming language features that make developing Android applications fun.