Better Android Development with Kotlin & Gradle

Download Slides

Ty will walk you through using Kotlin with Gradle and Android to streamline your workflow for app development, both within the build tool phase and within the application itself. After a brief overview of Kotlin language, he’ll dive into how it can be used with Gradle to accelerate Android Development with a consistent language through the entire stack.


Introduction (0:00)

Context switching greatly reduces efficiency and it can cause a lot of frustration for developers. In Android development, we use a mix of languages in the app source and the build tool source. This includes Java, Groovy, XML, C++, and even Javascript.

I’m Ty, and I’m an Android developer at Uber, working on our external developer platform. I’ll do an overview on the Kotlin language and illustrate how it can help make development more consistent throughout the entire stack.

What is Wrong With Java? (1:29)

Why isn’t Java good enough?

Java in Android can be quite error prone. Capturing inner classes, for example, leaves apps prone to memory leaks, especially when passing those on to asychronize operations. There are also several syntactical problems with Java beside lambdas.

Collection streaming and other systematic problems, such as the verboseness, nullability, and mutability are problems that can be solved with libraries that help improve the app development experience and safely back port some of those features from Java eight and seven. But these systematic problems cause a bit of an issue.

Groovy is what’s primarily used in building Gradle. It’s an expressive and useful language, but it has limitations that make it less than ideal. Because it’s a dynamic language, you’re going to spend more time processing the language because because of runtime checking. Using Kotlin, which is statically typed, in your Gradle builds will speed up the configuration step.

Groovy also brings a number of performance and memory concerns when used on Android. The standard run time for Groovy is quite large, and the dynamic nature means that the garbage collector and Android would be running quite frequently.

What is Kotlin? (4:30)

Kotlin is built by JetBrains, the company also behind IntelliJ and most of Android Studio. It’s built in the open, with Android developers as the target audience.

Since I first started giving this talk, the Gradle team has announced official support for Kotlin. The byte code that’s generated by Kotlin is 100% compatible with the JVM6. Because Kotlin is fully interoperable with Java, it can be used immediately - intermixed with Java.

Get more development news like this

Kotlin helps with many of the constraints that Java and Android both introduced. By utilizing Kotlin’s type system for null safety, it can help reduce easy to avoid null pointer error exceptions and make our code much more maintainable. Morever, Kotlin is expressive and concise enough to compete with many aspects of Groovy, the preferred build script language of Gradle while providing much needed type safety, to reduce errors and expedite the development of tooling.

Kotlin features (8:11)

Kotlin has a number of great features, such as higher order functions, properties, and mix-ins.

One of the most common exceptions in Java development is the null pointer exception. Having the value of null requires defensive programming, which leads to much less maintainable code as a result.

Here’s the first example to show Kotlin’s variable assignments:


var a: String = "foo" //Reversed Type and name
a = null //Compilation error

Kotlin uses a syntax that reverses the type and name ordering for variable declaration. In this case, there will be a compile time exception on the second line - because you cannot assign null to var a.

However, this notice of the question mark along with the declaration:


var b: String? = "bar"
b = null //ok

val l = b.length //Compilation error

This indicates to Kotlin that the value may be null and the programmer will hold the responsibility of doing the check. Once we try to utilize the variable, a compilation error will appear, as it’s necessary to explicitly check for null before using it.

Here are a few different formats to check for null.


val l = b?.length //Value null

//With a default value
val l = if (b != null) b.length else -1 //Value -1

//or with the Elvis operator
val l = b?.length ?: -1 //Value -1

The first line of code, you’ll note the question mark in use. When that is declared, any further method calls on that object will return null if the parent object is null. This removes the need for us to do the nested if-null checks Java. In the second line, instead of utilizing null, specify a default value using a single expression if-else. And in the last example, an Elvis operator is used simplify the syntax of the previous examples.

Class properties


Properties
class User(var name: String) {
...
}

fun setName(name: String): User {
	val user = User() // there's no 'new' keyword in Kotlin
	user.name = "Ty" //This is using a method, not a field
	return user
}

Getters and setters are automatically generated - the above is a synthetic property. We can, moreover, override the default generated getters and setters to specify custom logic by declaring a get and a set method below the property definition.


class User {
	var name: String = ...
		get() = name // Can declare methods inline
		set(value) {
			name = value().toLowerCase()
		}
}

POJOs (Plain Old Java Object)

In addition to using properties to clean up boilerplate code, Kotlin offers a concept called the data class.


data class User(name: String) {
}

This is designed to be a lightweight POJO. By adding the data keyword in the declaration, it’ll automatically generate an equals and a hash code method, in addition to creating the two-string method with all of the constructor parameters defined.

Lastly, it’ll generate the copy method with default parameters so it’s not necessary to implement builders to copy and create immutable POJOs throughout the code base.

Data classes do have many limits, for example, they cannot extend another class or be abstract.

Function literals

