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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 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 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 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 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 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 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 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 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 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 Slide

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

    View Slide

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

    View 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 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 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 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 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 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View 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 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 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 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 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 Slide

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

    View 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 Slide

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

    View 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 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 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 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 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 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Slide

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

    View 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 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 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 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 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 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 Slide

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

    View 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 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 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 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 Slide

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

    View 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 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 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 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 Slide

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

    View 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 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 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 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 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 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 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 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 Slide

  127. Sandwich Code
    The why behind Higher Order Functions.

    View Slide

  128. View Slide

  129. View Slide

  130. View Slide

  131. View Slide

  132. View Slide

  133. Boring Boring

    View Slide

  134. Boring Boring
    Interesting

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

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

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

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

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

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

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

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

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

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

  159. viewModelStub.dataSetChanges = adapter.dataSetChanges

    View Slide

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

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

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

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

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

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

  166. = 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 Slide

  167. @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 Slide

  168. @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 Slide

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

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

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

  172. @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 Slide

  173. }
    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 Slide

  174. @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 Slide

  175. Looking Ahead
    Kotlin is just getting started.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide