Slide 1

Slide 1 text

Better Android Development with Kotlin and Gradle Ty Smith @tsmith

Slide 2

Slide 2 text

But I like Java! • We're stuck in a Java purgatory • Error prone Code (no non-capturing anonymous inner classes) • General Java language restrictions @tsmith

Slide 3

Slide 3 text

How about Groovy? • Groovy is dynamic • Harder for IDEs to parse and infer • Won't ever be consistent with Android @tsmith

Slide 4

Slide 4 text

What is Kotlin? • Built by Jetbrains and Open Source • Official Gradle support • Java 6 bytecode compatible • Small standard library (625kb) • Statically typed with no runtime overhead • Modern Language @tsmith

Slide 5

Slide 5 text

How can Kotlin help today? • Reduce common errors • Reduce boilerplate • Improve Android API • Can achieve consistency in the app and build @tsmith

Slide 6

Slide 6 text

Kotlin Features Higher-order functions, properties, mixins and delegation, extension functions, static nullability checking, automatic casts, reified generics, declaration- site variance, modules and build infrastructure, inline- functions (zero-overhead closures), operator overloading, String interpolation, pattern matching, first class IDE support with Java converter, default parameters, infix methods, and more... @tsmith

Slide 7

Slide 7 text

Nullability "I call it my billion-dollar mistake... [which] has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years." — Sir Charles Antony Richard Hoare @tsmith

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Nullability var b: String? = "bar" b = null //ok val l = b.length //Compilation error @tsmith

Slide 10

Slide 10 text

Nullability 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 @tsmith

Slide 11

Slide 11 text

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 } @tsmith

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Data class data class User(name: String) { } • equals() and hashCode() • toString() of the form "User(name=John)", • copy() @tsmith

Slide 14

Slide 14 text

Function Literal (Lambda) Undeclared function bodies used as an expression (ie, as data). 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) @tsmith

Slide 15

Slide 15 text

Higher Order Functions A higher-order function is a function that takes functions as parameters, or returns a function 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} @tsmith

Slide 16

Slide 16 text

Extension Functions Functions added to a type without modifying the original. fun Int.differenceFromTheAnswer(): Int { return 42 - this } //Or the Single Expression Function version fun Int.differenceFromTheAnswer(): = 42 - this //Usage val difference = 2.differenceFromTheAnswer() //40 @tsmith

Slide 17

Slide 17 text

Extension Functions Java Interoperability //Example 1: @file:JvmName("IntUtil") fun Int.differenceFromTheAnswer(): = 42 - this //Example 2: @file:JvmName("Util") @file:JvmMultifileClass fun Int.differenceFromTheAnswer(): = 42 - this @tsmith

Slide 18

Slide 18 text

Extension Functions in Android SharedPreferences.Editor editor; editor = getSharedPreferences(MODE_PRIVATE).edit(); editor.putString("login_token", token); editor.apply(); @tsmith

Slide 19

Slide 19 text

Extension Functions in Android Also Higher Order functions and Function Literals! fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) { val editor = SharedPreferences.Editor() editor.func() editor.apply() } getSharedPreferences(MODE_PRIVATE).edit { putString("login_token", token) } @tsmith

Slide 20

Slide 20 text

Inline Functions //All the memory allocation fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) //inlines into java byte code, matching the java signature inline fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) @tsmith

Slide 21

Slide 21 text

Builders Anko verticalLayout { padding = dip(16) textView("Username:") { textSize = 18f }.layoutParams { verticalMargin = dip(4) } val login = editText() button("Sign up") { textSize = 20f onClick { login(login.text) } }.layoutParams { topMargin = dip(8) } } @tsmith

Slide 22

Slide 22 text

Kotlin -> Gradle @tsmith

Slide 23

Slide 23 text

Installing Kotlin 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" } @tsmith

Slide 24

Slide 24 text

Gradle Build script in Kotlin buildscript { repositories { jcenter() } dependencies { classpath("com.android.tools.build:gradle:2.2.0-alpha4") } } apply() configure { buildToolsVersion("23.0.3") compileSdkVersion(23) defaultConf { minSdkVersion = DefaultApiVersion.create(15) applicationId = "com.example.kotlingradle" versionCode = 1 versionName = "1.0" } } inline fun AppExtension.defaultConf(func: DefaultProductFlavor.() -> Unit) = defaultConfig.apply(func) @tsmith

Slide 25

Slide 25 text

The case of API Keys public class SampleApplication extends Application { private static final String CONSUMER_KEY = "PRODUCTION_KEY"; private static final String CONSUMER_SECRET = "PRODUCTION_SECRET"; @Override public void onCreate() { super.onCreate(); SessionConfig authConfig = new SessionConfig(CONSUMER_KEY, CONSUMER_SECRET); Sdk.init(authConfig); } } @tsmith

Slide 26

Slide 26 text

The case of API Keys private static final String CONSUMER_KEY = "PRODUCTION_KEY"; private static final String CONSUMER_SECRET = "PRODUCTION_KEY"; ... final SessionConfig authConfig = new SessionConfig(CONSUMER_KEY, CONSUMER_SECRET); @tsmith

Slide 27

Slide 27 text

