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

I Walk The Line - What Should Be Kotlin/Native And What (Maybe) Shouldn't - KotlinConf, Copenhagen, Denmark, December 2019

I Walk The Line - What Should Be Kotlin/Native And What (Maybe) Shouldn't - KotlinConf, Copenhagen, Denmark, December 2019

Code:
https://github.com/bakkenbaeck/PorchPirateProtector

Abstract:
Kotlin/Native is an incredibly powerful toolset for creating cross-platform code, particularly for mobile apps. But iOS and Android both have their...quirks, particularly around handling Application, View Controller, and Fragment/Activity lifecycle. The line between getting things done more efficiently and getting sucked into a whirlpool of memory leaks can seem mighty thin, but it can be walked. In this talk, Ellen will discuss how she's walked it, what's worked -- and what definitely has not.

Ellen Shapiro
PRO

December 06, 2019
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

  1. I WALK THE LINE
    WHAT SHOULD BE KOTLIN/NATIVE, AND WHAT (MAYBE) SHOULDN'T
    KOTLINCONF | COPENHAGEN, DENMARK | DECEMBER 2019
    ELLEN SHAPIRO | @DESIGNATEDNERD | APOLLOGRAPHQL.COM

    View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. !

    View Slide

  8. View Slide

  9. EVERYONE HAS A CROSS-PLATFORM
    !
    HORROR STORY

    View Slide

  10. !
    AIRBNB :
    REACT NATIVE

    View Slide

  11. View Slide

  12. View Slide

  13. JAVASCRIIIIIIIIPT!

    View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. !
    DROPBOX
    C++

    View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. DE-PRIORITIZE DEVELOPER EXPERIENCE
    AT YOUR OWN RISK

    View Slide

  25. DEVS WHO HAVE WORKED IN SAFER LANGUAGES
    REFUSE TO GO BACK

    View Slide

  26. View Slide

  27. !
    SHAMELESS PLUG ALERT!

    View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. View Slide

  33. View Slide

  34. View Slide

  35. View Slide

  36. View Slide

  37. View Slide

  38. View Slide

  39. KOTLIN/NATIVE
    FOCUSES ON SHARING LOGIC

    View Slide

  40. CENTRALIZE TESTING

    View Slide

  41. View Slide

  42. View Slide

  43. !

    View Slide

  44. View Slide

  45. USE KOTLIN/NATIVE FOR
    ALL THE THINGS!

    View Slide

  46. !
    PORCH PIRATE PROTECTOR
    https://github.com/bakkenbaeck/porchpirateprotector

    View Slide

  47. View Slide

  48. View Slide

  49. iOS app

    View Slide

  50. iOS app
    Android app

    View Slide

  51. iOS app
    Android App
    Server

    View Slide

  52. iOS app
    Android app
    Server
    Raspberry pi

    View Slide

  53. Works With Kotlin?
    iOS app
    Android app
    Server
    Raspberry pi

    View Slide

  54. Works With Kotlin?
    iOS app ✅
    Android app ✅
    Server ✅
    Raspberry pi ✅

    View Slide

  55. Works With Kotlin?
    iOS app ✅
    Android app ✅
    Server ✅
    Raspberry pi* ✅
    * - soon, I hope

    View Slide

  56. View Slide

  57. View Slide

  58. MVA*

    View Slide

  59. MVA*
    * - ANYTHING OTHER THAN A MODEL OR A VIEW FOR THE LOVE OF GOD

    View Slide

  60. MVP

    View Slide

  61. View Slide

  62. View Slide

  63. MODEL:
    VIEW:
    PRESENTER:

    View Slide

  64. MODEL: data class
    VIEW:
    PRESENTER:

    View Slide

  65. MODEL: data class
    VIEW: interface
    PRESENTER:

    View Slide

  66. MODEL: data class
    VIEW: interface
    PRESENTER: class

    View Slide

  67. VIEW: interface

    View Slide

  68. IOS VIEW: UIViewController
    ANDROID VIEW:
    TESTING VIEW:

    View Slide

  69. IOS VIEW: UIViewController
    ANDROID VIEW: Fragment*
    TESTING VIEW:

    View Slide

  70. IOS VIEW: UIViewController
    ANDROID VIEW: Fragment*
    TESTING VIEW:
    *May not apply if your name is jake wharton

    View Slide

  71. IOS VIEW: UIViewController
    ANDROID VIEW: Fragment*
    TESTING VIEW: ¯\_(ϑ)_/¯
    *May not apply if your name is jake wharton

    View Slide

  72. View Slide

  73. commonMain PRESENTER
    class DeviceDetailPresenter(
    val view: DeviceDetailView,
    val device: PairedDevice,
    storage: SecureStorage
    ): BaseCoroutinePresenter(secureStorage = storage) {
    // ...
    }

    View Slide

  74. commonMain VIEW
    interface DeviceDetailView: IndefiniteLoadingIndicating {
    fun setTitle(toString: String)
    fun setLockButtonEnabled(enabled: Boolean)
    fun setUnlockButtonEnabled(enabled: Boolean)
    fun setApiError(toString: String?)
    }

    View Slide

  75. ANDROID APP FRAGMENT
    class DeviceDetailFragment: Fragment(), DeviceDetailView {
    private val presenter by lazy { DeviceDetailPresenter(
    this,
    currentDevice,
    KeyStoreManager(this.context!!)
    )
    }
    //...
    }

    View Slide

  76. WHAT HAVE I DONE WRONG HERE?
    class DeviceDetailFragment: Fragment(), DeviceDetailView {
    private val presenter by lazy { DeviceDetailPresenter(
    this,
    currentDevice,
    KeyStoreManager(this.context!!)
    )
    }
    //...
    }

    View Slide

  77. WHAT HAVE I DONE WRONG HERE?
    class DeviceDetailFragment: Fragment(), DeviceDetailView {
    private val presenter by lazy { DeviceDetailPresenter(
    this,
    currentDevice,
    KeyStoreManager(this.context!!)
    )
    }
    //...
    }

    View Slide

  78. PRESENTER
    class DeviceDetailPresenter(
    val view: DeviceDetailView,
    val device: PairedDevice,
    storage: SecureStorage
    ): BaseCoroutinePresenter(secureStorage = storage) {
    // ...
    }

    View Slide

  79. View Slide

  80. View Slide

  81. View Slide

  82. (Copyright Alex Norris of Webcomic Name)

    View Slide

  83. MVP

    View Slide

  84. MVSP

    View Slide

  85. MVSP
    MODEL VIEWSTATE PRESENTER

    View Slide

  86. VIEW INTERFACES -> VIEWSTATE OBJECTS

    View Slide

  87. class DeviceDetailPresenter(
    val device: PairedDevice
    ): BaseCoroutinePresenter() {
    data class DeviceDetailViewState(
    val lockButtonEnabled: Boolean,
    val unlockButtonEnabled: Boolean,
    val errorMessage: String? = null,
    val indicatorAnimating: Boolean = false
    )
    // ...
    }

    View Slide

  88. suspend fun getStatusAsync(
    initialViewStateHandler: (DeviceDetailViewState) -> Unit,
    secureStorage: SecureStorage
    ): DeviceDetailViewState {
    // ...
    }
    suspend fun lockAsync(
    initialViewStateHandler: (DeviceDetailViewState) -> Unit,
    secureStorage: SecureStorage
    ): DeviceDetailViewState {
    //...
    }
    suspend fun unlockAsync(
    initialViewStateHandler: (DeviceDetailViewState) -> Unit,
    secureStorage: SecureStorage
    ): DeviceDetailViewState {
    //..
    }

    View Slide

  89. suspend fun getStatusAsync(
    initialViewStateHandler: (DeviceDetailViewState) -> Unit,
    secureStorage: SecureStorage
    ): DeviceDetailViewState {
    // ...
    }
    suspend fun lockAsync(
    initialViewStateHandler: (DeviceDetailViewState) -> Unit,
    secureStorage: SecureStorage
    ): DeviceDetailViewState {
    //...
    }
    suspend fun unlockAsync(
    initialViewStateHandler: (DeviceDetailViewState) -> Unit,
    secureStorage: SecureStorage
    ): DeviceDetailViewState {
    //..
    }

    View Slide

  90. suspend fun getStatusAsync(
    initialViewStateHandler: (DeviceDetailViewState) -> Unit,
    secureStorage: SecureStorage
    ): DeviceDetailViewState {
    // ...
    }
    suspend fun lockAsync(
    initialViewStateHandler: (DeviceDetailViewState) -> Unit,
    secureStorage: SecureStorage
    ): DeviceDetailViewState {
    //...
    }
    suspend fun unlockAsync(
    initialViewStateHandler: (DeviceDetailViewState) -> Unit,
    secureStorage: SecureStorage
    ): DeviceDetailViewState {
    //..
    }

    View Slide

  91. UNIDIRECTIONAL

    DATA FLOW

    View Slide

  92. BLAME SWIFTUI

    View Slide

  93. DESTROY YOUR HEROES
    UI STATE

    View Slide

  94. View Slide

  95. import SwiftUI
    struct ValueView: View {
    var value: Double = 0
    var body: some View {
    return Text("\(value)")
    }
    }

    View Slide

  96. import SwiftUI
    struct ValueView: View {
    var value: Double = 0
    var body: some View {
    return Text("\(value)")
    }
    }

    View Slide

  97. import SwiftUI
    struct ValueView: View {
    var value: Double = 0
    var body: some View {
    return Text("\(value)")
    }
    }

    View Slide

  98. LESS MUTABLE STATE -> FEWER PROBLEMS

    View Slide

  99. SHARING MAKES SENSE
    THEORETICALLY

    View Slide

  100. STYLE DEFINITION

    View Slide

  101. COLORS

    View Slide

  102. val colorPrimary = "#05C1FD"
    val colorPrimaryDark = "#5848F4"
    val colorAccent = "#fb5502"
    val colorAccentSelected = "#c94402"
    val textLight = "#ffffff"
    val textMiddle = "#777777"
    val textDark = "#000000"
    val errorRed = "#cc0000"
    val success = "#0d9b2a"

    View Slide

  103. enum class PPPColor(val hexColor: String) {
    ColorPrimary("#05C1FD"),
    ColorPrimaryDark("#5848F4"),
    ColorAccent("#fb5502"),
    ColorAccentSelected("#c94402"),
    TextLight("#ffffff"),
    TextMiddle("#777777"),
    TextDark("#000000"),
    ErrorRed("#cc0000"),
    Success("#0d9b2a");
    val red: Long
    get() = hexColor.substring(1, 3).toLong(16)
    val green: Long
    get() = hexColor.substring(3, 5).toLong(16)
    val blue: Long
    get() = hexColor.substring(5, 7).toLong(16)
    }

    View Slide

  104. enum class PPPColor(val hexColor: String) {
    ColorPrimary("#05C1FD"),
    ColorPrimaryDark("#5848F4"),
    ColorAccent("#fb5502"),
    ColorAccentSelected("#c94402"),
    TextLight("#ffffff"),
    TextMiddle("#777777"),
    TextDark("#000000"),
    ErrorRed("#cc0000"),
    Success("#0d9b2a");
    val red: Long
    get() = hexColor.substring(1, 3).toLong(16)
    val green: Long
    get() = hexColor.substring(3, 5).toLong(16)
    val blue: Long
    get() = hexColor.substring(5, 7).toLong(16)
    }

    View Slide

  105. import platform.UIKit.UIColor
    fun PPPColor.toUIColor(): UIColor {
    return UIColor(
    red = this.red.toDouble() / 255.0f,
    green = this.green.toDouble() / 255.0f,
    blue = this.blue.toDouble() / 255.0f,
    alpha = 1.0
    )
    }

    View Slide

  106. import android.graphics.Color
    import no.bakkenbaeck.pppshared.ui.PPPColor
    fun PPPColor.toAndroidColor(): Int {
    return Color.parseColor(this.hexColor)
    }

    View Slide

  107. View Slide

  108. SHOULD YOU SHARE CODE
    OR SOMETHING ELSE?

    View Slide

  109. View Slide

  110. View Slide

  111. View Slide

  112. View Slide

  113. View Slide

  114. View Slide

  115. View Slide

  116. View Slide

  117. View Slide

  118. !
    LIBRARIES

    View Slide

  119. ktor + kotlinx.serialization

    View Slide

  120. NETWORK + PARSING

    View Slide

  121. NETWORK + PARSING
    IOS + ANDROID + SERVER

    View Slide

  122. @Serializable
    data class LockState(
    val deviceId: Int,
    val isLocked: Boolean
    )

    View Slide

  123. KTOR USES SINGLE-THREADED COROUTINES

    View Slide

  124. View Slide

  125. View Slide

  126. DIG IN TO FIND OUT WHERE LIBRARIES
    ARE KINDA CHEATING

    View Slide

  127. View Slide

  128. DATABASE
    SQLDelight

    View Slide

  129. PRO: ONE DATABASE TO
    !
    RULE THEM ALL

    View Slide

  130. CON: GIVE UP DEEP INTEGRATION
    OF CoreData AND Room

    View Slide

  131. (HOW BAD IS THAT, REALLY?)

    View Slide

  132. ANDROID: SQLDELIGHT
    FLOW EXTENSIONS

    View Slide

  133. View Slide

  134. View Slide

  135. IOS: SWIFTUI

    View Slide

  136. KNOW WHY YOU'RE USING
    KOTLIN/NATIVE

    View Slide

  137. View Slide

  138. View Slide

  139. View Slide

  140. via http://www.dorkly.com/post/67855/watch-someone-beat-all-4-original-mario-games-at-the-same-time

    View Slide

  141. View Slide

  142. CROSS-PLATFORM
    IS NOT AN EXCUSE
    TO IGNORE THE PLATFORM

    View Slide

  143. WHAT AM I GIVING UP
    THAT THE SYSTEM HAS BEEN DOING FOR ME?

    View Slide

  144. IS IT WORTH THAT TRADE-OFF?

    View Slide

  145. OBLIGATORY SUMMARY SLIDE

    View Slide

  146. OBLIGATORY SUMMARY SLIDE
    > Kotlin/Native can be a double-edged sword

    View Slide

  147. OBLIGATORY SUMMARY SLIDE
    > Kotlin/Native can be a double-edged sword
    > You can move way faster on some things

    View Slide

  148. OBLIGATORY SUMMARY SLIDE
    > Kotlin/Native can be a double-edged sword
    > You can move way faster on some things
    > Be mindful of the trade-offs you're making

    View Slide

  149. OBLIGATORY SUMMARY SLIDE
    > Kotlin/Native can be a double-edged sword
    > You can move way faster on some things
    > Be mindful of the trade-offs you're making
    > Prioritize developer experience to drive adoption

    View Slide

  150. OBLIGATORY SUMMARY SLIDE
    > Kotlin/Native can be a double-edged sword
    > You can move way faster on some things
    > Be mindful of the trade-offs you're making
    > Prioritize developer experience to drive adoption
    > Remember why you're using K/N in the first place

    View Slide

  151. LINKS!
    > The (not so) hidden cost of sharing code between iOS
    and Android
    https://blogs.dropbox.com/tech/2019/08/
    the-not-so-hidden-cost-of-sharing-code-
    between-ios-and-android/
    > React Native at AirBnB
    https://medium.com/airbnb-engineering/
    react-native-at-airbnb-f95aa460be1c

    View Slide