$30 off During Our Annual Pro Sale. View Details »

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. Better [Sad Puppy] Development
    with Kotlin and Gradle
    Ty Smith
    Uber
    goo.gl/hG20WW
    @tsmith

    View Slide

  2. 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

    View Slide

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

    View Slide

  4. 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

    View Slide

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

    View Slide

  6. 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

    View Slide

  7. 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

    View Slide

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

    View Slide

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

    View Slide

  10. 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

    View Slide

  11. 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

    View Slide

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

    View Slide

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

    View Slide

  14. 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

    View Slide

  15. 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

    View Slide

  16. 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

    View Slide

  17. 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

    View Slide

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

    View Slide

  19. 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

    View Slide

  20. 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

    View Slide

  21. 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

    View Slide

  22. Kotlin -> Gradle
    @tsmith

    View Slide

  23. 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

    View Slide

  24. build.gradle.kts
    buildscript {
    repositories {
    jcenter()
    gradleScriptKotlin()
    }
    dependencies { classpath("com.android.tools.build:gradle:2.2.0") }
    }
    apply()
    android {
    buildToolsVersion("23.0.3")
    compileSdkVersion(23)
    defaultConfigExtension {
    setMinSdkVersion(15)
    setTargetSdkVersion(23)
    applicationId = "com.example.kotlingradle"
    versionCode = 1
    versionName = "1.0"
    }
    }
    @tsmith

    View Slide

  25. build.gradle.kts Extension Functions
    fun Project.android(setup: AppExtension.() -> Unit) = the().setup()
    fun NamedDomainObjectContainer.release(setup: BuildType.() -> Unit) = findByName("release").setup()
    fun AppExtension.defaultConfigExtension(setup: DefaultProductFlavor.() -> Unit) = defaultConfig.setup()
    fun AppExtension.buildTypesExtension(setup: NamedDomainObjectContainer.() -> 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

    View Slide

  26. 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

    View Slide

  27. 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

    View Slide

  28. 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

    View Slide

  29. 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

    View Slide

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

    View Slide

  31. 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

    View Slide

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

    View Slide

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

    View Slide

  34. 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

    View Slide

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

    View Slide

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

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  42. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  48. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. 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

    View Slide

  53. 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

    View Slide

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

    View Slide

  55. 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

    View Slide

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

    View Slide

  57. Thanks!
    Ty Smith
    @tsmith

    View Slide