Enter Gradle android { buildTypes { release { buildConfigField 'String', 'CONSUMER_KEY', "\"PRODUCTION_KEY\"" buildConfigField 'String', 'CONSUMER_SECRET', "\"PRODUCTION_SECRET\"" } debug { buildConfigField 'String', 'CONSUMER_KEY', '\"DEBUG_KEY\"' buildConfigField 'String', 'CONSUMER_SECRET', '\"DEBUG_SECRET\"' } } ... @tsmith

Slide 28

Slide 28 text

BuildConfig class public final class BuildConfig { public static final String CONSUMER_KEY = "PRODUCTION_KEY"; ... public class AwesomeApplication extends Application { private static final String CONSUMER_KEY = BuildConfig.CONSUMER_KEY; @tsmith

Slide 29

Slide 29 text

Gradle properties • Gradle (Project) Properties • System Variables • Custom properties file @tsmith

Slide 30

Slide 30 text

Provided values def getProp(String key) { if (System.properties.get(key) != null) { return System.properties.get(key) } else if (project.hasProperty(key)) { return project.property(key) } throw new GradleException("${key} is not available") } @tsmith

Slide 31

Slide 31 text

Using the provided value //Using provided value via getProp function release { buildConfigField "String", "CONSUMER_KEY", "${getProp("INJECTED_KEY")}" ... @tsmith

Slide 32

Slide 32 text

custom.properties INJECTED_KEY=PRODUCTION_KEY INJECTED_SECRET=PRODUCTION_SECRET ... @tsmith

Slide 33

Slide 33 text

Custom Task task createCustomPropertiesFile << { def props = new Properties() props.put('INJECTED_KEY', getProp('INJECTED_KEY')) props.put('INJECTED_SECRET', getProp('INJECTED_SECRET')) file('custom.properties').withOutputStream { props.store(it, "") } } @tsmith

Slide 34

Slide 34 text

Custom Properties file tasks << 'createCustomPropertiesFile' { ... } $ ./gradlew createCustomPropertiesFile -PINJECTED_KEY=DEV_KEY @tsmith

Slide 35

Slide 35 text

Gradle plugin Basic ingredients: • Task • Extension • Plugin @tsmith

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Task 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); } @tsmith

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Task class CreatePropertiesFileTask extends DefaultTask { @Input List keys @OutputFile File outputFile ... } @tsmith

Slide 40

Slide 40 text

Extension class PropertyHolderExtension { List keys = [] File customPropertiesFile Project project PropertyHolderExtension(Project project) { this.project = project } @tsmith

Slide 41

Slide 41 text

Extension 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)) { ... @tsmith

Slide 42

Slide 42 text

Plugin class PropertyHolderPlugin implements Plugin { @Override void apply(Project project) { def propertyHolder = project.extensions.create( "propertyHolder", PropertyHolderExtension, project) @tsmith

Slide 43

Slide 43 text

Plugin //Example 1. Without configure def propertyHolder = project.extensions.create(...) //Example 2. With configure project.configure(project) { def propertyHolder = extensions.create(...) @tsmith

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Consuming the plugin apply 'custom-properties' propertyHolder{ keys = 'CONSUMER_KEY',... keyFile 'custom.properties' } @tsmith

Slide 46

Slide 46 text

Kotlin Version - Task open class CreatePropertiesFileTask() : DefaultTask() { @Input var keys: List = ArrayList() @OutputFile var keyFile: File? = null @tsmith

Slide 47

Slide 47 text

Kotlin Version - Task @TaskAction fun createFile() { val properties = Properties() for (key in keys) { properties.put(key, getProp(key)) } val fileOutputStream = FileOutputStream(keyFile) properties.store(fileOutputStream, "") } @tsmith

Slide 48

Slide 48 text

Kotlin Version - Task @TaskAction fun createFile() { val properties = Properties() keys.forEach { key -> properties.put(key, getProp(key)) } properties.store(keyFile!!.outputStream(), "") @tsmith

Slide 49

Slide 49 text

Extension - Kotlin version data class PropertyHolderExtension(val project: Project, var keys: List = ArrayList(), var customFile: File? = null) @tsmith

Slide 50

Slide 50 text

Plugin - Kotlin Version class PropertyHolderPlugin : Plugin { override fun apply(project: Project) { project.extensions .add("kotlinHolder",PropertyHolderExtension(project)) @tsmith

Slide 51

Slide 51 text

Plugin - Kotlin Version class PropertyHolderPlugin : Plugin { override fun apply(project: Project) { project.extensions .add("kotlinHolder", PropertyHolderExtension(project)) project.afterEvaluate { project -> val extension = project.extensions .getByName("kotlinHolder") as PropertyHolderExtension } @tsmith

Slide 52

Slide 52 text

Sprinkle it with Extension Functions fun Project.configure(exec: Project.() -> Unit) = exec() fun Project.extensions(exec: ExtensionContainer.() -> Unit) = extensions.exec() fun Project.propertyHolder() = extensions.getByName("kotlinHolder") as PropertyHolderExtension @tsmith

Slide 53

Slide 53 text

Kotlin Version - Plugin //before project.extensions .add("kotlinHolder", PropertyHolderExtension(project)) //after project.configure { extensions { add("kotlinHolder", PropertyHolderExtension(project)) } @tsmith

Slide 54

Slide 54 text

Kotlin Version - Plugin 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!! } } } } @tsmith

Slide 55

Slide 55 text

Resources • Kotlin docs • Kotlin Koans • Anko • Gradle Script Kotlin • Advancing Android Development with the Kotlin Language • This project @tsmith

Slide 56

Slide 56 text

Thanks! Ty Smith @tsmith