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

    - discount) / 100.0 * price Discount in €? Cents? Or Percentage??
  2. // 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
  3. // 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
  4. // 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
  5. // before fun applyDiscount(price: Double, discount: Double): Double // fix

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

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

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

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

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

    not create a new type • Allows for easy refactoring
  13. inline classes • New in Kotlin 1.3 • Still experimental

    • Useful for creating “wrappers” for primitive types
  14. // 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?
  15. // before fun applyDiscount(price: Double, discount: Double): Double // wrapper

    classes class Price(val value: Double) class Percentage(val value: Double)
  16. // 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
  17. // 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
  18. // 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
  19. // 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
  20. // 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
  21. // 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
  22. // wrapper classes inline class Price(val value: Double) inline class

    Percentage(val value: Double) // Usage fun applyDiscount(price: Price, discount: Percentage): Price
  23. // 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); }
  24. // 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); }
  25. // 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!
  26. // 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); }
  27. // 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
  28. 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”
  29. inline classes typealiases Can assign typealias to underlying type: typealias

    Password = String fun login(password: String) val pwd: Password = "kisHuy8q172" login(password = pwd)
  30. 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)
  31. property delegates • Special kinds of properties that are implemented

    differently • E.g lazy properties, observable properties etc.
  32. fun computeExpensiveStringThatDoesntChange() = "stringggg" val expensiveValue : String by lazy

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

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

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

    @Inject lateinit var viewModelFactory: ViewModelFactory<MyViewModel> private val viewModel by viewModels<MyViewModel> { viewModelFactory } }
  38. 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"
  39. class ViewModelLazy<VM : ViewModel> ( private val viewModelClass: KClass<VM>, private

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

    val storeProducer: () -> ViewModelStore, private val factoryProducer: () -> ViewModelProvider.Factory ) : Lazy<VM> { }
  41. 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 }
  42. 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) }
  43. 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!
  44. 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()) ... }
  45. Sample usage - recyclerview adapter update & diffutil class ItemAdapter

    @Inject constructor() : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() { var items: List<Item> by observableList(initialValue = listOf()) ... }
  46. 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)
  47. 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) } }
  48. 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) } }
  49. 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) } }
  50. 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) } }
  51. 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) } }
  52. 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!