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

Pragmatic Kotlin

Segun Famisa
September 26, 2019

Pragmatic Kotlin

Talk given at Kotlin Everywhere Berlin, Android Chapter in September 2019.

Event:
https://www.eventbrite.com/e/kotlin-everywhere-android-chapter-tickets-70491789841

Summary:

Kotlin promises a lot of features, and while some features (like data classes, lambda functions, etc.) have gained more popularity than others, there are some features that are not so widely used.

In this talk, we will look at a couple of nice features that are not very widely used. Some of the features we will look at include typealiases and inline classes as well as property delegations and others and their practical applications to Android development. Wherever it applies, we will also discuss the technical trade-offs to consider when using these features.

At the end of the talk, we would have touched on some of these nice features, and how we can immediately start applying in our codebase to get instant benefits.

Resources:

https://kotlinlang.org/docs/reference/
https://gist.github.com/segunfamisa/716401e58ead9e6eb1c848a51451f927
https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/activity/activity-ktx/src/main/java/androidx/activity/ActivityViewModelLazy.kt

Segun Famisa

September 26, 2019
Tweet

More Decks by Segun Famisa

Other Decks in Programming

Transcript

  1. Pragmatic Kotlin
    Android Chapter
    September, 2019

    View Slide

  2. segunfamisa
    segunfamisa.com

    View Slide

  3. Introduction

    View Slide

  4. typealiases

    View Slide

  5. typealiases

    View Slide

  6. typealiases
    ● Nicknames for regular existing types

    View Slide

  7. // before
    fun applyDiscount(price: Double, discount: Double): Double =
    (100.0 - discount) / 100.0 * price

    View Slide

  8. // before
    fun applyDiscount(price: Double, discount: Double): Double =
    (100.0 - discount) / 100.0 * price
    Discount in €? Cents?
    Or Percentage??

    View Slide

  9. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #1

    View Slide

  10. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #1
    /**
    * @param price - original price in Euros
    * @param discount - discount in percentage
    */
    fun applyDiscount(price: Double, discount: Double): Double

    View Slide

  11. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #1
    /**
    * @param price - original price in Euros
    * @param discount - discount in percentage
    */
    fun applyDiscount(price: Double, discount: Double): Double
    KDoc comment

    View Slide

  12. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #1
    /**
    * @param price - original price in Euros
    * @param discount - discount in percentage
    */
    fun applyDiscount(price: Double, discount: Double): Double
    KDoc comment

    View Slide

  13. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #2

    View Slide

  14. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #2
    fun applyDiscount(price: Double, discountInPercentage: Double): Double

    View Slide

  15. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #2
    fun applyDiscount(price: Double, discountInPercentage: Double): Double
    Clearer param name

    View Slide

  16. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #3
    // create typealias
    typealias Percentage = Double

    View Slide

  17. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #3
    // create typealias
    typealias Percentage = Double
    // usage
    fun applyDiscount(price: Double, discount: Percentage): Double

    View Slide

  18. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #3
    // create typealias
    typealias Percentage = Double
    // usage
    fun applyDiscount(price: Double, discount: Percentage): Double

    View Slide

  19. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #3
    // create typealias
    typealias Percentage = Double
    // usage
    fun applyDiscount(price: Double, discount: Percentage): Double
    ● Short parameter name
    ● “type” of discount is clear
    ● No new type created

    View Slide

  20. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // fix #3
    // create typealias
    typealias Percentage = Double
    // usage
    fun applyDiscount(price: Double, discount: Percentage): Double
    ● Short parameter name
    ● “type” of discount is clear
    ● No new type created

    View Slide

  21. typealias
    ● Improves readability at no performance cost
    ● Does not create a new type
    ● Allows for easy refactoring

    View Slide

  22. inline classes

    View Slide

  23. inline classes
    ● New in Kotlin 1.3
    ● Still experimental
    ● Useful for creating “wrappers” for primitive types

    View Slide

  24. // before
    fun applyDiscount(price: Double, discount: Double): Double =
    (100.0 - discount) / 100.0 * price

    View Slide

  25. // before
    fun applyDiscount(price: Double, discount: Double): Double =
    (100.0 - discount) / 100.0 * price
    Same problems as before
    ● Easy to mess up the order of the
    parameters
    ● Not clear what discount is - in Euros or
    in percentage?

    View Slide

  26. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // wrapper classes
    class Price(val value: Double)
    class Percentage(val value: Double)

    View Slide

  27. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // wrapper classes
    class Price(val value: Double)
    class Percentage(val value: Double)
    // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price

    View Slide

  28. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // wrapper classes
    class Price(val value: Double)
    class Percentage(val value: Double)
    // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price

    View Slide

  29. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // wrapper classes
    class Price(val value: Double)
    class Percentage(val value: Double)
    // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price
    ● Less error-prone
    ● Clear intentions/expectations

    View Slide

  30. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // wrapper classes
    class Price(val value: Double)
    class Percentage(val value: Double)
    // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price
    ● Additional memory allocation

    View Slide

  31. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // wrapper classes
    class Price(val value: Double)
    class Percentage(val value: Double)
    // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price
    ● Additional memory allocation

    View Slide

  32. // before
    fun applyDiscount(price: Double, discount: Double): Double
    // wrapper classes
    inline class Price(val value: Double)
    inline class Percentage(val value: Double)
    // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price

    View Slide

  33. // wrapper classes
    inline class Price(val value: Double)
    inline class Percentage(val value: Double)
    // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price

    View Slide

  34. // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price

    View Slide

  35. // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price
    // Generated Java code
    public final double discountedPrice_lwIBXQA(
    double price,
    double discount
    ) {
    return Price.constructor-impl((100.0D - discount) / 100.0D * price);
    }

    View Slide

  36. // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price
    // Generated Java code
    public final double discountedPrice_lwIBXQA(
    double price,
    double discount
    ) {
    return Price.constructor-impl((100.0D - discount) / 100.0D * price);
    }

    View Slide

  37. // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price
    // Generated Java code
    public final double discountedPrice_lwIBXQA(
    double price,
    double discount
    ) {
    return Price.constructor-impl((100.0D - discount) / 100.0D * price);
    }
    Inline class variables are resolved to
    the primitive type they wrap!

    View Slide

  38. // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price
    // Generated Java code
    public final double discountedPrice_lwIBXQA(
    double price,
    double discount
    ) {
    return Price.constructor-impl((100.0D - discount) / 100.0D * price);
    }

    View Slide

  39. // Usage
    fun applyDiscount(price: Price, discount: Percentage): Price
    // Generated Java code
    public final double discountedPrice_lwIBXQA(
    double price,
    double discount
    ) {
    return Price.constructor-impl((100.0D - discount) / 100.0D * price);
    }
    “-” is not a valid character for functions in Java,
    so for now, inline classes don’t work in Java

    View Slide

  40. inline classes
    ● Don’t work with Java for now
    ● Kotlin compiler is not always able to inline
    ● inline classes cannot have more than one “param”

    View Slide

  41. Another example
    ● Android Resource wrapper
    inline class StringResource(@StringRes val resId: Int)

    View Slide

  42. inline classes
    typealiases

    View Slide

  43. inline classes
    typealiases
    No new type is introduced

    View Slide

  44. inline classes
    New type is introduced
    typealiases
    No new type is introduced

    View Slide

  45. inline classes
    typealiases
    Can assign typealias to
    underlying type:
    typealias Password = String
    fun login(password: String)
    val pwd: Password = "kisHuy8q172"
    login(password = pwd)

    View Slide

  46. inline classes
    Cannot assign inline class
    to the wrapped type:
    inline class Password(val value: String)
    fun login(password: String)
    val pwd = Password("kisHuy8q172")
    login(password = pwd) // won't compile
    typealiases
    Can assign typealias to
    underlying type:
    typealias Password = String
    fun login(password: String)
    val pwd: Password = "kisHuy8q172"
    login(password = pwd)

    View Slide

  47. property delegates

    View Slide

  48. property delegates
    ● Special kinds of properties that are implemented
    differently
    ● E.g lazy properties, observable properties etc.

    View Slide

  49. Lazy
    Initialized only once.

    View Slide

  50. fun computeExpensiveStringThatDoesntChange() = "stringggg"
    val expensiveValue : String by lazy {
    println("Log expensiveValue created")
    computeExpensiveStringThatDoesntChange()
    }

    View Slide

  51. fun computeExpensiveStringThatDoesntChange() = "stringggg"
    val expensiveValue : String by lazy {
    println("Log expensiveValue created")
    computeExpensiveStringThatDoesntChange()
    }
    // Usage
    println(expensiveValue)
    println(expensiveValue)

    View Slide

  52. fun computeExpensiveStringThatDoesntChange() = "stringggg"
    val expensiveValue : String by lazy {
    println("Log expensiveValue created")
    computeExpensiveStringThatDoesntChange()
    }
    // Usage
    println(expensiveValue)
    println(expensiveValue)
    // Output
    "Log expensiveValue created"
    "stringggg"
    "stringggg"

    View Slide

  53. fun computeExpensiveStringThatDoesntChange() = "stringggg"
    val expensiveValue : String by lazy {
    println("Log expensiveValue created")
    computeExpensiveStringThatDoesntChange()
    }
    // Usage
    println(expensiveValue)
    println(expensiveValue)
    // Output
    "Log expensiveValue created"
    "stringggg"
    "stringggg"
    First access to the variable prints
    this

    View Slide

  54. fun computeExpensiveStringThatDoesntChange() = "stringggg"
    val expensiveValue : String by lazy {
    println("Log expensiveValue created")
    computeExpensiveStringThatDoesntChange()
    }
    // Usage
    println(expensiveValue)
    println(expensiveValue)
    // Output
    "Log expensiveValue created"
    "stringggg"
    "stringggg" Subsequent access doesn’t go into the lazy block

    View Slide

  55. Another sample - viewModel creation
    class MyActivity : Activity {
    @Inject
    lateinit var viewModelFactory: ViewModelFactory
    private val viewModel by viewModels {
    viewModelFactory
    }
    }

    View Slide

  56. Another sample - viewModel creation
    class MyActivity : Activity {
    @Inject
    lateinit var viewModelFactory: ViewModelFactory
    private val viewModel by viewModels {
    viewModelFactory
    }
    }
    implementation "androidx.activity:activity-ktx:$libs.ktxActivityVersion"

    View Slide

  57. class ViewModelLazy (
    private val viewModelClass: KClass,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory
    ) : Lazy {
    }

    View Slide

  58. class ViewModelLazy (
    private val viewModelClass: KClass,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory
    ) : Lazy {
    }

    View Slide

  59. class ViewModelLazy (
    ...
    ) : Lazy {
    private var cached: VM? = null
    override val value: VM
    get() {
    val viewModel = cached
    return if (viewModel == null) {
    val factory = factoryProducer()
    val store = storeProducer()
    ViewModelProvider(store,factory)
    .get(viewModelClass.java).also { cached = it }
    } else {
    viewModel
    }
    }
    override fun isInitialized() = cached != null
    }

    View Slide

  60. Putting it all together:
    inline fun ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
    ): Lazy {
    val factoryPromise = factoryProducer ?: {
    val application = application ?: throw IllegalArgumentException()
    AndroidViewModelFactory.getInstance(application)
    }
    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
    }

    View Slide

  61. Putting it all together:
    inline fun ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
    ): Lazy {
    val factoryPromise = factoryProducer ?: {
    val application = application ?: throw IllegalArgumentException()
    AndroidViewModelFactory.getInstance(application)
    }
    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
    }
    Extension function to make access easier!

    View Slide

  62. Observable fields
    Get notified whenever value changes.
    Need to supply initial value

    View Slide

  63. Sample usage - recyclerview adapter update & diffutil
    https://gist.github.com/segunfamisa/716401e58ead9e6eb1c848a51451f927
    class ItemAdapter @Inject constructor() :
    RecyclerView.Adapter() {
    var items: List by observableList(initialValue = listOf())
    ...
    }

    View Slide

  64. Sample usage - recyclerview adapter update & diffutil
    class ItemAdapter @Inject constructor() :
    RecyclerView.Adapter() {
    var items: List by observableList(initialValue = listOf())
    ...
    }

    View Slide

  65. class ObservableListField(
    private val adapter: RecyclerView.Adapter,
    private val diffUtilCallbackProducer: (List, List) -> DiffUtil.Callback,
    initialValue: List
    ) : ObservableProperty>(initialValue = initialValue)

    View Slide

  66. class ObservableListField(
    private val adapter: RecyclerView.Adapter,
    private val diffUtilCallbackProducer: (List, List) -> DiffUtil.Callback,
    initialValue: List
    ) : ObservableProperty>(initialValue = initialValue) {
    override fun beforeChange(
    property: KProperty,
    oldValue: List,
    newValue: List
    ): Boolean {
    return true
    }
    override fun afterChange(property: KProperty, oldValue: List, newValue: List) {
    super.afterChange(property, oldValue, newValue)
    }
    }

    View Slide

  67. class ObservableListField(
    private val adapter: RecyclerView.Adapter,
    private val diffUtilCallbackProducer: (List, List) -> DiffUtil.Callback,
    initialValue: List
    ) : ObservableProperty>(initialValue = initialValue) {
    private var result: DiffUtil.DiffResult? = null
    override fun beforeChange(
    property: KProperty,
    oldValue: List,
    newValue: List
    ): Boolean {
    val diffUtilCallback = diffUtilCallbackProducer?.invoke(oldValue, newValue)
    result = DiffUtil.calculateDiff(diffUtilCallback)
    return true
    }
    override fun afterChange(property: KProperty, oldValue: List, newValue: List) {
    result?.dispatchUpdatesTo(adapter)
    super.afterChange(property, oldValue, newValue)
    }
    }

    View Slide

  68. class ObservableListField(
    private val adapter: RecyclerView.Adapter,
    private val diffUtilCallbackProducer: (List, List) -> DiffUtil.Callback,
    initialValue: List
    ) : ObservableProperty>(initialValue = initialValue) {
    private var result: DiffUtil.DiffResult? = null
    override fun beforeChange(
    property: KProperty,
    oldValue: List,
    newValue: List
    ): Boolean {
    val diffUtilCallback = diffUtilCallbackProducer?.invoke(oldValue, newValue)
    result = DiffUtil.calculateDiff(diffUtilCallback)
    return true
    }
    override fun afterChange(property: KProperty, oldValue: List, newValue: List) {
    result?.dispatchUpdatesTo(adapter)
    super.afterChange(property, oldValue, newValue)
    }
    }

    View Slide

  69. class ObservableListField(
    private val adapter: RecyclerView.Adapter,
    private val diffUtilCallbackProducer: (List, List) -> DiffUtil.Callback,
    initialValue: List
    ) : ObservableProperty>(initialValue = initialValue) {
    private var result: DiffUtil.DiffResult? = null
    override fun beforeChange(
    property: KProperty,
    oldValue: List,
    newValue: List
    ): Boolean {
    val diffUtilCallback = diffUtilCallbackProducer?.invoke(oldValue, newValue)
    result = DiffUtil.calculateDiff(diffUtilCallback)
    return true
    }
    override fun afterChange(property: KProperty, oldValue: List, newValue: List) {
    result?.dispatchUpdatesTo(adapter)
    super.afterChange(property, oldValue, newValue)
    }
    }

    View Slide

  70. class ObservableListField(
    private val adapter: RecyclerView.Adapter,
    private val diffUtilCallbackProducer: (List, List) -> DiffUtil.Callback,
    initialValue: List
    ) : ObservableProperty>(initialValue = initialValue) {
    private var result: DiffUtil.DiffResult? = null
    override fun beforeChange(
    property: KProperty,
    oldValue: List,
    newValue: List
    ): Boolean {
    val diffUtilCallback = diffUtilCallbackProducer?.invoke(oldValue, newValue)
    result = DiffUtil.calculateDiff(diffUtilCallback)
    return true
    }
    override fun afterChange(property: KProperty, oldValue: List, newValue: List) {
    result?.dispatchUpdatesTo(adapter)
    super.afterChange(property, oldValue, newValue)
    }
    }

    View Slide

  71. fun RecyclerView.Adapter.observableList(
    initialValue: List,
    diffUtilCallbackProducer: ((List, List) -> DiffUtil.Callback)
    ): ObservableProperty> {
    return ObservableListField(
    adapter = this,
    diffUtilCallbackProducer = diffUtilCallbackProducer,
    initialValue = initialValue
    )
    }
    Putting it all together!

    View Slide

  72. More Kotlin features
    ● Generics
    ● Kotlin native
    ● Annotations

    View Slide

  73. Resources

    View Slide

  74. Resources
    https://kotlinlang.org/docs/reference/
    https://gist.github.com/segunfamisa/716401e58ead9e6eb1c848a51451f927
    AndroidX Source

    View Slide

  75. Thank you!

    View Slide