Function literals, or lambdas, are a great way to make a code more readable, and this is standard with Kotlin.


val sum: (Int, Int) -> Int = {x, y -> x+y }
val anAnswer = sum(6,7) //Variable type is inferred

//Or use "it" to get the inferred parameter

val double: (Int) -> Int = {it*2}
val theAnswer = double(21)

Expanding on the function literal concept, Kotlin also provides higher order functions or functions that take functions as parameters or return them.


fun apply(one: Int, two: Int, func: (Int, Int) -> Int) = func(one, two)
//Kotlin has great generic support to simplify this too!

val multiply = apply(6, 7) {x, y -> x * y}
val difference = apply(44, 2) {x, y -> x -y}

Kotlin gives users the ability to add methods to existing types.


fun Int.differenceFromTheAnswer(): Int {
	return 42 - this
}

//Or the Single Expression Function version
fun Int.differenceFromTheAnswer(): = 42 - this

//Usage
val difference = 2.differenceFromTheAnswer() //40

This is very powerful and similar to other languages like C#.

Kotlin only gives the ability to access to the method defined in a class. If you wanted to use it in other classes, you would need to explicitly import it from the class where you had defined it.

Kotlin meets Gradle (17:42)

Suppose you decided to use Kotlin in your app along with a mix of Groovy and Java in the build tools. There are now up to three languages to consider. An alternative is to use Kotlin in the build tool as well.


buildscript {
	ext.kotlin_version = '1.0.1'

	repositories { mavenCentral() }
	dependencies {
		classpath "org.jetbrains.kotlin:kotlin-Gradle-plugin:$kotlin_version"
	}
}

