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

SDK Design and Publishing For Kotlin Multiplatf...

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.

Kevin Galligan

July 06, 2022
Tweet

More Decks by Kevin Galligan

Other Decks in Technology

Transcript

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

    message: () -> String) { //etc } log.i { "Just a string" }
  2. 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"}
  3. 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)
  4. 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"}
  5. inline fun i(message: () -> String) { //etc } inline

    fun i(throwable: Throwable, message: () -> String) { //etc }
  6. 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) }
  7. inline fun i(throwable: Throwable? = null, tag: String = this.tag,

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

    message: () -> String){ logBlock(Severity.Info, tag, throwable, message) }
  9. 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) }
  10. 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
  11. Shared Models, Networking Shared Framework (Maybe) Hide with modules Kermit

    kotlinx.coroutines SqlDelight someData():DataMyData Data someData():MyData
  12. Common iOS Framework Dev Dev App Publish (XC)Framework Dev Big

    App Other Team Consumes (XC)Framework iOS Devs Android Devs