Slide 1

Slide 1 text

KOTLIN 102 GDG TORONTO ANDROID Jigar Brahmbhatt (@shaktiman_droid)

Slide 2

Slide 2 text

WRITE CODE IN KOTLIN WAY

Slide 3

Slide 3 text

Read popular open-sourced Kotlin-first code How?

Slide 4

Slide 4 text

Discuss idiomatic way to write Kotlin during PR reviews Read popular open-sourced Kotlin-first code How?

Slide 5

Slide 5 text

Discuss idiomatic way to write Kotlin during PR reviews Ask yourself, is there a better way to write this Kotlin ? Read popular open-sourced Kotlin-first code How?

Slide 6

Slide 6 text

1 https://play.google.com/store/apps/details?id=com.intersect.specly

Slide 7

Slide 7 text

1 https://play.google.com/store/apps/details?id=com.intersect.specly

Slide 8

Slide 8 text

1 private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) } else { textInputLayout.setText(newValue.toString()) } }

Slide 9

Slide 9 text

private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) } else { textInputLayout.setText(newValue.toString()) } }

Slide 10

Slide 10 text

private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) } else { textInputLayout.setText(newValue.toString()) } }

Slide 11

Slide 11 text

private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) } else { textInputLayout.setText(newValue.toString()) } }

Slide 12

Slide 12 text

private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) } else { textInputLayout.setText(newValue.toString()) } }

Slide 13

Slide 13 text

Can we improve this ? private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) } else { textInputLayout.setText(newValue.toString()) } }

Slide 14

Slide 14 text

