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

Kotlin 2 ½ Years Later

Kotlin 2 ½ Years Later

This is a "brief" story about how Cody Engel uses Kotlin along with some general tips/tricks/ideas that can be applied to your own projects.

Cody Engel

April 17, 2019
Tweet

More Decks by Cody Engel

Other Decks in Technology

Transcript

  1. Kotlin 2 ½ Years Later
    A brief story about how Cody uses Kotlin.

    View full-size slide

  2. Started Android In 2011
    Java and Eclipse were fun.

    View full-size slide

  3. Started Learning Kotlin At Yello
    Split my time about 25% Kotlin and 75% Java at the time.

    View full-size slide

  4. Working Exclusively In Kotlin At ActiveCampaign
    We’re hiring. Message me on engel.dev if you want to learn more.

    View full-size slide

  5. Time For Some Kotlin
    If you have questions during the talk, message me on engel.dev.

    View full-size slide

  6. val typeInference: Int = 1
    What I’m Not Talking About

    View full-size slide

  7. val typeInference = 1
    What I’m Not Talking About

    View full-size slide

  8. data class Speaker(val name: String = "Cody")
    val speaker = Speaker()
    val stringTemplates = """
    Wow this is cool.
    Thanks, ${speaker.name}
    """.trimIndent()
    What I’m Not Talking About

    View full-size slide

  9. data class Speaker(val name: String = "Cody")
    val speaker = Speaker()
    val stringTemplates = """
    Wow this is cool.
    Thanks, ${speaker.name}
    """.trimIndent()
    What I’m Not Talking About

    View full-size slide

  10. fun conditionalExpressions(a: Int, b: Int): Int {
    if (a > b) {
    return a
    } else {
    return b
    }
    }
    What I’m Not Talking About

    View full-size slide

  11. fun conditionalExpressions(a: Int, b: Int): Int {
    return if (a > b) {
    a
    } else {
    b
    }
    }
    What I’m Not Talking About

    View full-size slide

  12. fun conditionalExpressions(a: Int, b: Int)
    = if (a > b) a else b
    What I’m Not Talking About

    View full-size slide

  13. fun typeCasting(any: Any) {
    when (any) {
    is String -> any.length
    is Map<*, *> -> any.entries
    is Int -> any + 16
    }
    }
    What I’m Not Talking About

    View full-size slide

  14. class DefaultValues(number: Int = 0)
    fun defaultValues(defaultValues: DefaultValues = DefaultValues()) {
    defaultValues()
    }
    What I’m Not Talking About

    View full-size slide

  15. Goodbye NPE if (nullDoesntExist != null) { }
    Kotlin greatly reduces the
    chance that you will
    inadvertently cause a
    NullPointerException in
    production.
    engel.dev

    View full-size slide

  16. if (nullDoesntExist != null) {
    // Why check null if it does
    // not exist in Kotlin?
    }
    Why Still Check For Null?

    View full-size slide

  17. package kotlin
    public open class KotlinNullPointerException : java.lang.NullPointerException {
    public constructor() { /* compiled code */ }
    public constructor(message: kotlin.String?) { /* compiled code */ }
    }
    Because NPEs Still Exist.

    View full-size slide

  18. val stringWithNothing: String? = null
    if (stringWithNothing != null)
    Which Do You Prefer?
    val stringWithNothing: String = “”
    stringWithNothing.isNotEmpty()

    View full-size slide

  19. val intWithNothing: Int? = null
    if (intWithNothing != null)
    Which Do You Prefer?
    val intWithNothing: Int = -1
    if (intWithNothing != -1)

    View full-size slide

  20. val intWithNothing: Int? = null
    if (intWithNothing != null)
    Which Do You Prefer?
    val intWithNothing: Int = 0
    if (intWithNothing != -1)

    View full-size slide

  21. if (possiblyNull == null) {
    // What do we do!?!?!?
    }

    View full-size slide

  22. val possiblyNull: String? = null
    val definitelyNotNull = possiblyNull ?: ""

    View full-size slide

  23. val possiblyNull: String? = null
    val definitelyNotNull = possiblyNull ?: ""

    View full-size slide

  24. val possiblyNull: String? = null
    val definitelyNotNull = possiblyNull?.run {
    println("Cool this wasn't null.")
    } ?: ""

    View full-size slide

  25. val possiblyNull: String? = null
    val definitelyNotNull = possiblyNull ?: {
    println("This used to be null.")
    ""
    }()

    View full-size slide

  26. val possiblyNull: String? = null
    val definitelyNotNull = possiblyNull?.let {
    println("Cool this wasn't null.")
    } ?: {
    println("This used to be null.")
    ""
    }()

    View full-size slide

  27. val possiblyNull: String? = null
    val definitelyNotNull = possiblyNull?.let {
    println("Cool this wasn't null.")
    } ?: {
    println("This used to be null.")
    ""
    }.invoke()

    View full-size slide

  28. Working With The NullPointerException
    data class LocalUser(
    val apiKey: String? = null,
    val username: String? = null,
    val userId: Long? = null
    )

    View full-size slide

  29. data class LocalUser(
    val apiKey: String? = null,
    val username: String? = null,
    val userId: Long? = null
    )
    data class AuthenticationRecord(
    val apiKey: String,
    val username: String,
    val userId: Long
    )

    View full-size slide

  30. data class AuthenticationRecord(
    val apiKey: String,
    val username: String,
    val userId: Long
    )
    val localUser = LocalUser()
    val authRecord = AuthenticationRecord(
    apiKey = localUser.apiKey,
    username = localUser.username,
    userId = localUser.userId
    )

    View full-size slide

  31. data class AuthenticationRecord(
    val apiKey: String,
    val username: String,
    val userId: Long
    )
    val localUser = LocalUser()
    val authRecord = AuthenticationRecord(
    apiKey = localUser.apiKey,
    username = localUser.username,
    userId = localUser.userId
    )
    Required: String, Found String?

    View full-size slide

  32. data class AuthenticationRecord(
    val apiKey: String,
    val username: String,
    val userId: Long
    )
    val localUser = LocalUser()
    val authRecord = AuthenticationRecord(
    apiKey = localUser.apiKey,
    username = localUser.username,
    userId = localUser.userId
    )
    Required: String, Found String?
    Required: String, Found String?

    View full-size slide

  33. data class AuthenticationRecord(
    val apiKey: String,
    val username: String,
    val userId: Long
    )
    val localUser = LocalUser()
    val authRecord = AuthenticationRecord(
    apiKey = localUser.apiKey,
    username = localUser.username,
    userId = localUser.userId
    )
    Required: String, Found String?
    Required: String, Found String?
    Required: String, Found String?

    View full-size slide

  34. val localUser = LocalUser()
    val authRecord = AuthenticationRecord(
    apiKey = localUser.apiKey ?: "",
    username = localUser.username ?: "",
    userId = localUser.userId ?: -1
    )
    val authRecord = AuthenticationRecord(
    apiKey = localUser.apiKey,
    username = localUser.username,
    userId = localUser.userId
    )
    Required: String, Found String?
    Required: String, Found String?
    Required: String, Found String?

    View full-size slide

  35. val localUser = LocalUser()
    val authRecord = AuthenticationRecord(
    apiKey = localUser.apiKey!!,
    username = localUser.username!!,
    userId = localUser.userId!!
    )
    val localUser = LocalUser()
    val authRecord = AuthenticationRecord(
    apiKey = localUser.apiKey ?: "",
    username = localUser.username ?: "",
    userId = localUser.userId ?: -1
    )

    View full-size slide

  36. apiKey = localUser.apiKey!!,
    username = localUser.username!!,
    userId = localUser.userId!!
    )
    val authRecord = try {
    AuthenticationRecord(
    apiKey = localUser.apiKey!!,
    username = localUser.username!!,
    userId = localUser.userId!!
    )
    } catch (exception: KotlinNullPointerException) {
    // Provide an error to the user because they
    // aren't authenticated.
    }

    View full-size slide

  37. // Provide an error to the user because they
    // aren't authenticated.
    }
    val authRecord = if (
    localUser.apiKey != null &&
    localUser.username != null &&
    localUser.userId != null) {
    AuthenticationRecord(
    localUser.apiKey,
    localUser.username,
    localUser.userId
    )
    } else {
    // Provide an error to the user because they
    // aren't authenticated.
    }

    View full-size slide

  38. } else {
    // Provide an error to the user because they
    // aren't authenticated.
    }
    val authRecord = try {
    AuthenticationRecord(
    apiKey = localUser.apiKey!!,
    username = localUser.username!!,
    userId = localUser.userId!!
    )
    } catch (exception: KotlinNullPointerException) {
    // Provide an error to the user because they
    // aren't authenticated.
    }

    View full-size slide

  39. This is Not Jake Wharton Approved…
    …so that was kind of

    View full-size slide

  40. Kotlin Defaults To Non-Null For Java APIs…
    …sorry.

    View full-size slide

  41. Data Classes data class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    val tina = Cat()
    tina.name // Tina
    tina.age // 4
    tina.equals(Cat(name = "Tina")) // true
    tina.hashCode() // some number
    val (name, age) = tina
    val tinaIsOlder = tina.copy(age = 5) // older
    Sometimes classes exist for
    the sole purpose of
    containing data. This is
    where data classes shine.
    engel.dev

    View full-size slide

  42. class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    val tina = Cat()
    tina.name // Tina
    tina.age // 4
    tina.equals(Cat(name = "Tina")) // true
    tina.hashCode() // some number
    val (name, age) = tina
    val tinaIsOlder = tina.copy(age = 5) // older
    Normal Boring Class.

    View full-size slide

  43. class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    val tina = Cat()
    tina.name // Tina
    tina.age // 4
    tina.equals(Cat(name = "Tina")) // false
    tina.hashCode() // some number
    val (name, age) = tina
    val tinaIsOlder = tina.copy(age = 5) // older
    Normal Boring Class.

    View full-size slide

  44. class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    val tina = Cat()
    tina.name // Tina
    tina.age // 4
    tina.equals(Cat(name = "Tina")) // false
    tina.hashCode() // some number, but calculated differently
    val (name, age) = tina
    val tinaIsOlder = tina.copy(age = 5) // older
    Normal Boring Class.

    View full-size slide

  45. class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    val tina = Cat()
    tina.name // Tina
    tina.age // 4
    tina.equals(Cat(name = "Tina")) // false
    tina.hashCode() // some number, but calculated differently
    val (name, age) = tina
    val tinaIsOlder = tina.copy(age = 5) // older
    Normal Boring Class.

    View full-size slide

  46. class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    val tina = Cat()
    tina.name // Tina
    tina.age // 4
    tina.equals(Cat(name = "Tina")) // false
    tina.hashCode() // some number, but calculated differently
    val (name, age) = tina
    val tinaIsOlder = tina.copy(age = 5)
    Normal Boring Class.

    View full-size slide

  47. class Cat(
    val name: String = "Tina",
    val age: Int = 4,
    currentlyHungry: Boolean
    )
    Normal Boring Class.

    View full-size slide

  48. data class Cat(
    val name: String = "Tina",
    val age: Int = 4,
    currentlyHungry: Boolean
    )
    Exciting Data Class.

    View full-size slide

  49. data class Cat(
    val name: String = "Tina",
    val age: Int = 4,
    currentlyHungry: Boolean
    )
    "
    Exciting Data Class.

    View full-size slide

  50. data class Cat(
    val name: String = "Tina",
    val age: Int = 4,
    currentlyHungry: Boolean
    )
    "
    Exciting Data Class.
    Data class primary constructor must have only property (val/var) parameters

    View full-size slide

  51. data class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    Using Data Classes.

    View full-size slide

  52. data class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    val shouldBeConsistent = tina.hashCode()
    Using Data Classes.

    View full-size slide

  53. data class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    val shouldBeTrue = tina.equals(Cat(name = “Tina"))
    val shouldBeConsistent = tina.hashCode()
    Using Data Classes.

    View full-size slide

  54. data class Cat(
    val name: String = "Tina",
    val age: Int = 4
    )
    val tinaIsOlder = tina.copy(age = 5)
    val shouldBeTrue = tina.equals(Cat(name = “Tina"))
    val shouldBeConsistent = tina.hashCode()
    Using Data Classes.

    View full-size slide

  55. Sealed Classes sealed class DealCustomFieldValue(
    val fieldId: Long?,
    val value: String?,
    val title: String?,
    val order: Long?,
    val type: String?,
    val fieldValueId: Long?
    ) {
    data class StandardFieldValue(
    private val _fieldId: Long?,
    private val _value: String?,
    private val _title: String?,
    private val _order: Long?,
    private val _type: String?,
    private val _fieldValueId: Long?
    ) : DealCustomFieldValue(
    _fieldId,
    _value,
    _title,
    _order,
    _type,
    _fieldValueId
    )
    data class NumberFieldValue(
    private val _fieldId: Long?,
    private val _value: String?,
    private val _title: String?,
    private val _order: Long?,
    private val _type: String?,
    private val _fieldValueId: Long?
    ) : DealCustomFieldValue(
    _fieldId,
    _value,
    _title,
    _order,
    _type,
    _fieldValueId
    )
    data class CurrencyFieldValue(
    private val _fieldId: Long?,
    private val _value: String?,
    private val _title: String?,
    private val _order: Long?,
    private val _type: String?,
    private val _fieldValueId: Long?,
    val fieldCurrency: String?,
    val currencyPosition: String?
    ) : DealCustomFieldValue(
    _fieldId,
    _value,
    _title,
    _order,
    _type,
    _fieldValueId
    )
    }
    Used for representing
    restricted class hierarchies.
    They are like an extension on
    enum classes where sub-
    classes can have multiple
    instances available at a
    time.
    engel.dev

    View full-size slide

  56. sealed class DealCustomFieldValue(
    val fieldId: Long?,
    val value: String?,
    val title: String?,
    val order: Long?,
    val type: String?,
    val fieldValueId: Long?
    ) {
    data class StandardFieldValue(

    View full-size slide

  57. ) {
    data class StandardFieldValue(
    private val _fieldId: Long?,
    private val _value: String?,
    private val _title: String?,
    private val _order: Long?,
    private val _type: String?,
    private val _fieldValueId: Long?
    ) : DealCustomFieldValue(
    _fieldId,

    View full-size slide

  58. private val _type: String?,
    private val _fieldValueId: Long?
    ) : DealCustomFieldValue(
    _fieldId,
    _value,
    _title,
    _order,
    _type,
    _fieldValueId
    )

    View full-size slide

  59. data class CurrencyFieldValue(
    private val _fieldId: Long?,
    private val _value: String?,
    private val _title: String?,
    private val _order: Long?,
    private val _type: String?,
    private val _fieldValueId: Long?,
    val fieldCurrency: String?,
    val currencyPosition: String?
    ) : DealCustomFieldValue(

    View full-size slide

  60. val fieldCurrency: String?,
    val currencyPosition: String?
    ) : DealCustomFieldValue(
    _fieldId,
    _value,
    _title,
    _order,
    _type,
    _fieldValueId
    )
    }

    View full-size slide

  61. sealed class DealCustomFieldValue(
    val fieldId: Long?,
    val value: String?,
    val title: String?,
    val order: Long?,
    val type: String?,
    val fieldValueId: Long?
    ) {
    data class StandardFieldValue(

    View full-size slide

  62. sealed class DealCustomFieldValue {
    abstract val fieldId: Long?
    abstract val value: String?
    abstract val title: String?
    abstract val order: Long?
    abstract val type: String?
    abstract val fieldValueId: Long?
    data class StandardFieldValue(
    override val fieldId: Long?,

    View full-size slide

  63. data class StandardFieldValue(
    override val fieldId: Long?,
    override val value: String?,
    override val title: String?,
    override val order: Long?,
    override val type: String?,
    override val fieldValueId: Long?
    ) : DealCustomFieldValue()
    data class NumberFieldValue(
    override val fieldId: Long?,

    View full-size slide

  64. data class CurrencyFieldValue(
    override val fieldId: Long?,
    override val value: String?,
    override val title: String?,
    override val order: Long?,
    override val type: String?,
    override val fieldValueId: Long?,
    val fieldCurrency: String?,
    val currencyPosition: String?
    ) : DealCustomFieldValue()

    View full-size slide

  65. sealed class DealCustomFieldValue(
    val fieldId: Long?,
    val value: String?,
    val title: String?,
    val order: Long?,
    val type: String?,
    val fieldValueId: Long?
    ) {
    data class StandardFieldValue(
    private val _fieldId: Long?,
    private val _value: String?,
    private val _title: String?,
    private val _order: Long?,
    private val _type: String?,
    private val _fieldValueId: Long?
    ) : DealCustomFieldValue(
    _fieldId,
    _value,
    _title,
    _order,
    _type,
    _fieldValueId
    )
    data class NumberFieldValue(
    private val _fieldId: Long?,
    private val _value: String?,
    private val _title: String?,
    private val _order: Long?,
    private val _type: String?,
    private val _fieldValueId: Long?
    ) : DealCustomFieldValue(
    _fieldId,
    _value,
    _title,
    _order,
    _type,
    _fieldValueId
    )
    data class CurrencyFieldValue(
    private val _fieldId: Long?,
    private val _value: String?,
    private val _title: String?,
    private val _order: Long?,
    private val _type: String?,
    private val _fieldValueId: Long?,
    val fieldCurrency: String?,
    val currencyPosition: String?
    ) : DealCustomFieldValue(
    _fieldId,
    _value,
    _title,
    _order,
    _type,
    _fieldValueId
    )
    }
    sealed class DealCustomFieldValue {
    abstract val fieldId: Long?
    abstract val value: String?
    abstract val title: String?
    abstract val order: Long?
    abstract val type: String?
    abstract val fieldValueId: Long?
    data class StandardFieldValue(
    override val fieldId: Long?,
    override val value: String?,
    override val title: String?,
    override val order: Long?,
    override val type: String?,
    override val fieldValueId: Long?
    ) : DealCustomFieldValue()
    data class NumberFieldValue(
    override val fieldId: Long?,
    override val value: String?,
    override val title: String?,
    override val order: Long?,
    override val type: String?,
    override val fieldValueId: Long?
    ) : DealCustomFieldValue()
    data class CurrencyFieldValue(
    override val fieldId: Long?,
    override val value: String?,
    override val title: String?,
    override val order: Long?,
    override val type: String?,
    override val fieldValueId: Long?,
    val fieldCurrency: String?,
    val currencyPosition: String?
    ) : DealCustomFieldValue()
    }

    View full-size slide

  66. fun mapCustomFieldResponse(field: DealCustomFieldValue): StandardFieldData {
    return field.run {
    when (this) {
    is DealCustomFieldValue.CurrencyFieldValue -> {
    createStandardField(
    title = title ?: "",
    body = currencyUtil.serverValueToDisplayValue(
    value?.toDouble(), currencyPosition, fieldCurrency
    ),
    leftIcon = R.drawable.ic_short_text
    )
    }
    is DealCustomFieldValue.NumberFieldValue ->
    createStandardField(
    title = title ?: "",
    body = value.toDoubleFormatTwoDecimalPlaces(),
    leftIcon = R.drawable.ic_short_text
    )
    is DealCustomFieldValue.StandardFieldValue ->
    createStandardField(
    title = title ?: “",
    body = value ?: “",
    leftIcon = R.drawable.ic_short_text
    )
    }
    }
    }

    View full-size slide

  67. when (this) {
    is DealCustomFieldValue.CurrencyFieldValue -> {}
    is DealCustomFieldValue.NumberFieldValue -> {}
    is DealCustomFieldValue.StandardFieldValue -> {}
    }

    View full-size slide

  68. when (this) {
    is DealCustomFieldValue.CurrencyFieldValue -> {}
    is DealCustomFieldValue.NumberFieldValue -> {}
    is DealCustomFieldValue.StandardFieldValue -> {}
    else -> {}
    }

    View full-size slide

  69. when (this) {
    is DealCustomFieldValue.CurrencyFieldValue -> {}
    is DealCustomFieldValue.NumberFieldValue -> {}
    is DealCustomFieldValue.StandardFieldValue -> {}
    else -> { /* not necessary */ }
    }

    View full-size slide

  70. when (this) {
    is DealCustomFieldValue.CurrencyFieldValue -> {}
    is DealCustomFieldValue.NumberFieldValue -> {
    createStandardField(
    title = title ?: "",
    body = value.toDoubleFormatTwoDecimalPlaces(),
    leftIcon = R.drawable.ic_short_text
    )
    }
    is DealCustomFieldValue.StandardFieldValue -> {}
    }

    View full-size slide

  71. when (this) {
    is DealCustomFieldValue.CurrencyFieldValue -> {}
    is DealCustomFieldValue.NumberFieldValue -> {
    createStandardField(
    title = title ?: "",
    body = value.toDoubleFormatTwoDecimalPlaces(),
    leftIcon = R.drawable.ic_short_text
    )
    }
    is DealCustomFieldValue.StandardFieldValue -> {
    createStandardField(
    title = title ?: “",
    body = value ?: “",
    leftIcon = R.drawable.ic_short_text
    )
    }
    }

    View full-size slide

  72. when (this) {
    is DealCustomFieldValue.CurrencyFieldValue -> {
    createStandardField(
    title = title ?: "",
    body = currencyUtil.serverValueToDisplayValue(
    value?.toDouble(), currencyPosition, fieldCurrency
    ),
    leftIcon = R.drawable.ic_short_text
    )
    }
    is DealCustomFieldValue.NumberFieldValue -> {
    createStandardField(
    title = title ?: "",
    body = value.toDoubleFormatTwoDecimalPlaces(),
    leftIcon = R.drawable.ic_short_text
    )
    }

    View full-size slide

  73. Collections.kt val cats = listOf()
    cats.map { it.age } // List of Ages.
    cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Cats grouped by their age.
    cats.random() // Random Cat from the list.
    The Collections framework
    that ships with Kotlin makes
    working with collections a
    breeze.
    engel.dev

    View full-size slide

  74. val cats = listOf()
    cats.map { it.age } // List of Ages.
    cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    Collections.kt Abbreviated.

    View full-size slide

  75. val cats = listOf()
    cats.map { it.age } // List of Ages.
    cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    Collections.kt Abbreviated.

    View full-size slide

  76. val cats = listOf()
    cats.map { it.age } // List of Ages.
    cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    Collections.kt Abbreviated.

    View full-size slide

  77. val cats = listOf()
    cats.map { it.age } // List of Ages.
    cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    Collections.kt Abbreviated.

    View full-size slide

  78. val cats = listOf()
    cats.map { it.age } // List of Ages.
    cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    Collections.kt Abbreviated.

    View full-size slide

  79. val cats = listOf()
    cats.map { it.age } // List of Ages.
    cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    Collections.kt Abbreviated.

    View full-size slide

  80. val cats = listOf()
    cats.map { it.age } // List of Ages.
    cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    Collections.kt Abbreviated.

    View full-size slide

  81. val cats = listOf()
    cats.map { it.age } // List of Ages.
    cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    Collections.kt Abbreviated.

    View full-size slide

  82. cats.filter { it.age > 4 } // List of Cats older than 4.
    cats.last { it.name == "Tina" } // Last Cat named Tina.
    cats.slice(5..10) // Cats within the range of 5 to 10.
    cats.takeLast(15) // The last 15 Cats in the list.
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    cats
    .filter { it.age > 4 }
    .random() // Random Cat, older than 4.

    View full-size slide

  83. // Random Cat, from the last 20 elements, older than 4.
    cats
    .filter { it.age > 4 }
    .takeLast(20)
    .random()
    cats.groupBy { it.age } // Group Cats by their age.
    cats.random() // Random Cat from the list.
    cats
    .filter { it.age > 4 }
    .random() // Random Cat, older than 4.

    View full-size slide

  84. // Random Cat, from the last 20 elements, older than 4.
    cats
    .filter { it.age > 4 }
    .takeLast(20)
    .random()
    // Random Cat, older than 4, from the last 20 elements.
    cats
    .takeLast(20)
    .filter { it.age > 4 }
    .random()

    View full-size slide

  85. // Random Cat, from the last 20 elements, older than 4.
    cats
    .filter { it.age > 4 }
    .takeLast(20)
    .random()
    cats
    .takeLast(20)
    // New list of 20 cats.
    .filter { it.age > 4 }
    // New list containing only cats older than 4.
    .random()

    View full-size slide

  86. .takeLast(20)
    // New list of 20 cats.
    .filter { it.age > 4 }
    // New list containing only cats older than 4.
    .random()
    cats.reversed().asSequence()
    .take(20)
    .filter { it.age > 4 }
    .toList()
    .random()

    View full-size slide

  87. .takeLast(20)
    // New list of 20 cats.
    .filter { it.age > 4 }
    // New list containing only cats older than 4.
    .random()
    cats.reversed().asSequence()
    .take(20)
    .filter { it.age > 4 }
    .toList()
    .random()

    View full-size slide

  88. .takeLast(20)
    // New list of 20 cats.
    .filter { it.age > 4 }
    // New list containing only cats older than 4.
    .random()
    cats.reversed().asSequence()
    .take(20)
    .filter { it.age > 4 }
    .toList()
    .random()

    View full-size slide

  89. .takeLast(20)
    // New list of 20 cats.
    .filter { it.age > 4 }
    // New list containing only cats older than 4.
    .random()
    cats.reversed().asSequence()
    .take(20)
    .filter { it.age > 4 }
    .toList()
    .random()

    View full-size slide

  90. cats.reversed().asSequence()
    .take(20)
    .filter { it.age > 4 }
    .toList()
    .random()
    sealed class FieldValue {
    abstract val value: T
    data class NumericField(override val value: Long) : FieldValue()
    data class MoneyField(override val value: Double) : FieldValue()
    data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }

    View full-size slide

  91. abstract val value: T
    data class NumericField(override val value: Long) : FieldValue()
    data class MoneyField(override val value: Double) : FieldValue()
    data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }
    val fieldValues = listOf()
    val fieldGroup = mutableMapOf>()
    for (fieldValue in fieldValues) {
    val fieldGroupValues = fieldGroup[fieldValue::class] ?: mutableListOf()
    fieldGroupValues.add(fieldValue)
    fieldGroup[fieldValue::class] = fieldGroupValues
    }

    View full-size slide

  92. abstract val value: T
    data class NumericField(override val value: Long) : FieldValue()
    data class MoneyField(override val value: Double) : FieldValue()
    data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }
    val fieldValues = listOf()
    val fieldGroup = mutableMapOf>()
    for (fieldValue in fieldValues) {
    val fieldGroupValues = fieldGroup[fieldValue::class] ?: mutableListOf()
    fieldGroupValues.add(fieldValue)
    fieldGroup[fieldValue::class] = fieldGroupValues
    }

    View full-size slide

  93. abstract val value: T
    data class NumericField(override val value: Long) : FieldValue()
    data class MoneyField(override val value: Double) : FieldValue()
    data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }
    val fieldValues = listOf()
    val fieldGroup = mutableMapOf>()
    for (fieldValue in fieldValues) {
    val fieldGroupValues = fieldGroup[fieldValue::class] ?: mutableListOf()
    fieldGroupValues.add(fieldValue)
    fieldGroup[fieldValue::class] = fieldGroupValues
    }

    View full-size slide

  94. abstract val value: T
    data class NumericField(override val value: Long) : FieldValue()
    data class MoneyField(override val value: Double) : FieldValue()
    data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }
    val fieldValues = listOf()
    val fieldGroup = mutableMapOf>()
    for (fieldValue in fieldValues) {
    val fieldGroupValues = fieldGroup[fieldValue::class] ?: mutableListOf()
    fieldGroupValues.add(fieldValue)
    fieldGroup[fieldValue::class] = fieldGroupValues
    }

    View full-size slide

  95. abstract val value: T
    data class NumericField(override val value: Long) : FieldValue()
    data class MoneyField(override val value: Double) : FieldValue()
    data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }
    val fieldValues = listOf()
    val fieldGroup = mutableMapOf>()
    for (fieldValue in fieldValues) {
    val fieldGroupValues = fieldGroup[fieldValue::class] ?: mutableListOf()
    fieldGroupValues.add(fieldValue)
    fieldGroup[fieldValue::class] = fieldGroupValues
    }

    View full-size slide

  96. abstract val value: T
    data class NumericField(override val value: Long) : FieldValue()
    data class MoneyField(override val value: Double) : FieldValue()
    data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }
    val fieldValues = listOf()
    val fieldGroup = mutableMapOf>()
    for (fieldValue in fieldValues) {
    val fieldGroupValues = fieldGroup[fieldValue::class] ?: mutableListOf()
    fieldGroupValues.add(fieldValue)
    fieldGroup[fieldValue::class] = fieldGroupValues
    }

    View full-size slide

  97. abstract val value: T
    data class NumericField(override val value: Long) : FieldValue()
    data class MoneyField(override val value: Double) : FieldValue()
    data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }
    val fieldValues = listOf()
    val fieldGroup = mutableMapOf>()
    for (fieldValue in fieldValues) {
    val fieldGroupValues = fieldGroup[fieldValue::class] ?: mutableListOf()
    fieldGroupValues.add(fieldValue)
    fieldGroup[fieldValue::class] = fieldGroupValues
    }

    View full-size slide

  98. abstract val value: T
    data class NumericField(override val value: Long) : FieldValue()
    data class MoneyField(override val value: Double) : FieldValue()
    data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }
    val fieldValues = listOf()
    val fieldGroup = mutableMapOf>()
    for (fieldValue in fieldValues) {
    val fieldGroupValues = fieldGroup[fieldValue::class] ?: mutableListOf()
    fieldGroupValues.add(fieldValue)
    fieldGroup[fieldValue::class] = fieldGroupValues
    }

    View full-size slide

  99. data class TextField(override val value: String) : FieldValue()
    data class MultiSelect(override val value: List) : FieldValue>()
    }
    val fieldValues = listOf()
    val fieldGroup = mutableMapOf>()
    for (fieldValue in fieldValues) {
    val fieldGroupValues = fieldGroup[fieldValue::class] ?: mutableListOf()
    fieldGroupValues.add(fieldValue)
    fieldGroup[fieldValue::class] = fieldGroupValues
    }
    val fieldValues = listOf()
    val fieldGroup = fieldValues.groupBy { it::class }

    View full-size slide

  100. Extensions editText.setTextIfChanged(“blah")
    view.verticalPositionInParent
    moshi.toJson(EmailBody("subject", “body"))
    activeCampaignAnalytics.markComplete()
    intent.conversationUri
    In Java extending
    functionality on concrete
    classes require static Util
    classes.
    Kotlin allows us to add
    functionality to concrete
    classes through extensions.
    engel.dev

    View full-size slide

  101. editText.setTextIfChanged(“blah")
    view.verticalPositionInParent
    moshi.toJson(EmailBody("subject", “body"))
    activeCampaignAnalytics.markComplete()
    intent.conversationUri

    View full-size slide

  102. fun TextView.setTextIfChanged(text: CharSequence) {
    if (this.text.toString() != text) {
    setTextKeepState(text)
    }
    }
    editText.setTextIfChanged(“blah”)
    view.verticalPositionInParent
    moshi.toJson(EmailBody("subject", “body"))
    activeCampaignAnalytics.markComplete()
    intent.conversationUri

    View full-size slide

  103. editText.setTextIfChanged(“blah”)
    val View.verticalPositionInParent: Int
    get() {
    val offsetViewBounds = Rect()
    parentAsViewGroup
    .offsetDescendantRectToMyCoords(
    this,
    offsetViewBounds
    )
    return offsetViewBounds.top
    }
    view.verticalPositionInParent
    moshi.toJson(EmailBody("subject", “body"))
    activeCampaignAnalytics.markComplete()

    View full-size slide

  104. .offsetDescendantRectToMyCoords(
    this,
    offsetViewBounds
    )
    return offsetViewBounds.top
    }
    view.verticalPositionInParent
    inline fun Moshi.toJson(value: T): String {
    val adapter: JsonAdapter = adapter(T::class.java)
    return adapter.toJson(value)
    }
    moshi.toJson(EmailBody("subject", “body”))
    activeCampaignAnalytics.markComplete()
    intent.conversationUri

    View full-size slide

  105. view.verticalPositionInParent
    inline fun Moshi.toJson(value: T): String {
    val adapter: JsonAdapter = adapter(T::class.java)
    return adapter.toJson(value)
    }
    moshi.toJson(EmailBody("subject", “body”))
    fun ActiveCampaignAnalytics.markComplete() {
    sendEvent(completeConversationEvent)
    }
    activeCampaignAnalytics.markComplete()
    intent.conversationUri

    View full-size slide

  106. view.verticalPositionInParent
    inline fun Moshi.toJson(value: T): String {
    val adapter: JsonAdapter = adapter(T::class.java)
    return adapter.toJson(value)
    }
    moshi.toJson(EmailBody("subject", “body”))
    fun ActiveCampaignAnalytics.markComplete() {
    sendEvent(completeConversationEvent)
    }
    activeCampaignAnalytics.markComplete()
    private val Intent.conversationUri: String
    get() = getStringExtra(EXTRA_CONVERSATION_URI)
    intent.conversationUri

    View full-size slide

  107. Class Delegation abstract class AbstractActivity : AppCompatActivity(),
    DisposableHandler by DisposableHandlerReal(),
    KeyboardHandler by KeyboardHandlerReal() {
    Delegation allows for code
    reuse in a manner that
    prefers composition over
    inheritance.
    engel.dev

    View full-size slide

  108. abstract class AbstractActivity : AppCompatActivity(),
    DisposableHandler by DisposableHandlerReal(),
    KeyboardHandler by KeyboardHandlerReal() {}

    View full-size slide

  109. abstract class AbstractActivity : AppCompatActivity(),
    DisposableHandler by DisposableHandlerReal(),
    KeyboardHandler by KeyboardHandlerReal() {
    override fun addDisposable(disposable: Disposable) {}
    override fun addDisposables(vararg disposables: Disposable) {}
    override fun clearDisposables() {}
    }

    View full-size slide

  110. abstract class AbstractActivity : AppCompatActivity(),
    DisposableHandler by DisposableHandlerReal(),
    KeyboardHandler by KeyboardHandlerReal() {
    override fun addDisposable(disposable: Disposable) {
    compositeDisposable.add(disposable)
    }
    override fun addDisposables(vararg disposables: Disposable) {
    compositeDisposable.addAll(*disposables)
    }
    override fun clearDisposables() {
    compositeDisposable.clear()
    }
    }

    View full-size slide

  111. abstract class AbstractActivity : AppCompatActivity(),
    DisposableHandler by DisposableHandlerReal(),
    KeyboardHandler by KeyboardHandlerReal() {}
    class DisposableHandlerReal : DisposableHandler {
    private val compositeDisposable = CompositeDisposable()
    override fun addDisposable(disposable: Disposable) {
    compositeDisposable.add(disposable)
    }
    override fun addDisposables(vararg disposables: Disposable) {
    compositeDisposable.addAll(*disposables)
    }
    override fun clearDisposables() {
    compositeDisposable.clear()
    }
    }

    View full-size slide

  112. abstract class AbstractViewModel : ViewModel(),
    DisposableHandler by DisposableHandlerReal()
    class DisposableHandlerReal : DisposableHandler {
    private val compositeDisposable = CompositeDisposable()
    override fun addDisposable(disposable: Disposable) {
    compositeDisposable.add(disposable)
    }
    override fun addDisposables(vararg disposables: Disposable) {
    compositeDisposable.addAll(*disposables)
    }
    override fun clearDisposables() {
    compositeDisposable.clear()
    }
    }

    View full-size slide

  113. abstract class AbstractActivity : AppCompatActivity(),
    DisposableHandler by DisposableHandlerReal(),
    KeyboardHandler by KeyboardHandlerReal() {}

    View full-size slide

  114. abstract class AbstractActivity : AppCompatActivity(),
    DisposableHandler by DisposableHandlerReal(),
    KeyboardHandler by KeyboardHandlerReal() {
    override fun showKeyboard(context: Context) {}
    override fun dismissKeyboard(context: Context, view: View) {}
    }

    View full-size slide

  115. abstract class AbstractActivity : AppCompatActivity(),
    DisposableHandler by DisposableHandlerReal(),
    KeyboardHandler by KeyboardHandlerReal() {
    override fun showKeyboard(context: Context) {
    val inputMethodManager = context
    .getSystemService(Context.INPUT_METHOD_SERVICE)
    inputMethodManager.toggleSoftInput(
    InputMethodManager.SHOW_FORCED,
    InputMethodManager.HIDE_IMPLICIT_ONLY
    )
    }
    override fun dismissKeyboard(context: Context, view: View) {
    val inputMethodManager = context
    .getSystemService(Context.INPUT_METHOD_SERVICE)
    inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
    }
    }

    View full-size slide

  116. abstract class AbstractActivity : AppCompatActivity(),
    DisposableHandler by DisposableHandlerReal(),
    KeyboardHandler by KeyboardHandlerReal() {}
    class KeyboardHandlerReal : KeyboardHandler {
    override fun showKeyboard(context: Context) {
    val inputMethodManager = context
    .getSystemService(Context.INPUT_METHOD_SERVICE)
    inputMethodManager.toggleSoftInput(
    InputMethodManager.SHOW_FORCED,
    InputMethodManager.HIDE_IMPLICIT_ONLY
    )
    }
    override fun dismissKeyboard(context: Context, view: View) {
    val inputMethodManager = context
    .getSystemService(Context.INPUT_METHOD_SERVICE)
    inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
    }
    }

    View full-size slide

  117. class AbstractFragment : Fragment(),
    KeyboardHandler by KeyboardHandlerReal() {}
    class KeyboardHandlerReal : KeyboardHandler {
    override fun showKeyboard(context: Context) {
    val inputMethodManager = context
    .getSystemService(Context.INPUT_METHOD_SERVICE)
    inputMethodManager.toggleSoftInput(
    InputMethodManager.SHOW_FORCED,
    InputMethodManager.HIDE_IMPLICIT_ONLY
    )
    }
    override fun dismissKeyboard(context: Context, view: View) {
    val inputMethodManager = context
    .getSystemService(Context.INPUT_METHOD_SERVICE)
    inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
    }
    }

    View full-size slide

  118. class Example {
    var property: String by Delegate()
    }
    Property Delegation

    View full-size slide

  119. class Example {
    var property: String by Delegate()
    }
    class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String)
    }

    View full-size slide

  120. class Example {
    var property: String by Delegate()
    }
    class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    println("$value has been assigned to '${property.name}' in $thisRef.")
    }
    }
    class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String)
    }

    View full-size slide

  121. val lazyInitialization by lazy {
    """
    This String Is Created When First Needed
    Then Cached And Reused In The Future
    """.trimIndent()
    }
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    println("$value has been assigned to '${property.name}' in $thisRef.")
    }
    }

    View full-size slide

  122. """.trimIndent()
    }
    /*
    * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache
    2.0 license
    * that can be found in the license/LICENSE.txt file.
    */
    @file:kotlin.jvm.JvmName("LazyKt")
    @file:kotlin.jvm.JvmMultifileClass
    package kotlin
    /**
    * Creates a new instance of the [Lazy] that uses the specified initialization function
    [initializer]
    * and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
    *
    * If the initialization of a value throws an exception, it will attempt to reinitialize
    the value at next access.
    *
    * Note that the returned instance uses itself to synchronize on. Do not synchronize from
    external code on

    View full-size slide

  123. * If the initialization of a value throws an exception, it will attempt to reinitialize
    the value at next access.
    *
    * Note that the returned instance uses itself to synchronize on. Do not synchronize from
    external code on
    * the returned instance as it may cause accidental deadlock. Also this behavior can be
    changed in the future.
    */
    public actual fun lazy(initializer: () -> T): Lazy =
    SynchronizedLazyImpl(initializer)
    /**
    * Creates a new instance of the [Lazy] that uses the specified initialization function
    [initializer]
    * and thread-safety [mode].
    *
    * If the initialization of a value throws an exception, it will attempt to reinitialize
    the value at next access.
    *
    * Note that when the [LazyThreadSafetyMode.SYNCHRONIZED] mode is specified the returned
    instance uses itself
    * to synchronize on. Do not synchronize from external code on the returned instance as
    it may cause accidental deadlock.

    View full-size slide

  124. */
    public actual fun lazy(lock: Any?, initializer: () -> T): Lazy =
    SynchronizedLazyImpl(initializer, lock)
    private class SynchronizedLazyImpl(initializer: () -> T, lock: Any? = null) :
    Lazy, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this
    override val value: T
    get() {
    val _v1 = _value
    if (_v1 !== UNINITIALIZED_VALUE) {
    @Suppress("UNCHECKED_CAST")
    return _v1 as T
    }
    return synchronized(lock) {
    val _v2 = _value
    if (_v2 !== UNINITIALIZED_VALUE) {

    View full-size slide

  125. return newValue
    }
    }
    @Suppress("UNCHECKED_CAST")
    return _value as T
    }
    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy
    value not initialized yet."
    private fun writeReplace(): Any = InitializedLazyImpl(value)
    companion object {
    private val valueUpdater =
    java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
    SafePublicationLazyImpl::class.java,
    Any::class.java,
    "_value"
    )
    }
    }

    View full-size slide

  126. Higher Order
    Functions
    fun randomerNumber(randomNumber: () -> Long): () -> Long {
    return { randomNumber() * randomNumber() }
    }
    randomerNumber { Random.nextLong() }()
    Mostly used for passing a
    function into another
    function. However you can
    return a function from a
    function too.
    engel.dev

    View full-size slide

  127. Sandwich Code
    The why behind Higher Order Functions.

    View full-size slide

  128. Boring Boring

    View full-size slide

  129. Boring Boring
    Interesting

    View full-size slide

  130. val startTime = System.currentTimeMillis()
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  131. val startTime = System.currentTimeMillis()
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  132. val startTime = System.currentTimeMillis()
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  133. val startTime = System.currentTimeMillis()
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  134. val startTime = System.currentTimeMillis()
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  135. fun benchmark(blockOfCode: () -> Unit): Long {
    val startTime = System.currentTimeMillis()
    blockOfCode.invoke()
    val endTime = System.currentTimeMillis()
    return endTime - startTime
    }
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  136. fun benchmark(blockOfCode: () -> Unit): Long {
    val startTime = System.currentTimeMillis()
    blockOfCode.invoke()
    val endTime = System.currentTimeMillis()
    return endTime - startTime
    }
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  137. fun benchmark(blockOfCode: () -> Unit): Long {
    val startTime = System.currentTimeMillis()
    blockOfCode.invoke()
    val endTime = System.currentTimeMillis()
    return endTime - startTime
    }
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  138. fun benchmark(blockOfCode: () -> Unit): Long {
    val startTime = System.currentTimeMillis()
    blockOfCode.invoke()
    val endTime = System.currentTimeMillis()
    return endTime - startTime
    }
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  139. fun benchmark(blockOfCode: () -> Unit): Long {
    val startTime = System.currentTimeMillis()
    blockOfCode()
    val endTime = System.currentTimeMillis()
    return endTime - startTime
    }
    println("Cat: ${Cat()}")
    }
    val endTime = System.currentTimeMillis()
    val runtime = endTime - startTime

    View full-size slide

  140. benchmark({
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    }
    val endTime = System.currentTimeMi
    return endTime - startTime
    }

    View full-size slide

  141. benchmark {
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    }
    val endTime = System.currentTimeMi
    return endTime - startTime
    }

    View full-size slide

  142. fun generateCats() {
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    }
    benchmark(::generateCats)
    }
    }

    View full-size slide

  143. fun generateCats() {
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    }
    benchmark(::generateCats)
    }
    }

    View full-size slide

  144. fun generateCats() {
    for (i in 0..100000) {
    println("Cat: ${Cat()}")
    }
    }
    benchmark(::generateCats)
    }
    }

    View full-size slide

  145. interface MutableListViewModel : ListViewModel {
    var dataSetChanges: ((change: DataSetChange) -> Unit)?
    fun addItem(item: T)
    fun moveItem(item: T, newPosition: Int)
    fun removeItem(item: T)
    }

    View full-size slide

  146. interface MutableListViewModel : ListViewModel {
    var dataSetChanges: ((change: DataSetChange) -> Unit)?
    fun addItem(item: T)
    fun moveItem(item: T, newPosition: Int)
    fun removeItem(item: T)
    }

    View full-size slide

  147. class MutableListViewModelDelegate(
    initialItems: List = emptyList()
    ) : MutableListViewModel {
    private val items = mutableListOf()
    .apply { addAll(initialItems) }
    override var dataSetChanges: ((change: DataSetChange) -> Unit)? = null
    override val itemCount: Int = items.size
    override fun get(position: Int): T = items[position]
    override fun addItem(item: T) {
    items.add(item)
    dataSetChanges?.invoke(DataSetChange.Inserted(items.size, 1))
    }
    override fun moveItem(item: T, newPosition: Int) {
    val from = items.indexOf(item)
    items.removeAt(from)
    items.add(newPosition, item)
    dataSetChanges?.invoke(DataSetChange.Moved(from, newPosition))
    }
    override fun removeItem(item: T) {
    val index = items.indexOf(item)
    items.removeAt(index)
    dataSetChanges?.invoke(DataSetChange.Removed(index, 1))
    }
    }

    View full-size slide

  148. class MutableListViewModelDelegate(
    initialItems: List = emptyList()
    ) : MutableListViewModel {
    private val items = mutableListOf()
    .apply { addAll(initialItems) }
    override var dataSetChanges: ((change: DataSetChange) -> Unit)? = null
    override val itemCount: Int = items.size
    override fun get(position: Int): T = items[position]
    override fun addItem(item: T) {
    items.add(item)
    dataSetChanges?.invoke(DataSetChange.Inserted(items.size, 1))
    }
    override fun moveItem(item: T, newPosition: Int) {
    val from = items.indexOf(item)
    items.removeAt(from)
    items.add(newPosition, item)
    dataSetChanges?.invoke(DataSetChange.Moved(from, newPosition))
    }
    override fun removeItem(item: T) {
    val index = items.indexOf(item)
    items.removeAt(index)
    dataSetChanges?.invoke(DataSetChange.Removed(index, 1))
    }
    }

    View full-size slide

  149. class MutableListViewModelDelegate(
    initialItems: List = emptyList()
    ) : MutableListViewModel {
    override var dataSetChanges: ((change: DataSetChange) -> Unit)? = null
    override fun addItem(item: T) {
    dataSetChanges?.invoke(DataSetChange.Inserted(items.size, 1))
    }
    override fun moveItem(item: T, newPosition: Int) {
    dataSetChanges?.invoke(DataSetChange.Moved(from, newPosition))
    }
    override fun removeItem(item: T) {
    dataSetChanges?.invoke(DataSetChange.Removed(index, 1))
    }
    }

    View full-size slide

  150. sealed class DataSetChange {
    object All : DataSetChange()
    data class Changed(val positionStart: Int, val itemCount: Int = 1) : DataSetChange()
    data class Inserted(val positionStart: Int, val itemCount: Int = 1) : DataSetChange()
    data class Moved(val from: Int, val to: Int) : DataSetChange()
    data class Removed(val positionStart: Int, val itemCount: Int = 1) : DataSetChange()
    }

    View full-size slide

  151. abstract class ListViewModelAdapter> : RecyclerView.Adapter() {
    protected abstract val listViewModel: ListViewModel
    val dataSetChanges: (change: DataSetChange) -> Unit = {
    when (it) {
    is DataSetChange.Inserted -> notifyItemRangeInserted(it.positionStart, it.itemCount)
    is DataSetChange.Moved -> notifyItemMoved(it.from, it.to)
    is DataSetChange.Removed -> notifyItemRangeRemoved(it.positionStart, it.itemCount)
    is DataSetChange.Changed -> notifyItemRangeChanged(it.positionStart, it.itemCount)
    is DataSetChange.All -> notifyDataSetChanged()
    }
    }
    override fun getItemCount(): Int = listViewModel.itemCount
    override fun onBindViewHolder(holder: VH, position: Int) {
    holder.bind(listViewModel[position])
    }
    }

    View full-size slide

  152. abstract class ListViewModelAdapter> : RecyclerView.Adapter() {
    protected abstract val listViewModel: ListViewModel
    val dataSetChanges: (change: DataSetChange) -> Unit = {
    when (it) {
    is DataSetChange.Inserted -> notifyItemRangeInserted(it.positionStart, it.itemCount)
    is DataSetChange.Moved -> notifyItemMoved(it.from, it.to)
    is DataSetChange.Removed -> notifyItemRangeRemoved(it.positionStart, it.itemCount)
    is DataSetChange.Changed -> notifyItemRangeChanged(it.positionStart, it.itemCount)
    is DataSetChange.All -> notifyDataSetChanged()
    }
    }
    override fun getItemCount(): Int = listViewModel.itemCount
    override fun onBindViewHolder(holder: VH, position: Int) {
    holder.bind(listViewModel[position])
    }
    }

    View full-size slide

  153. abstract class ListViewModelAdapter>
    : RecyclerView.Adapter() {
    val dataSetChanges: (change: DataSetChange) -> Unit = {
    when (it) {
    is DataSetChange.Inserted ->
    notifyItemRangeInserted(it.positionStart, it.itemCount)
    is DataSetChange.Moved ->
    notifyItemMoved(it.from, it.to)
    is DataSetChange.Removed ->
    notifyItemRangeRemoved(it.positionStart, it.itemCount)
    is DataSetChange.Changed ->
    notifyItemRangeChanged(it.positionStart, it.itemCount)
    is DataSetChange.All ->
    notifyDataSetChanged()
    }
    }
    }

    View full-size slide

  154. viewModelStub.dataSetChanges = adapter.dataSetChanges

    View full-size slide

  155. Unit Testing
    If you don’t want to ship
    Kotlin to production, then
    start with your CI/CD
    process.
    class LoginViewModelTest : ViewModelTest() {
    @MockK
    private lateinit var authenticationRepository: Authenti
    @MockK
    private lateinit var stringLoader: StringLoader
    @MockK
    private lateinit var telemetry: Telemetry
    @RelaxedMockK
    private lateinit var analytics: ActiveCampaignAnalytics
    private lateinit var subject: LoginViewModel
    private val viewStateAssertion = ViewStateAssertionprivate val accountTextChanges by lazy { EventObservabl
    private val usernameTextChanges by lazy { EventObservab
    private val passwordTextChanges by lazy { EventObservab
    private val domainTextChanges by lazy { EventObservable
    private val loginClicks by lazy { EventObservable(
    private val textChange by lazy { randomString() }
    private val errorMessage by lazy { randomString() }
    private val genericErrorMessage by lazy { randomString(
    private val invalidRequestErrorMessage by lazy { random
    private val noPermissionsErrorMessage by lazy { randomS
    engel.dev

    View full-size slide

  156. class LoginViewModelTest : ViewModelTest() {
    @MockK
    private lateinit var authenticationRepository: AuthenticationRepository
    @MockK
    private lateinit var stringLoader: StringLoader
    @MockK
    private lateinit var telemetry: Telemetry
    @RelaxedMockK
    private lateinit var analytics: ActiveCampaignAnalytics
    private lateinit var subject: LoginViewModel
    private val viewStateAssertion = ViewStateAssertion()
    private val accountTextChanges by lazy { EventObservable() }
    private val usernameTextChanges by lazy { EventObservable() }
    private val passwordTextChanges by lazy { EventObservable() }
    private val domainTextChanges by lazy { EventObservable() }
    private val loginClicks by lazy { EventObservable() }
    private val textChange by lazy { randomString() }
    private val errorMessage by lazy { randomString() }
    private val genericErrorMessage by lazy { randomString() }
    private val invalidRequestErrorMessage by lazy { randomString() }
    private val noPermissionsErrorMessage by lazy { randomString() }
    private val accountExpiredErrorMessage by lazy { randomString() }
    private val trace by lazy { mockk() }

    View full-size slide

  157. ss LoginViewModelTest : ViewModelTest() {
    @MockK
    private lateinit var authenticationRepository: AuthenticationRepository
    @MockK
    private lateinit var stringLoader: StringLoader
    @MockK
    private lateinit var telemetry: Telemetry
    @RelaxedMockK
    private lateinit var analytics: ActiveCampaignAnalytics
    private lateinit var subject: LoginViewModel
    private val viewStateAssertion = ViewStateAssertion()
    private val accountTextChanges by lazy { EventObservable() }
    private val usernameTextChanges by lazy { EventObservable() }
    private val passwordTextChanges by lazy { EventObservable() }
    private val domainTextChanges by lazy { EventObservable() }
    private val loginClicks by lazy { EventObservable() }
    private val textChange by lazy { randomString() }
    private val errorMessage by lazy { randomString() }
    private val genericErrorMessage by lazy { randomString() }
    private val invalidRequestErrorMessage by lazy { randomString() }
    private val noPermissionsErrorMessage by lazy { randomString() }
    private val accountExpiredErrorMessage by lazy { randomString() }
    private val trace by lazy { mockk() }
    MockKAnnotations.init(this)

    View full-size slide

  158. private val viewStateAssertion = ViewStateAssertion()
    private val accountTextChanges by lazy { EventObservable() }
    private val usernameTextChanges by lazy { EventObservable() }
    private val passwordTextChanges by lazy { EventObservable() }
    private val domainTextChanges by lazy { EventObservable() }
    private val loginClicks by lazy { EventObservable() }
    private val textChange by lazy { randomString() }
    private val errorMessage by lazy { randomString() }
    private val genericErrorMessage by lazy { randomString() }
    private val invalidRequestErrorMessage by lazy { randomString() }
    private val noPermissionsErrorMessage by lazy { randomString() }
    private val accountExpiredErrorMessage by lazy { randomString() }
    private val trace by lazy { mockk() }
    private fun prepareTelemetry() {
    every { telemetry.startTrace(any()) } returns trace
    every { trace.putAttribute(any(), any()) } just Runs
    every { telemetry.endTrace(trace) } just Runs
    }

    View full-size slide

  159. private val viewStateAssertion = ViewStateAssertion()
    private val accountTextChanges by lazy { EventObservable() }
    private val usernameTextChanges by lazy { EventObservable() }
    private val passwordTextChanges by lazy { EventObservable() }
    private val domainTextChanges by lazy { EventObservable() }
    private val loginClicks by lazy { EventObservable() }
    private val textChange by lazy { randomString() }
    private val errorMessage by lazy { randomString() }
    private val genericErrorMessage by lazy { randomString() }
    private val invalidRequestErrorMessage by lazy { randomString() }
    private val noPermissionsErrorMessage by lazy { randomString() }
    private val accountExpiredErrorMessage by lazy { randomString() }
    private val trace by lazy { mockk() }
    private fun prepareTelemetry() {
    every { telemetry.startTrace(any()) } returns trace
    every { trace.putAttribute(any(), any()) } just Runs
    every { telemetry.endTrace(trace) } just Runs
    }

    View full-size slide

  160. private val viewStateAssertion = ViewStateAssertion()
    private val accountTextChanges by lazy { EventObservable() }
    private val usernameTextChanges by lazy { EventObservable() }
    private val passwordTextChanges by lazy { EventObservable() }
    private val domainTextChanges by lazy { EventObservable() }
    private val loginClicks by lazy { EventObservable() }
    private val textChange by lazy { randomString() }
    private val errorMessage by lazy { randomString() }
    private val genericErrorMessage by lazy { randomString() }
    private val invalidRequestErrorMessage by lazy { randomString() }
    private val noPermissionsErrorMessage by lazy { randomString() }
    private val accountExpiredErrorMessage by lazy { randomString() }
    private val trace by lazy { mockk() }
    private fun prepareTelemetry() {
    every { telemetry.startTrace(any()) } returns trace
    every { trace.putAttribute(any(), any()) } just Runs
    every { telemetry.endTrace(trace) } just Runs
    }

    View full-size slide

  161. = ViewStateAssertion()
    by lazy { EventObservable() }
    s by lazy { EventObservable() }
    s by lazy { EventObservable() }
    by lazy { EventObservable() }
    y { EventObservable() }
    { randomString() }
    zy { randomString() }
    e by lazy { randomString() }
    rMessage by lazy { randomString() }
    Message by lazy { randomString() }
    rMessage by lazy { randomString() }
    ckk() }
    {
    ce(any()) } returns trace
    (any(), any()) } just Runs
    (trace) } just Runs
    /**
    * Generates a random string in the form of a [UUID].
    */
    fun randomString() = UUID.randomUUID().toString()

    View full-size slide

  162. @Test
    fun `initial state should have loginSuccessful set to false`() {
    viewStateAssertion.assertFirstViewState {
    assertThat(it.loginSuccessful).isFalse()
    }
    }
    @Test
    fun `initial state should have loginEnabled set to false`() {
    viewStateAssertion.assertFirstViewState {
    assertThat(it.loginEnabled).isFalse()
    }
    }
    @Test
    fun `initial state should have loginForm set to empty values`() {
    viewStateAssertion.assertFirstViewState {
    assertThat(it.loginForm).isEqualTo(LoginViewModel.LoginForm())
    }
    }

    View full-size slide

  163. @Test
    fun `initial state should have loginSuccessful set to false`() {
    viewStateAssertion.assertFirstViewState {
    assertThat(it.loginSuccessful).isFalse()
    }
    }
    @Test
    fun `initial state should have loginEnabled set to false`() {
    viewStateAssertion.assertFirstViewState {
    assertThat(it.loginEnabled).isFalse()
    }
    }
    @Test
    fun `initial state should have loginForm set to empty values`() {
    viewStateAssertion.assertFirstViewState {
    assertThat(it.loginForm).isEqualTo(LoginViewModel.LoginForm())
    }
    }

    View full-size slide

  164. statefulModel.state.removeObserver(currentObserver!!)
    }
    currentObserver = null
    }
    fun assertLastViewState(assertion: (actualState: S) -> Unit) {
    assertViewStateSeries { actualStates ->
    assertion(actualStates.last())
    }
    }
    fun assertFirstViewState(assertion: (actualState: S) -> Unit) {
    assertViewStateSeries { actualStates ->
    assertion(actualStates.first())
    }
    }
    fun assertViewStateSeries(assertion: (actualStates: List) -> Unit) {
    assertion(viewStates)
    }
    }

    View full-size slide

  165. statefulModel.state.removeObserver(currentObserver!!)
    }
    currentObserver = null
    }
    fun assertLastViewState(assertion: (actualState: S) -> Unit) {
    assertViewStateSeries { actualStates ->
    assertion(actualStates.last())
    }
    }
    fun assertFirstViewState(assertion: (actualState: S) -> Unit) {
    assertViewStateSeries { actualStates ->
    assertion(actualStates.first())
    }
    }
    fun assertViewStateSeries(assertion: (actualStates: List) -> Unit) {
    assertion(viewStates)
    }
    }

    View full-size slide

  166. class ViewStateAssertion {
    private val viewStates = mutableListOf()
    private var currentObserver: Observer? = null
    private lateinit var statefulModel: StatefulModel
    val numberOfStates: Int
    get() = viewStates.size
    fun start(statefulModel: StatefulModel) {
    this.statefulModel = statefulModel
    currentObserver = Observer { viewStates.add(it!!) }
    try {
    viewStates.clear()
    } catch (ex: Exception) {
    Assert.fail("Unable to complete ViewStateAssertion.start(). The followin
    }
    this.statefulModel.state.observeForever(currentObserver!!)
    }

    View full-size slide

  167. @Test
    fun `accountTextChanges should update the loginForm with new account value`() {
    prepareForFormInteractions()
    accountTextChanges.updateValue(textChange)
    viewStateAssertion.assertLastViewState {
    assertThat(it.loginForm.account).isEqualTo(textChange)
    }
    }
    @Test
    fun `usernameTextChanges should update the loginForm with new username value`() {
    prepareForFormInteractions()
    usernameTextChanges.updateValue(textChange)
    viewStateAssertion.assertLastViewState {
    assertThat(it.loginForm.username).isEqualTo(textChange)
    }
    }

    View full-size slide

  168. }
    this.statefulModel.state.observeForever(currentObserver!!)
    }
    fun end() {
    if (currentObserver != null) {
    statefulModel.state.removeObserver(currentObserver!!)
    }
    currentObserver = null
    }
    fun assertLastViewState(assertion: (actualState: S) -> Unit) {
    assertViewStateSeries { actualStates ->
    assertion(actualStates.last())
    }
    }
    fun assertFirstViewState(assertion: (actualState: S) -> Unit) {
    assertViewStateSeries { actualStates ->
    assertion(actualStates.first())
    }
    }

    View full-size slide

  169. @Test
    fun `accountTextChanges should update the loginForm with new account value`() {
    prepareForFormInteractions()
    accountTextChanges.updateValue(textChange)
    viewStateAssertion.assertLastViewState {
    assertThat(it.loginForm.account).isEqualTo(textChange)
    }
    }
    @Test
    fun `usernameTextChanges should update the loginForm with new username value`() {
    prepareForFormInteractions()
    usernameTextChanges.updateValue(textChange)
    viewStateAssertion.assertLastViewState {
    assertThat(it.loginForm.username).isEqualTo(textChange)
    }
    }

    View full-size slide

  170. Looking Ahead
    Kotlin is just getting started.

    View full-size slide

  171. Coroutines
    It’s like multi-threading without overhead and imperative.

    View full-size slide

  172. Contracts
    What if your functions could provide visibility into what they infer internally?

    View full-size slide

  173. Inline Classes
    Give meaning to things like Strings and Ints without overhead.

    View full-size slide

  174. Kotlin/Native
    Kotlin isn’t bound to the JVM.

    View full-size slide

  175. Questions?
    Let’s talk after or message me on engel.dev

    View full-size slide