apply plugin: 'kotlin'
repositories { mavenCentral() }
dependencies {
	compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

By adding a build time dependency, applying the plug-in then including the standard library and run time., we can intermix the .KT, Java, and Groovy files.


buildscript {
	repositories {
		jcenter()
		GradleScriptKotlin()
	}
	dependencies { classpath("com.android.tools.build:Gradle:2.2.0") }
}

apply<AppPlugin>()

android {
	buildToolsVersion("23.0.3")
	compileSdkVersion(23)
	defaultConfigExtension {
		setMinSdkVersion(15)
		setTargetSdkVersion(23)
		applicationId = "com.example.kotlinGradle"
		versionCode = 1
		versionName = "1.0"
	}
}

Build a Gradle plugin (27:20)

Let’s make this more consistent for all your apps through building a Gradle plug-in. A Gradle plug-in has three basic ingredients.

The first is the task. It’s typically used to represent an action triggered from the command line or another task. A very simple way to create a custom task is by extending the default task class.


class CreatePropertiesFileTask extends DefaultTask {
	@TaskAction
	void createFile() {
		Properties properties = new Properties()
		keys?.each {
			properties.put(it, getProp(it))
		}
		outputFile?.withOutputStream {
			properties.store(it, "")
		}
	}
}

Functions annotated with the @TaskAction will be called when the task is executed. The createFile function contains the logic into the custom property file with some generalization to allow for reusability.

This is what it looks like in Java:


try {
	Properties properties = new Properties();
	if (keys != null) {
		for (String key : keys) {
			properties.put(key, getProp(key));
		}
	}
	if (keyFile != null) {
		FileOutputStream fileOutputStream = new FileOutputStream(keyFile);
		properties.store(fileOutputStream, "");
	}
} catch (FileNotFoundException fe) {
	throw new GradleScriptException("Error", fe);
} catch (IOException ie) {
	throw new GradleScriptException("Error", ie);
}

For dramatic effect, this is what it looks like with ceremonial and boilerplate code removed.


//

	Properties properties = new Properties();
	//
		for (String key : keys) {
			properties.put(key, getProp(key));
		}
	//
	//
		FileOutputStream fileOutputStream = new FileOutputStream(keyFile);
		properties.store(fileOutputStream, "");
	//
//
	//
//
	//

To continue along with the task, another important part of it is the defined inputs and outputs.


class CreatePropertiesFileTask extends DefaultTask {
	@Input List<String> keys
	@OutputFile File outputFile

	...

}

Input is the Gradle annotation that marks the field as an input task to be completed. An outputFile is another Gradle annotation that’s declared in an output of the task for this file.

To make it easier for our plug-in consumers to configure the task, we’ll provide an extension.


class PropertyHolderExtension {

	List<String> keys = []
	File customPropertiesFile
	Project project

	PropertyHolderExtension(Project project) {
		this.project = project
	}

An extension is a simple class that allows the plug-in consumer to provide the certain data required to run the plug-in.


class KeyHolderExtension {
	...
	void keyFile(String filename) {
		keyFile = new File(filename)
	}

	def getProp(String key) {
		if (System.properties.get(key) != null) {
			return System.properties.get(key)
		} else if (project.hasProperty(key)) {
	...

Lastly, the plug-in itself that will help us bind and integrate these components into the build life cycle.


class PropertyHolderPlugin implements Plugin<Project> {

	@Override
	void apply(Project project) {
		def propertyHolder = project.extensions.create(
			"propertyHolder", PropertyHolderExtension, project)

First, add the extension into the project object by using the extensions property. This allows for access to the provided data by the plug-in consumer and allows them to use its syntax in their build.

The plug-in class interacts a lot with the project object and the Gradle provides a configuration function to allow for a more concise syntax by inferring that project object will be used in the following closure.


//Example 1. Without configure
def propertyHolder = project.extensions.create(...)

//Example 2. With configure
project.configure(project) {
	def propertyHolder = extensions.create(...)

Another important part of the plug-ins is to make the task available to the project.


project.configure(project) {
	...
	afterEvaluate {
		if (!propertyHolder.keys.isEmpty()) {
			project.tasks.create("createCustomPropertiesFile",
									CreatePropertiesFileTask) {
				it.keyFile = propertyHolder.customPropertiesFile
				it.keys = propertyHolder.keys
			}
		}
}
...

The afterEvaluate closure means that if you want the block of code to be called after the initialization phase of Gradle has been completed. Suppose you want to add the task if the keys have actually been assigned. Do it in the afterEvaluate closure to allow the plug-in consumer setting it in their build.Gradle.

Here is how one would use that plug-in in your build.Gradle script.


apply 'custom-properties'

propertyHolder{
	keys = 'CONSUMER_KEY',...
	keyFile 'custom.properties'
}

What it would look like in Kotlin.


open class CreatePropertiesFileTask() : DefaultTask() {
	@Input var keys: List<String> = ArrayList()

	@OutputFile var keyFile: File? = null

There are two fields: keys that are a list and a File that is nullable.

Kotlin needs to be able to interact with that API and know that a nullable is coming in. The open declares that this class is not final. By default, all classes in Kotlin are final and that’ll be an issue when it comes to building this in Gradle. Mark it as open such that Gradle can inherit from it and add its own logic.

Here’s that version of createFile() when translated line by line, similar to Groovy.


@TaskAction
fun createFile() {
	val properties = Properties()
	for (key in keys) {
		properties.put(key, getProp(key))
	}

	val fileOutputStream = FileOutputStream(keyFile)
	
	properties.store(fileOutputStream, "")
}

Kotlin does not have checked exceptions, so any uncaught exception will just be elevated to the caller.

Below is a more idiomatic version with a forEach and a function literal.


@TaskAction
fun createFile() {
	val properties = Properties()

	keys.forEach { key -> properties.put(key, getProp(key)) }

	properties.store(keyFile!!.outputStream(), "")
}

Here we’re using the double bang operator. This is another way to deal with nullable objects coming from Java; it uses the data class from Kotlin


data class PropertyHolderExtension(val project: Project,
									var keys: List<String> = ArrayList(),
									var customFile: File? = null) {
}

The plug-in class.


class PropertyHolderPlugin : Plugin<Project> {
	override fun apply(project: Project) {
		project.extensions
			.add("kotlinHolder",PropertyHolderExtension(project))
	}
}

Similar to before, we want to add the extension to the project. Use afterEvaluate to allow the extension to be set in the build.Gradle.


class PropertyHolderPlugin : Plugin<Project> {
	override fun apply(project: Project) {
		project.extensions.add("kotlinHolder", PropertyHolderExtension(project))

		project.afterEvaluate { project ->
			val extension = project.extensions.getByName("kotlinHolder")
							as PropertyHolderExtension
		}
	}
}

Here is the final version of our plug-in in Kotlin.


project.configure {
	extensions {
		add("kotlinHolder", PropertyHolderExtension(project))
	}
	afterEvaluate { project ->
		if (!propertyHolder().keys.isEmpty()) {
			tasks.create("createPropertiesFile",
					CreatePropertiesFileTask::class.java) { task ->
				task.keys = propertyHolder().keys
				task.keyFile = propertyHolder().customFile!!
			}
		}
	}
}

Conclusion (36:14)

The official documents is a great place to start for learning more about Kotlin, and the Kotlin Koans are a series of exercises to learn and play with Kotlin in the browser.

Because Google may take time to sanction Kotlin for Android, it’s up to the community to drive the best practices forward.

Next Up: Kotlin in Depth #7: Kotlin: Ready for Production

General link arrow white
`

Ty Smith

Ty has been working on Android since 2009. He is a tech lead at Uber, focusing on the external developer platform. He is a member of the Google Developer Expert program and regularly speaks at international conferences on Android. He organizes the SF Android Meetup group and Droidcon SF. He is a member of the technical advisory and investment group, Specialized Types. Prior to Uber, Ty worked on the Fabric tools at Twitter, the Evernote Android App and SDK, a messaging platform for Sprint, and Zagat for Android.

Transcribed by Hilary Fosdal
Edited by Will Ha