private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) return } else { textInputLayout.setText(newValue.toString()) } } fun setCurrentValue(newValue: Int) { if (newValue in MIN_VALUE#..MAX_VALUE) { textInputLayout.text = newValue.toString() } else { textInputLayout.text = newValue.coerceIn(MIN_VALUE, MAX_VALUE).toString() } }

Slide 15

Slide 15 text

fun setCurrentValue(newValue: Int) { if (newValue in MIN_VALUE#..MAX_VALUE) { textInputLayout.text = newValue.toString() } else { textInputLayout.text = newValue.coerceIn(MIN_VALUE, MAX_VALUE).toString() } } private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) return } else { textInputLayout.setText(newValue.toString()) } }

Slide 16

Slide 16 text

Primives.kt fun Int.coerceIn(minimumValue: Int, maximumValue: Int): Int { if (minimumValue > maximumValue) throw IllegalArgumentException("Cannot coerce value to an empty range: maximum $maximumValue is less than minimum $minimumValue.") if (this < minimumValue) return minimumValue if (this > maximumValue) return maximumValue return this } https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/

Slide 17

Slide 17 text

fun setCurrentValue(newValue: Int) { if (newValue in MIN_VALUE#..MAX_VALUE) { textInputLayout.text = newValue.toString() } else { textInputLayout.text = newValue.coerceIn(MIN_VALUE, MAX_VALUE).toString() } } private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) return } else { textInputLayout.setText(newValue.toString()) } } Can we improve this further ?

Slide 18

Slide 18 text

fun setCurrentValue(newValue: Int) { if (newValue in MIN_VALUE#..MAX_VALUE) { textInputLayout.text = newValue.toString() } else { textInputLayout.text = newValue.coerceIn(MIN_VALUE, MAX_VALUE).toString() } } private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) return } else { textInputLayout.setText(newValue.toString()) } } fun setCurrentValue(newValue: Int) { val text = newValue .takeIf { newValue in MIN_VALUE#..MAX_VALUE } #?: newValue.coerceIn(MIN_VALUE, MAX_VALUE) textInputLayout.text = text.toString() }

Slide 19

Slide 19 text

fun setCurrentValue(newValue: Int) { if (newValue in MIN_VALUE#..MAX_VALUE) { textInputLayout.text = newValue.toString() } else { textInputLayout.text = newValue.coerceIn(MIN_VALUE, MAX_VALUE).toString() } } private const val MAX_VALUE = 99 private const val MIN_VALUE = 8 fun setCurrentValue(newValue: Int) { if (newValue > MAX_VALUE #|| newValue < MIN_VALUE) { val valueToSet = if (newValue > MAX_VALUE) MAX_VALUE else MIN_VALUE textInputLayout.setText(valueToSet.toString()) return } else { textInputLayout.setText(newValue.toString()) } } fun setCurrentValue(newValue: Int) { val text = newValue .takeIf { newValue in MIN_VALUE#..MAX_VALUE } #?: newValue.coerceIn(MIN_VALUE, MAX_VALUE) textInputLayout.text = text.toString() }

Slide 20

Slide 20 text

Standard.Kt /** * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't. "*/ public inline fun T.takeIf(predicate: (T) #-> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (predicate(this)) this else null } https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/

Slide 21

Slide 21 text

Standard.Kt /** * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't. "*/ public inline fun T.takeIf(predicate: (T) #-> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (predicate(this)) this else null } https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/

Slide 22

Slide 22 text

2 fun getUserId(): String? { return if (isValid) { userIdLocal } else { null } }

Slide 23

Slide 23 text

2 fun getUserId(): String? { return userIdLocal.takeIf { isValid } } fun getUserId(): String? { return if (isValid) { userIdLocal } else { null } }

Slide 24

Slide 24 text

3 fun getDeviceHash(): Int? { val deviceHash = prefs.getString(KEY_ENCRYPTED_HASH, null) return deviceHash#?.let { hash #-> decrypt(hash)#?.toInt() } }

Slide 25

Slide 25 text

3 fun getDeviceHash(): Int? { with(prefs.getString(KEY_ENCRYPTED_HASH, null)) { return this.takeUnless { this #== null }#?.toInt() } } fun getDeviceHash(): Int? { val deviceHash = prefs.getString(KEY_ENCRYPTED_HASH, null) return deviceHash#?.let { hash #-> decrypt(hash)#?.toInt() } }

Slide 26

Slide 26 text

3 fun getDeviceHash(): Int? { with(prefs.getString(KEY_ENCRYPTED_HASH, null)) { return this.takeUnless { this #== null }#?.toInt() } } fun getDeviceHash(): Int? { val deviceHash = prefs.getString(KEY_ENCRYPTED_HASH, null) return deviceHash#?.let { hash #-> decrypt(hash)#?.toInt() } }

Slide 27

Slide 27 text

SOME INTERESTING FINDINGS

Slide 28

Slide 28 text

Mutability in list val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun setGiftCards(list: List) { println(list) }

Slide 29

Slide 29 text

Mutability in list Output: [123, 456] val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun setGiftCards(list: List) { println(list) }

Slide 30

Slide 30 text

Mutability in list val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun setGiftCards(list: List) { list.add("789") #// read-only println(list) }

Slide 31

Slide 31 text

Mutability in list #//Java private static void setGiftCards(List cards) { cards.add("789"); } val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun setGiftCards(list: List) { list.add("789") #// read-only println(list) }

Slide 32

Slide 32 text

Mutability in list val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun setGiftCards(list: List) { (list as MutableList).add("789") println(list) }

Slide 33

Slide 33 text

Mutability in list val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun setGiftCards(list: List) { (list as MutableList).add("789") println(list) } Output: [123, 456, 789]

Slide 34

Slide 34 text

Mutability in list val giftCards = mutableListOf("123", "456") val newGiftCards = listOf("123", "456") setGiftCards(newGiftCards) fun setGiftCards(list: List) { (list as MutableList).add("789") println(list) }

Slide 35

Slide 35 text

val giftCards = mutableListOf("123", "456") val newGiftCards = listOf("123", "456") setGiftCards(newGiftCards) fun setGiftCards(list: List) { (list as MutableList).add("789") println(list) } Mutability in list java.lang.UnsupportedOperationException at java.util.AbstractList.add(AbstractList.java:148) at java.util.AbstractList.add(AbstractList.java:108)

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

Mutability in list implementation "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.2"

Slide 38

Slide 38 text

Mutability in list val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun setGiftCards(list: List) { this.list = list.toImmutableList() println(list) }

Slide 39

Slide 39 text

Mutability in list val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun setGiftCards(list: List) { this.list = list.toImmutableList() (list as MutableList).add("789") println(list) }

Slide 40

Slide 40 text

Mutability in list java.lang.ClassCastException: kotlinx.collections.immutable.implementations. immutableList.SmallPersistentVector cannot be cast to kotlin.collections.MutableList val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun setGiftCards(list: List) { this.list = list.toImmutableList() (list as MutableList).add("789") println(list) }

Slide 41

Slide 41 text

Requirements Analytics for product listing and product listing action should be sequential Execute Lambda in queue

Slide 42

Slide 42 text

Execute Lambda in queue object PlaygroundSdk { private lateinit var retrofitService: RetrofitService fun sendProductListAnalytics() { GlobalScope.launch { putProductListAnalytics() } } private suspend fun putProductListAnalytics() { retrofitService.putAnalytics(data) } fun sendProductListActionAnalytics() { GlobalScope.launch { putProductListActionAnalytics() } } private suspend fun putProductListActionAnalytics() { retrofitService.putAnalytics(data) } }

Slide 43

Slide 43 text

private val putCallQueue = PriorityQueue Unit>() Execute Lambda in queue

Slide 44

Slide 44 text

private suspend fun enqueuePut(f: suspend () #-> Unit) { if (putCallQueue.isEmpty()) { putCallQueue.add(f) f.invoke() putCallQueue.apply { remove() poll()#?.let { enqueuePut(it) } } } else { putCallQueue.add(f) } } private val putCallQueue = PriorityQueue Unit>() Execute Lambda in queue

Slide 45

Slide 45 text

private val putCallQueue = PriorityQueue Unit>() fun sendProductListAnalytics() { GlobalScope.launch { enqueuePut { putProductListAnalytics() } } } fun sendProductListActionAnalytics() { GlobalScope.launch { enqueuePut { putProductListActionAnalytics() } } } Execute Lambda in queue private suspend fun enqueuePut(f: suspend () #-> Unit) { if (putCallQueue.isEmpty()) { putCallQueue.add(f) f.invoke() putCallQueue.apply { remove() poll()#?.let { enqueuePut(it) } } } else { putCallQueue.add(f) } }

Slide 46

Slide 46 text

Trick with type alias #// SDK class PlaygroundManeger { fun manage() { TODO() } }

Slide 47

Slide 47 text

Trick with type alias #// SDK class PlaygroundManeger { fun manage() { TODO() } } #// App PlaygroundManeger().manage()

Slide 48

Slide 48 text

Trick with type alias #// SDK class PlaygroundManeger { fun manage() { TODO() } } #// App PlaygroundManeger().manage()

Slide 49

Slide 49 text

Trick with type alias #// SDK class PlaygroundManager { fun manage() { TODO() } } #// App PlaygroundManeger().manage()

Slide 50

Slide 50 text

Trick with type alias #// SDK class PlaygroundManager { fun manage() { TODO() } } #// App PlaygroundManeger().manage()

Slide 51

Slide 51 text

Trick with type alias #// SDK class PlaygroundManager { fun manage() { TODO() } } #// Kotlin app PlaygroundManeger().manage() typealias PlaygroundManeger = PlaygroundManager

Slide 52

Slide 52 text

Exhaustive `when`

Slide 53

Slide 53 text

Exhaustive `when`

Slide 54

Slide 54 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState()

Slide 55

Slide 55 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState() fun handleState(state: ViewState) { #// Not used as an expression i.e. no return when(state) { is Normal #-> { #// do something } is Loading #-> { #// do something } } }

Slide 56

Slide 56 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState() object Error: ViewState() fun handleState(state: ViewState) { #// Not used as an expression i.e. no return when(state) { is Normal #-> { #// do something } is Loading #-> { #// do something } } }

Slide 57

Slide 57 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState() object Error: ViewState() fun handleState(state: ViewState) { #// Not used as an expression i.e. no return when(state) { is Normal #-> { #// do something } is Loading #-> { #// do something } } } No errors!

Slide 58

Slide 58 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState() object Error: ViewState() fun handleState(state: ViewState) { #// Not used as an expression i.e. no return when(state) { is Normal #-> { #// do something } is Loading #-> { #// do something } } } No errors!

Slide 59

Slide 59 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState() fun handleState(state: ViewState) { #// Used as an expression val value = when(state) { is Normal #-> { #// Return something } is Loading #-> { #// Return something } } }

Slide 60

Slide 60 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState() fun handleState(state: ViewState) { #// Used as an expression val value = when(state) { is Normal #-> { #// Return something } is Loading #-> { #// Return something } } }

Slide 61

Slide 61 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState() fun handleState(state: ViewState) { #// Used as an expression val value = when(state) { is Normal #-> { #// Return something } is Loading #-> { #// Return something } } }

Slide 62

Slide 62 text

Popular solution package io.plaidapp.core.util /** * Helper to force a when statement to assert all options are matched in a when statement. * * By default, Kotlin doesn't care if all branches are handled in a when statement. However, if you * use the when statement as an expression (with a value) it will force all cases to be handled. * * This helper is to make a lightweight way to say you meant to match all of them. * * Usage: * * ``` * when(sealedObject) { * is OneType "-> "// * is AnotherType "-> "// * }.exhaustive "*/ val T.exhaustive: T get() = this https:#//github.com/android/plaid/

Slide 63

Slide 63 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState() fun handleState(state: ViewState) { #// Not used as an expression i.e. no return when(state) { is Normal #-> { #// do something } is Loading #-> { #// do something } }.exhaustive }

Slide 64

Slide 64 text

Exhaustive `when` sealed class ViewState data class Normal(val info: String): ViewState() object Loading: ViewState() fun handleState(state: ViewState) { #// Not used as an expression i.e. no return when(state) { is Normal #-> { #// do something } is Loading #-> { #// do something } }.exhaustive }

Slide 65

Slide 65 text

THANK YOU HOPE YOU LEARNED SOMETHING! Jigar Brahmbhatt (@shaktiman_droid)