Engineering
April 11, 2024
April 11, 2024
(updated)

Using Kotlin Multiplatform with KMMBridge and SKIE to publish a native Swift SDK

Manrich van Greunen

Our vision at PowerSync is to provide developers with a platform-agnostic sync layer for building local-first apps. We’re starting by expanding our client SDK to a range of frameworks, but in the near future we’ll also be adding additional database adapters (starting with MySQL). 

We recently released our Kotlin Multiplatform (KMP) SDK, adding to our growing list of PowerSync SDKs, see here

Some customers have been requesting support for PowerSync in native Swift, but as a small team implementing and shipping a Swift SDK would take some time. In the KMP community, many have faced a similar challenge and have developed a host of tools and approaches we could investigate. 

KMP Summary 

(Feel free to skip this section if you’re already familiar with KMP)

  • KMP provides a framework and set of tools to write and maintain code for different platforms, with the flexibility and reliability of native programming.
  • Kotlin supports compiling to native targets. For iOS, the Kotlin compiler compiles shared- and native-specific code into Objective-C. This gets bundled into an Xcode framework which can then be included when building an iOS app.
  • A typical KMP developer will most likely not have to write much Swift code, apart from implementing Swift UI. Using UI frameworks like Kotlin Compose, the typical KMP developer might not even need to write any Swift code.

Enter SKIE

(pronounced as 'sky')

In teams where iOS developers need to implement iOS- or Swift-specific parts of a KMP mobile app, they might find it difficult to use the Objective-C code and headers.

The difficulties might be because of the fact that the code is generated from Kotlin, which has differences in how [.inline-code-snippet]objects[.inline-code-snippet], [.inline-code-snippet]classes[.inline-code-snippet], [.inline-code-snippet]enums[.inline-code-snippet], [.inline-code-snippet]function[.inline-code-snippet] parameters, etc. are declared. Or the KMP implementation might be using software concepts that are not supported (or maybe not widely supported) in Swift. Concepts like [.inline-code-snippet]flows[.inline-code-snippet], [.inline-code-snippet]sealed classes[.inline-code-snippet], [.inline-code-snippet]async callbacks[.inline-code-snippet], etc.

SKIE is a Gradle plugin developed and maintained by Touchlab, headed by KMP expert Kevin Galligan. SKIE hooks into the KMP build pipeline to add and modify the generated Objective-C code that is included in the Xcode Framework. The aim is to improve the Developer Experience (DX) in Swift while preserving core Kotlin code features. It does this using a variety of approaches, which includes creating [.inline-code-snippet]completionHandler[.inline-code-snippet] wrappers, transpiling [.inline-code-snippet]sealed classes[.inline-code-snippet] into equivalent structures in Swift, renaming methods and parameters for better IntelliSense, and many more improvements. 

In summary, SKIE promises to provide better DX for Swift developers.

Publishing with KMMBridge

Since the PowerSync SDK can be added to existing projects to enable local-first capabilities, we needed a way to build and publish the Xcode framework produced by KMP. Luckily the team at Touchlab also built a toolset called KMMBridge to enable this. They’ve also written various guides and provided a template project that was very helpful to get us set up.

Apart from a few issues with needing GitHub Personal Access Tokens and flushing Swift caches that needed to be cleared, we got everything working and could use the PowerSync framework in a standalone Swift app.

Implementation

Using SKIE is as simple as applying the Gradle plugin to the module where the [.inline-code-snippet]framework[.inline-code-snippet] block for native compilations is declared. 

For setting up KMMBridge, we followed this guide which has an accompanying GitHub template.

The basic approach is as follows:

  1. Create a dedicated Gradle module to use the KMP tooling to declare the iOS targets and configure a static [.inline-code-snippet]framework[.inline-code-snippet] that exports our [.inline-code-snippet]core[.inline-code-snippet] SDK module.
  2. Configure KMMBridge to use Swift Package Manager (instead of CocoaPods) and publish to our Maven repo hosted on GitHub.
  3. Set up a GitHub action with the relevant environment variables that calls Touchlab’s KMMBridge action. Given that our PowerSync Kotlin repo uses a custom Gradle plugin to publish to Sonatype Portal, we needed to iron out some issues with the multiple publishing config.
  4. After publishing, the framework can be added as a Package Dependency in Xcode, then imported in Swift files as any other framework, giving you access to exported code from KMP.

Learnings

At the end of implementation, we had a decent sense of the pros and cons of this approach.

Pros

  1. Write a Kotlin SDK and get Swift support, i.e. less code maintenance.
  2. Touchlabs are pushing to make SKIE + KMMBridge great (performant and good DX).
  3. Our team has more experience on the Kotlin/Java side of things, versus Swift.

Cons

  1. Requires jumping through some config hoops, both for deploying the Swift Package and actually getting it installed in an app. [1]
  2. Not the best Swift DX out of the box (lots of generated code, less Swifty code patterns, and APIs), although these can be improved by using SKIE annotations and guidelines.
  3. The total bundle size does increase, because the Kotlin standard library is included in the generated library.

Summary

Weighing up all the above and in light of positive early feedback from customers, we have decided to continue pursuing using SKIE + KMMBridge for publishing our Swift SDK.

Our Swift SDK is currently in closed alpha with select customers — please get in touch on our Discord if you’d like to test it!

Footnotes

[1] We found that installing the SDK as an end-user required these steps:

1. You’ll need a GitHub Personal Access Token (PAT) with [.inline-code-snippet]read:packages[.inline-code-snippet] permissions. If you don't have one, create one at https://github.com/settings/tokens

2. Create a [.inline-code-snippet]~/.netrc[.inline-code-snippet] file with the following contents (this is the recommended approach by KMMBridge):

  machine maven.pkg.github.com
    login [your GitHub username]
    password [your PAT from step 1]

3. Clear Swift caches (typically needed after there were issues while adding our Package Dependency, including PAT with incorrect permission, incorrect version or tag, etc.)

rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf ~/Library/org.swift.swiftpm

4. Reset Packages in Xcode [.inline-code-snippet]File->Packages->Reset Packages[.inline-code-snippet]

5. Clean and Rebuild project