Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Better Android Development with Kotlin and Gradle

Ty Smith
September 30, 2016

Better Android Development with Kotlin and Gradle

In this talk, Ty will walk you through setting up and using Kotlin with Gradle to streamline your workflow for Android development, both within the build tool phase and within the application itself, so that you can use a consistent language through the entire Android stack. After a brief overview of Kotlin, we’ll dive into how it can be used with Gradle to accelerate Android Development with a consistent language. I'll walk through a real world example of building a Gradle plugin in and scripts in Groovy, then I'll convert those into Kotlin. An open source repo of the sample will be provided to follow along.

Ty Smith

September 30, 2016
Tweet

More Decks by Ty Smith

Other Decks in Programming

Transcript

  1. 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
  2. How about Groovy? • Groovy is dynamic • Harder for

    IDEs to parse and infer • Won't ever be consistent with Android @tsmith
  3. 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
  4. How can Kotlin help today? • Reduce common errors •

    Reduce boilerplate • Improve Android API • Can achieve consistency in the app and build @tsmith
  5. 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
  6. 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
  7. Nullability var a: String = "foo" //Reversed Type and name

    a = null //Compilation error @tsmith
  8. Nullability var b: String? = "bar" b = null //ok

    val l = b.length //Compilation error @tsmith
  9. 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
  10. 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
  11. Properties class User { var name: String = ... get()

    = name // Can declare methods inline set(value) { name = value().toLowerCase() } } @tsmith
  12. Data class data class User(name: String) { } • equals()

    and hashCode() • toString() of the form "User(name=John)", • copy() @tsmith
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. build.gradle.kts 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" } } @tsmith
  22. build.gradle.kts Extension Functions fun Project.android(setup: AppExtension.() -> Unit) = the<AppExtension>().setup()

    fun NamedDomainObjectContainer<BuildType>.release(setup: BuildType.() -> Unit) = findByName("release").setup() fun AppExtension.defaultConfigExtension(setup: DefaultProductFlavor.() -> Unit) = defaultConfig.setup() fun AppExtension.buildTypesExtension(setup: NamedDomainObjectContainer<BuildType>.() -> Unit) = buildTypes { it.setup() } fun DefaultProductFlavor.setMinSdkVersion(value: Int) = setMinSdkVersion(value.asApiVersion()) fun DefaultProductFlavor.setTargetSdkVersion(value: Int) = setTargetSdkVersion(value.asApiVersion()) fun Int.asApiVersion(): ApiVersion = DefaultApiVersion.create(this) @tsmith
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. Using the provided value //Using provided value via getProp function

    release { buildConfigField "String", "CONSUMER_KEY", "${getProp("INJECTED_KEY")}" ... @tsmith
  29. 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
  30. Custom Properties file tasks << 'createCustomPropertiesFile' { ... } $

    ./gradlew createCustomPropertiesFile -PINJECTED_KEY=DEV_KEY @tsmith
  31. 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
  32. 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
  33. Task // Properties properties = new Properties(); // for (String

    key : keys) { properties.put(key, getProp(key)); } // // FileOutputStream fileOutputStream = new FileOutputStream(keyFile); properties.store(fileOutputStream, ""); // // // // // @tsmith
  34. Extension class PropertyHolderExtension { List<String> keys = [] File customPropertiesFile

    Project project PropertyHolderExtension(Project project) { this.project = project } @tsmith
  35. 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
  36. Plugin class PropertyHolderPlugin implements Plugin<Project> { @Override void apply(Project project)

    { def propertyHolder = project.extensions.create( "propertyHolder", PropertyHolderExtension, project) @tsmith
  37. Plugin //Example 1. Without configure def propertyHolder = project.extensions.create(...) //Example

    2. With configure project.configure(project) { def propertyHolder = extensions.create(...) @tsmith
  38. Plugin project.configure(project) { ... afterEvaluate { if (!propertyHolder.keys.isEmpty()) { project.tasks.create("createCustomPropertiesFile",

    CreatePropertiesFileTask) { it.keyFile = propertyHolder.customPropertiesFile it.keys = propertyHolder.keys } } } ... @tsmith
  39. Kotlin Version - Task open class CreatePropertiesFileTask() : DefaultTask() {

    @Input var keys: List<String> = ArrayList() @OutputFile var keyFile: File? = null @tsmith
  40. 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
  41. Kotlin Version - Task @TaskAction fun createFile() { val properties

    = Properties() keys.forEach { key -> properties.put(key, getProp(key)) } properties.store(keyFile!!.outputStream(), "") @tsmith
  42. Extension - Kotlin version data class PropertyHolderExtension(val project: Project, var

    keys: List<String> = ArrayList(), var customFile: File? = null) @tsmith
  43. Plugin - Kotlin Version class PropertyHolderPlugin : Plugin<Project> { override

    fun apply(project: Project) { project.extensions .add("kotlinHolder",PropertyHolderExtension(project)) @tsmith
  44. Plugin - Kotlin Version 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 } @tsmith
  45. 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
  46. 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
  47. Resources • Kotlin docs • Kotlin Koans • Anko •

    Gradle Script Kotlin • Advancing Android Development with the Kotlin Language • This project @tsmith