$30 off During Our Annual Pro Sale. View Details »

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. KOTLIN 102
    GDG TORONTO ANDROID
    Jigar Brahmbhatt (@shaktiman_droid)

    View Slide

  2. WRITE

    CODE

    IN

    KOTLIN

    WAY

    View Slide

  3. Read popular open-sourced

    Kotlin-first code
    How?

    View Slide

  4. Discuss idiomatic way to write
    Kotlin during PR reviews
    Read popular open-sourced

    Kotlin-first code
    How?

    View Slide

  5. 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?

    View Slide

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

    View Slide

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

    View Slide

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

    }

    }

    View Slide

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

    } else {

    textInputLayout.setText(newValue.toString())

    }

    }

    View Slide

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

    }

    }

    View Slide

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

    }

    }

    View Slide

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

    }

    }

    View Slide

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

    }

    }

    View Slide

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

    }

    }

    View Slide

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

    }

    }

    View Slide

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

    View Slide

  17. 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 ?

    View Slide

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

    }

    View Slide

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

    }

    View Slide

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

    View Slide

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

    View Slide

  22. 2
    fun getUserId(): String? {

    return if (isValid) {

    userIdLocal

    } else {

    null

    }

    }

    View Slide

  23. 2
    fun getUserId(): String? {

    return userIdLocal.takeIf { isValid }

    }
    fun getUserId(): String? {

    return if (isValid) {

    userIdLocal

    } else {

    null

    }

    }

    View Slide

  24. 3
    fun getDeviceHash(): Int? {

    val deviceHash = prefs.getString(KEY_ENCRYPTED_HASH, null)

    return deviceHash#?.let { hash #->

    decrypt(hash)#?.toInt()

    }

    }

    View Slide

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

    }

    }

    View Slide

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

    }

    }

    View Slide

  27. SOME

    INTERESTING

    FINDINGS

    View Slide

  28. Mutability in list
    val giftCards = mutableListOf("123", "456")

    setGiftCards(giftCards.toList())

    fun setGiftCards(list: List) {

    println(list)

    }

    View Slide

  29. Mutability in list
    Output: [123, 456]
    val giftCards = mutableListOf("123", "456")

    setGiftCards(giftCards.toList())

    fun setGiftCards(list: List) {

    println(list)

    }

    View Slide

  30. Mutability in list
    val giftCards = mutableListOf("123", "456")

    setGiftCards(giftCards.toList())

    fun setGiftCards(list: List) {

    list.add("789") #// read-only

    println(list)

    }

    View Slide

  31. 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)

    }

    View Slide

  32. Mutability in list
    val giftCards = mutableListOf("123", "456")

    setGiftCards(giftCards.toList())

    fun setGiftCards(list: List) {

    (list as MutableList).add("789")


    println(list)

    }

    View Slide

  33. 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]

    View Slide

  34. 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)

    }

    View Slide

  35. 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)

    View Slide

  36. View Slide

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

    View Slide

  38. Mutability in list
    val giftCards = mutableListOf("123", "456")

    setGiftCards(giftCards.toList())

    fun setGiftCards(list: List) {

    this.list = list.toImmutableList()
    println(list)

    }

    View Slide

  39. 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)

    }

    View Slide

  40. 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)

    }

    View Slide

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

    View Slide

  42. 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)

    }

    }

    View Slide

  43. private val putCallQueue = PriorityQueue Unit>()


    Execute Lambda in queue

    View Slide

  44. 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

    View Slide

  45. 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)

    }

    }

    View Slide

  46. Trick with type alias
    #// SDK

    class PlaygroundManeger {

    fun manage() {

    TODO()

    }

    }

    View Slide

  47. Trick with type alias
    #// SDK

    class PlaygroundManeger {

    fun manage() {

    TODO()

    }

    }
    #// App

    PlaygroundManeger().manage()

    View Slide

  48. Trick with type alias
    #// SDK

    class PlaygroundManeger {

    fun manage() {

    TODO()

    }

    }
    #// App

    PlaygroundManeger().manage()

    View Slide

  49. Trick with type alias
    #// SDK

    class PlaygroundManager {

    fun manage() {

    TODO()

    }

    }
    #// App

    PlaygroundManeger().manage()

    View Slide

  50. Trick with type alias
    #// SDK

    class PlaygroundManager {

    fun manage() {

    TODO()

    }

    }
    #// App

    PlaygroundManeger().manage()

    View Slide

  51. Trick with type alias
    #// SDK

    class PlaygroundManager {

    fun manage() {

    TODO()

    }

    }
    #// Kotlin app

    PlaygroundManeger().manage()
    typealias PlaygroundManeger = PlaygroundManager

    View Slide

  52. Exhaustive `when`

    View Slide

  53. Exhaustive `when`

    View Slide

  54. Exhaustive `when`
    sealed class ViewState

    data class Normal(val info: String): ViewState()

    object Loading: ViewState()

    View Slide

  55. 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

    }

    }

    }

    View Slide

  56. 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

    }

    }

    }

    View Slide

  57. 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!

    View Slide

  58. 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!

    View Slide

  59. 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

    }

    }

    }

    View Slide

  60. 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

    }

    }

    }

    View Slide

  61. 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

    }

    }

    }

    View Slide

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

    View Slide

  63. 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

    }

    View Slide

  64. 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

    }

    View Slide

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

    View Slide