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

Kotlin 102

Kotlin 102

In this talk, you'll learn about some tips on writing code in Kotlin way, along with interesting findings about Kotlin collection's mutability, exhaustiveness of when clause and a couple of other tricks.

Cross-border remote meetup event with GDG Toronto Android community and GDG Boston Android community
https://www.meetup.com/ToAndroidDev/events/270874680/

Youtube link: https://www.youtube.com/watch?v=O6EmGzAGnuk

Jigar Brahmbhatt

June 17, 2020
Tweet

More Decks by Jigar Brahmbhatt

Other Decks in Programming

Transcript

  1. Discuss idiomatic way to write Kotlin during PR reviews Read

    popular open-sourced Kotlin-first code How?
  2. 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?
  3. 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()) } }
  4. 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()) } }
  5. 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()) } }
  6. 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()) } }
  7. 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()) } }
  8. 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()) } }
  9. 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() } }
  10. 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()) } }
  11. 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/
  12. 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 ?
  13. 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() }
  14. 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() }
  15. Standard.Kt /** * Returns `this` value if it satisfies the

    given [predicate] or `null`, if it doesn't. "*/ public inline fun <T> 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/
  16. Standard.Kt /** * Returns `this` value if it satisfies the

    given [predicate] or `null`, if it doesn't. "*/ public inline fun <T> 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/
  17. 2 fun getUserId(): String? { return userIdLocal.takeIf { isValid }

    } fun getUserId(): String? { return if (isValid) { userIdLocal } else { null } }
  18. 3 fun getDeviceHash(): Int? { val deviceHash = prefs.getString(KEY_ENCRYPTED_HASH, null)

    return deviceHash#?.let { hash #-> decrypt(hash)#?.toInt() } }
  19. 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() } }
  20. 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() } }
  21. Mutability in list Output: [123, 456] val giftCards = mutableListOf("123",

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

    setGiftCards(list: List<String>) { list.add("789") #// read-only println(list) }
  23. Mutability in list #//Java private static void setGiftCards(List<String> cards) {

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

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

    setGiftCards(list: List<String>) { (list as MutableList<String>).add("789") println(list) } Output: [123, 456, 789]
  26. Mutability in list val giftCards = mutableListOf("123", "456") val newGiftCards

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

    setGiftCards(newGiftCards) fun setGiftCards(list: List<String>) { (list as MutableList<String>).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)
  28. Mutability in list val giftCards = mutableListOf("123", "456") setGiftCards(giftCards.toList()) fun

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

    setGiftCards(list: List<String>) { this.list = list.toImmutableList() (list as MutableList<String>).add("789") println(list) }
  30. 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<String>) { this.list = list.toImmutableList() (list as MutableList<String>).add("789") println(list) }
  31. 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) } }
  32. 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<suspend () #-> Unit>() Execute Lambda in queue
  33. private val putCallQueue = PriorityQueue<suspend () #-> 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) } }
  34. Trick with type alias #// SDK class PlaygroundManeger { fun

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

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

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

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

    manage() { TODO() } } #// Kotlin app PlaygroundManeger().manage() typealias PlaygroundManeger = PlaygroundManager
  39. 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 } } }
  40. 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 } } }
  41. 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!
  42. 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!
  43. 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 } } }
  44. 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 } } }
  45. 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 } } }
  46. 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> T.exhaustive: T get() = this https:#//github.com/android/plaid/
  47. 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 }
  48. 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 }