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

C4861b1dfdf3bbb21faec4a1acdf183d?s=47 Ellen Shapiro
December 06, 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.

C4861b1dfdf3bbb21faec4a1acdf183d?s=128

Ellen Shapiro

December 06, 2019
Tweet

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
  2. None
  3. None
  4. None
  5. None
  6. None
  7. !

  8. None
  9. EVERYONE HAS A CROSS-PLATFORM ! HORROR STORY

  10. ! AIRBNB : REACT NATIVE

  11. None
  12. None
  13. JAVASCRIIIIIIIIPT!

  14. None
  15. None
  16. None
  17. None
  18. None
  19. None
  20. ! DROPBOX C++

  21. None
  22. None
  23. None
  24. DE-PRIORITIZE DEVELOPER EXPERIENCE AT YOUR OWN RISK

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

    BACK
  26. None
  27. ! SHAMELESS PLUG ALERT!

  28. None
  29. None
  30. None
  31. None
  32. None
  33. None
  34. None
  35. None
  36. None
  37. None
  38. None
  39. KOTLIN/NATIVE FOCUSES ON SHARING LOGIC

  40. CENTRALIZE TESTING

  41. None
  42. None
  43. !

  44. None
  45. USE KOTLIN/NATIVE FOR ALL THE THINGS!

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

  47. None
  48. None
  49. iOS app

  50. iOS app Android app

  51. iOS app Android App Server

  52. iOS app Android app Server Raspberry pi

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

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

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

    ✅ Raspberry pi* ✅ * - soon, I hope
  56. None
  57. None
  58. MVA*

  59. MVA* * - ANYTHING OTHER THAN A MODEL OR A

    VIEW FOR THE LOVE OF GOD
  60. MVP

  61. None
  62. None
  63. MODEL: VIEW: PRESENTER:

  64. MODEL: data class VIEW: PRESENTER:

  65. MODEL: data class VIEW: interface PRESENTER:

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

  67. VIEW: interface

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

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

  70. IOS VIEW: UIViewController ANDROID VIEW: Fragment* TESTING VIEW: *May not

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

    not apply if your name is jake wharton
  72. None
  73. commonMain PRESENTER class DeviceDetailPresenter( val view: DeviceDetailView, val device: PairedDevice,

    storage: SecureStorage ): BaseCoroutinePresenter(secureStorage = storage) { // ... }
  74. commonMain VIEW interface DeviceDetailView: IndefiniteLoadingIndicating { fun setTitle(toString: String) fun

    setLockButtonEnabled(enabled: Boolean) fun setUnlockButtonEnabled(enabled: Boolean) fun setApiError(toString: String?) }
  75. ANDROID APP FRAGMENT class DeviceDetailFragment: Fragment(), DeviceDetailView { private val

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

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

    { private val presenter by lazy { DeviceDetailPresenter( this, currentDevice, KeyStoreManager(this.context!!) ) } //... }
  78. PRESENTER class DeviceDetailPresenter( val view: DeviceDetailView, val device: PairedDevice, storage:

    SecureStorage ): BaseCoroutinePresenter(secureStorage = storage) { // ... }
  79. None
  80. None
  81. None
  82. (Copyright Alex Norris of Webcomic Name)

  83. MVP

  84. MVSP

  85. MVSP MODEL VIEWSTATE PRESENTER

  86. VIEW INTERFACES -> VIEWSTATE OBJECTS

  87. class DeviceDetailPresenter( val device: PairedDevice ): BaseCoroutinePresenter() { data class

    DeviceDetailViewState( val lockButtonEnabled: Boolean, val unlockButtonEnabled: Boolean, val errorMessage: String? = null, val indicatorAnimating: Boolean = false ) // ... }
  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 { //.. }
  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 { //.. }
  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 { //.. }
  91. UNIDIRECTIONAL ♻ DATA FLOW

  92. BLAME SWIFTUI

  93. DESTROY YOUR HEROES UI STATE

  94. None
  95. import SwiftUI struct ValueView: View { var value: Double =

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

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

    0 var body: some View { return Text("\(value)") } }
  98. LESS MUTABLE STATE -> FEWER PROBLEMS

  99. SHARING MAKES SENSE THEORETICALLY

  100. STYLE DEFINITION

  101. COLORS

  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"
  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) }
  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) }
  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 ) }
  106. import android.graphics.Color import no.bakkenbaeck.pppshared.ui.PPPColor fun PPPColor.toAndroidColor(): Int { return Color.parseColor(this.hexColor)

    }
  107. None
  108. SHOULD YOU SHARE CODE OR SOMETHING ELSE?

  109. None
  110. None
  111. None
  112. None
  113. None
  114. None
  115. None
  116. None
  117. None
  118. ! LIBRARIES

  119. ktor + kotlinx.serialization

  120. NETWORK + PARSING

  121. NETWORK + PARSING IOS + ANDROID + SERVER

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

    )
  123. KTOR USES SINGLE-THREADED COROUTINES

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

  127. None
  128. DATABASE SQLDelight

  129. PRO: ONE DATABASE TO ! RULE THEM ALL

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

  131. (HOW BAD IS THAT, REALLY?)

  132. ANDROID: SQLDELIGHT FLOW EXTENSIONS

  133. None
  134. None
  135. IOS: SWIFTUI

  136. KNOW WHY YOU'RE USING KOTLIN/NATIVE

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

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

  143. WHAT AM I GIVING UP THAT THE SYSTEM HAS BEEN

    DOING FOR ME?
  144. IS IT WORTH THAT TRADE-OFF?

  145. OBLIGATORY SUMMARY SLIDE

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

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

    > You can move way faster on some things
  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
  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
  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
  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