Slide 1

Slide 1 text

Pragmatic Kotlin Android Chapter September, 2019

Slide 2

Slide 2 text

segunfamisa segunfamisa.com

Slide 3

Slide 3 text

Introduction

Slide 4

Slide 4 text

typealiases

Slide 5

Slide 5 text

typealiases

Slide 6

Slide 6 text

typealiases ● Nicknames for regular existing types

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

// 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

Slide 11

Slide 11 text

// 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

Slide 12

Slide 12 text

// 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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

// 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

Slide 20

Slide 20 text

// 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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

inline classes

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

// 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?

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

// 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

Slide 28

Slide 28 text

// 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

Slide 29

Slide 29 text

// 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

Slide 30

Slide 30 text

// 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

Slide 31

Slide 31 text

// 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

Slide 32

Slide 32 text

// 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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

// 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); }

Slide 36

Slide 36 text

// 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); }

Slide 37

Slide 37 text

// 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!

Slide 38

Slide 38 text

// 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); }

Slide 39

Slide 39 text

// 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

Slide 40

Slide 40 text

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”

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

inline classes typealiases

Slide 43

Slide 43 text

inline classes typealiases No new type is introduced

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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)

Slide 47

Slide 47 text

property delegates

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Lazy Initialized only once.

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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"

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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 }

Slide 60

Slide 60 text

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) }

Slide 61

Slide 61 text

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!

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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()) ... }

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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) } }

Slide 67

Slide 67 text

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) } }

Slide 68

Slide 68 text

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) } }

Slide 69

Slide 69 text

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) } }

Slide 70

Slide 70 text

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) } }

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

More Kotlin features ● Generics ● Kotlin native ● Annotations

Slide 73

Slide 73 text

Resources

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Thank you!