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

SDK Design and Publishing For Kotlin Multiplatform Mobile

SDK Design and Publishing For Kotlin Multiplatform Mobile

Most Android developers who are aware of KMP know that you can code "once" then publish to iOS. That is technically true. However, as you progress beyond "POC" to integrating and shipping real production code, the design of the "SDK surface" becomes more complicated and nuanced (or, possibly, problematic). Many SDK design considerations are common, whether you have a Kotlin module in a single monorepo, are an internal team publishing SDK's for your org (a very common KMP use case), or a vendor publishing an SDK for public consumption. Some topics we'll cover: An Android consumer can see all the richness of the Kotlin language, but the iOS side gets filtered through Objctive-C into Swift. Much can get lost along the way. You need to understand and plan for this. Exposing too much to the iOS surface not only makes navigating documentation difficult but can have a significant binary size impact. We'll discuss what to look for and the techniques to use. Packaging and publishing of iOS artifacts have a number of things to consider (there's a bit on that). We'll walk through these topics and more to share what we've learned over the last few years of KMP/KMM production development.

58d1281770fe55a05a96600244ec8341?s=128

Kevin Galligan

July 06, 2022
Tweet

More Decks by Kevin Galligan

Other Decks in Technology

Transcript

  1. SDK Design and Publishing For Kotlin Multiplatform Mobile Kevin Galligan

  2. Partner at Kotlin GDE

  3. Touchlab

  4. None
  5. Lots of Promises final of talk arc

  6. context

  7. Android Conference just native mobile

  8. Kotlin (common) Android Framework Android Stuff iOS Stuff

  9. Android side? just kotlin

  10. iOS side? well…

  11. Kotlin —> Xcode Framework nice, done!

  12. API Not Ideal you can definitely make it worse

  13. Objc

  14. The Big Issues or this talk’s TOC

  15. Kotlin through an Objc lens lowest common denominator

  16. Modularization one framework, name collisions, …

  17. Limit Public API nothing extra

  18. Packaging and Team Stuff some thoughts about practical approaches

  19. “Everything Else” just shove it in there

  20. why bother?

  21. Writing code 2x is insane! if you didn’t need to,

    of course
  22. “Kotlin doesn’t do ___” misses the point

  23. ~$70k ~$65k

  24. ~$70k ~$0

  25. Yes, Currently 2x is Best in your org, with your

    team, hired/trained for 2x
  26. Native isn’t going away but there is consolidation

  27. UX Complexity/Sensitivity 2014 Not-Native Native

  28. No Precise Data obviously

  29. UX Complexity/Sensitivity 2018 Not-Native Native

  30. UX Complexity/Sensitivity 2022 Not-Native Native

  31. UX Complexity/Sensitivity The boring middle Not-Native Native

  32. UX Complexity/Sensitivity 2026? Not-Native Native

  33. KMP UI Story it’s coming

  34. compose UI on iOS

  35. None
  36. UX Complexity/Sensitivity The boring middle + Not-Native Native

  37. iOS Compose will be OK probably not perfect

  38. Optionally Native much lower platform risk

  39. But I digress…

  40. Shared Code Overhead this is the issue

  41. Zero Overhead? we wouldn’t write 2x

  42. All Options != kotlin focused on interop

  43. If *I* was on your team kmp party!

  44. Touchlab

  45. That’s what we’re here to talk about… bringing us back

    from the technical odyssey
  46. notes on the android side

  47. KMP Ready just JVM

  48. https://www.droidcon.com/2022/06/28/embracing- commonmain-for-android-development/

  49. https://github.com/handstandsam/kmp4free

  50. Just Kotlin basically

  51. kotlin through an objective-c lens

  52. Kotlin —> Objc lose expressiveness

  53. Some Major Highlights there are others, but…

  54. Default Parameters

  55. inline fun i(tag: String = this.tag, throwable: Throwable? = null,

    message: () -> String) { //etc }
  56. inline fun i(tag: String = this.tag, throwable: Throwable? = null,

    message: () -> String) { //etc } log.i { "Just a string" }
  57. inline fun i(tag: String = this.tag, throwable: Throwable? = null,

    message: () -> String) { //etc } log.i { "Just a string" } log.i(tag: log.tag, throwable: nil) {"A log"}
  58. Generics it’s complicated

  59. No interfaces variance gets interesting

  60. Coroutines also complicated

  61. Compiler outputs suspend functions we don’t use them (I’ll explain)

  62. (Forget Flows)

  63. Enums not swift-friendly

  64. Other Kotlin Structures sealed classes, etc

  65. Missing Swifty Things structs, enums with values

  66. fixing the api

  67. Two APIs direct kotlin and “not kotlin”

  68. Common Android iOS Kotlin-friendly API Direct calls Adapters Adapters for

    non-Kotlin Clients
  69. Context dependent more or less

  70. Favor Simple and Specific • Simple callbacks (unless you’re doing

    code gen) • Be careful with generics • Sealed classes won’t make sense • Enums are OK (but not great)
  71. Reactive Bridge (AKA make coroutines work)

  72. Suspend Functions we don’t use them

  73. Can’t control lifecycle no coroutine context

  74. Also can’t suppress them extra stuff in the interface

  75. And, Flows don’t work

  76. Several implementations us too (kind of)

  77. KMP-NativeCoroutines

  78. Missing Parallel Features in both kotlin and swift

  79. Default Parameters 3 options

  80. don’t worry about it (explicit params) remove them and have

    multiple methods swift extensions
  81. Enums (without data) simple adapter

  82. Different Language Features don’t blame objective-c

  83. Kotlin and Swift are different syntactic similarities aside

  84. Similar Problems different solutions

  85. Sealed Classes -> Enums enums with associated values

  86. Structs? transform from data classes?

  87. Generate Swift? for the non-kotlin things

  88. Direct Swift Interop won’t solve these issues

  89. Generics (again) yes to interfaces, no to variance

  90. A tale of 2 loggers mini self-own

  91. inline fun i(tag: String = this.tag, throwable: Throwable? = null,

    message: () -> String) { //etc } log.i { "Just a string" } log.i(tag: log.tag, throwable: nil) {"A log"}
  92. inline fun i(message: () -> String) { //etc } inline

    fun i(throwable: Throwable, message: () -> String) { //etc }
  93. extension [Not calling them out by name…] { static func

    v(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .verbose, tag: tag, items, separator: separator, file: file, function: function) } static func d(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .debug, tag: tag, items, separator: separator, file: file, function: function) } static func i(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .info, tag: tag, items, separator: separator, file: file, function: function) } static func w(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .warning, tag: tag, items, separator: separator, file: file, function: function) } static func e(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .error, tag: tag, items, separator: separator, file: file, function: function) } static func a(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .assert, tag: tag, items, separator: separator, file: file, function: function) }
  94. inline fun i(throwable: Throwable? = null, tag: String = this.tag,

    message: () -> String){ logBlock(Severity.Info, tag, throwable, message) }
  95. inline fun i(throwable: Throwable? = null, tag: String = this.tag,

    message: () -> String){ logBlock(Severity.Info, tag, throwable, message) }
  96. extension [Not calling them out by name…] { static func

    v(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .verbose, tag: tag, items, separator: separator, file: file, function: function) } static func d(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .debug, tag: tag, items, separator: separator, file: file, function: function) } static func i(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .info, tag: tag, items, separator: separator, file: file, function: function) } static func w(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .warning, tag: tag, items, separator: separator, file: file, function: function) } static func e(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .error, tag: tag, items, separator: separator, file: file, function: function) } static func a(tag: String? = nil, _ items: Any..., separator: String = " ", file: String = #file, function: String = #function) { log(logLevel: .assert, tag: tag, items, separator: separator, file: file, function: function) }
  97. This talk is already old as I’m writing it

  98. This talk is already old as I’m writing it

  99. Common Android iOS Kotlin-friendly API Direct calls Adapters Adapters for

    non-Kotlin Clients
  100. Common Android iOS Kotlin-friendly API Direct calls (Swift) Auto-gen Swift

    tweaks
  101. “swift thing”

  102. Swift Generation Library with unique parts

  103. Swift Templating kotlin names aren’t known

  104. Swift Linking one framework

  105. Objc Header Transforms shadow stuff

  106. Robust Swift Generation

  107. When? droidcon nyc-ish (so, september)

  108. this talk?

  109. We *may* need support pitching friday

  110. Swift Only! good luck with your JS, etc

  111. Practical issues with “2 APIs” good and bad stuff

  112. None
  113. Common Android iOS Adapters

  114. Common Android iOS Adapters

  115. Common Android iOS Adapters Export

  116. Increasingly Rare • Swift gen makes this less useful •

    Some PRs from Rick Clephas fix some of these issues directly • https://github.com/JetBrains/kotlin/pull/4818 - Annotations to suppress exporting Objc/Swift methods • https://github.com/JetBrains/kotlin/pull/4815 - Better Swift naming control
  117. Name Prefix blunt modularization

  118. Common Android iOS Adapters MyClass CommonMyClass

  119. Common Android iOS Adapters MyClass CommonMyClass Export

  120. It comes up later yes there’s more

  121. why you should give talks

  122. (or “big tangent time”)

  123. “Why am I doing this?” thought precedes every talk

  124. Explaining forces Examining

  125. “2 APIs” is kind of a hack and we’re trying

    to fix it
  126. This feels a little inconsistent to me all for the

    better
  127. Anyway… (just ping me on slack with specific questions)

  128. modularization

  129. Single Framework not a bug

  130. TaxCalc TaxCalcKt Framework Products ProductsKt Framework AcmeRepo stdlib AcmeRepo stdlib

  131. TaxCalc TaxCalcKt Framework Products ProductsKt Framework AcmeRepo stdlib AcmeRepo stdlib

  132. Modularize in Kotlin Layer ci can build multiple collection frameworks

  133. Name Collisions scale makes this worse

  134. Swift name has no namespace co.touchlab.kermit.Logger —> Logger co.touchlab.kermit.helper.Logger —>

    Logger
  135. Swift name has no namespace co.touchlab.kermit.Logger —> Logger co.touchlab.kermit.helper.Logger —>

    _Logger
  136. Dependency Prefix blunt but sort of reduces the problem

  137. Multiple Modules still one framework

  138. multiple modules/namespaces

  139. limit exposed api

  140. Shared Models, Data, Networking Shared Framework Framework from Single Module

  141. Shared Models, Data, Networking Shared Framework Everything visible, or referenced

  142. Shared Models, Data, Networking Shared Framework Dependencies Kermit kotlinx.coroutines SqlDelight

    getLogger():Logger getLogger():Logger
  143. Shared Models, Data, Networking Shared Framework Dependencies Kermit kotlinx.coroutines SqlDelight

    getLogger():Logger getLogger():KermitLogger
  144. Shared Models, Data, Networking Shared Framework Dependencies Kermit kotlinx.coroutines SqlDelight

    getLogger():Logger getLogger():Logger
  145. Shared Models, Data, Networking Shared Framework Who cares?

  146. Bigger header hard to parse (for a developer)

  147. Bigger binary each artifact gets an Objc adapter/logic

  148. Non-visible code is still there not in header and no

    extra binary
  149. Mark “internal” whatever you can

  150. Look for dependency refs scan the header

  151. Consider wrapping things for extreme cases

  152. Shared Models, Networking Shared Framework (Maybe) Hide with modules Kermit

    kotlinx.coroutines SqlDelight someData():DataMyData Data someData():MyData
  153. Export with caution especially transitive

  154. Measuring binary size not on-disk size

  155. Measuring KMP’s impact not just “app size”

  156. Push to app store gold standard

  157. Measure locally https://docs.flutter.dev/perf/app-size#ios

  158. We have a plugin just tells you

  159. packaging and team

  160. Quick Thoughts whole different talk

  161. Generalizations every team is different

  162. Just another SDK “and it better work!”

  163. Common iOS Framework Dev Dev App Publish (XC)Framework Dev Big

    App Other Team Consumes (XC)Framework
  164. Unwanted Guest always gets blamed first

  165. Everybody Writes Kotlin all new tools, build disruption

  166. Common iOS Framework Dev Dev App Publish (XC)Framework Dev Big

    App Other Team Consumes (XC)Framework iOS Devs Android Devs
  167. Common iOS Framework Dev App Publish (XC)Framework

  168. Common iOS Framework Dev App Publish (XC)Framework

  169. Common iOS Framework Dev App Publish (XC)Framework iOS Devs Android

    Devs
  170. Common iOS Framework Dev App Publish (XC)Framework Mobile Devs Mobile

    Devs
  171. Reach out if interested see “feedback we want” slide…

  172. “everything else”

  173. PR’s and YT’s to Watch https://github.com/touchlab/KaMPKit/discussions/252

  174. Feedback We Want Swift generation Introducing KMP to your team

    How your iOS code/team is structured
  175. Feedback We Want https://github.com/touchlab/KaMPKit/discussions/253

  176. We’re hiring! https://touchlab.co/careers-3/

  177. None
  178. Thanks! @kpgalligan