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

Pragmatic Kotlin

9ab0b3b080e75e0c03a0c643333f8b93?s=47 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

9ab0b3b080e75e0c03a0c643333f8b93?s=128

Segun Famisa

September 26, 2019
Tweet

Transcript

  1. Pragmatic Kotlin Android Chapter September, 2019

  2. segunfamisa segunfamisa.com

  3. Introduction

  4. typealiases

  5. typealiases

  6. typealiases • Nicknames for regular existing types

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

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

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

    #1
  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
  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
  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
  13. // before fun applyDiscount(price: Double, discount: Double): Double // fix

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

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

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

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

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

    #3 // create typealias typealias Percentage = Double // usage fun applyDiscount(price: Double, discount: Percentage): Double
  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
  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
  21. typealias • Improves readability at no performance cost • Does

    not create a new type • Allows for easy refactoring
  22. inline classes

  23. inline classes • New in Kotlin 1.3 • Still experimental

    • Useful for creating “wrappers” for primitive types
  24. // before fun applyDiscount(price: Double, discount: Double): Double = (100.0

    - discount) / 100.0 * price
  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?
  26. // before fun applyDiscount(price: Double, discount: Double): Double // wrapper

    classes class Price(val value: Double) class Percentage(val value: Double)
  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
  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
  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
  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
  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
  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
  33. // wrapper classes inline class Price(val value: Double) inline class

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

  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); }
  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); }
  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!
  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); }
  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
  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”
  41. Another example • Android Resource wrapper inline class StringResource(@StringRes val

    resId: Int)
  42. inline classes typealiases

  43. inline classes typealiases No new type is introduced

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

    is introduced
  45. inline classes typealiases Can assign typealias to underlying type: typealias

    Password = String fun login(password: String) val pwd: Password = "kisHuy8q172" login(password = pwd)
  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)
  47. property delegates

  48. property delegates • Special kinds of properties that are implemented

    differently • E.g lazy properties, observable properties etc.
  49. Lazy Initialized only once.

  50. fun computeExpensiveStringThatDoesntChange() = "stringggg" val expensiveValue : String by lazy

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

    { println("Log expensiveValue created") computeExpensiveStringThatDoesntChange() } // Usage println(expensiveValue) println(expensiveValue)
  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"
  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
  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
  55. Another sample - viewModel creation class MyActivity : Activity {

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

    @Inject lateinit var viewModelFactory: ViewModelFactory<MyViewModel> private val viewModel by viewModels<MyViewModel> { viewModelFactory } } implementation "androidx.activity:activity-ktx:$libs.ktxActivityVersion"
  57. class ViewModelLazy<VM : ViewModel> ( private val viewModelClass: KClass<VM>, private

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

    val storeProducer: () -> ViewModelStore, private val factoryProducer: () -> ViewModelProvider.Factory ) : Lazy<VM> { }
  59. class ViewModelLazy<VM : ViewModel> ( ... ) : Lazy<VM> {

    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 }
  60. Putting it all together: inline fun <reified VM : ViewModel>

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

    ComponentActivity.viewModels( noinline factoryProducer: (() -> Factory)? = null ): Lazy<VM> { val factoryPromise = factoryProducer ?: { val application = application ?: throw IllegalArgumentException() AndroidViewModelFactory.getInstance(application) } return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise) } Extension function to make access easier!
  62. Observable fields Get notified whenever value changes. Need to supply

    initial value
  63. Sample usage - recyclerview adapter update & diffutil https://gist.github.com/segunfamisa/716401e58ead9e6eb1c848a51451f927 class

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

    @Inject constructor() : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() { var items: List<Item> by observableList(initialValue = listOf()) ... }
  65. class ObservableListField<T, VH : RecyclerView.ViewHolder>( private val adapter: RecyclerView.Adapter<VH>, private

    val diffUtilCallbackProducer: (List<T>, List<T>) -> DiffUtil.Callback, initialValue: List<T> ) : ObservableProperty<List<T>>(initialValue = initialValue)
  66. class ObservableListField<T, VH : RecyclerView.ViewHolder>( private val adapter: RecyclerView.Adapter<VH>, private

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

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

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

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

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

    List<T>) -> DiffUtil.Callback) ): ObservableProperty<List<T>> { return ObservableListField( adapter = this, diffUtilCallbackProducer = diffUtilCallbackProducer, initialValue = initialValue ) } Putting it all together!
  72. More Kotlin features • Generics • Kotlin native • Annotations

  73. Resources

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

  75. Thank you!