How we built the RevenueCat SDK for Kotlin Multiplatform

Explore the architecture and key decisions behind building the RevenueCat Kotlin Multiplatform SDK, designed to streamline in-app purchases across platforms.

How we built the RevenueCat SDK for Kotlin Multiplatform
Jay Shortway
Published

If you’ve been keeping an eye on our purchases-kmp GitHub repository, you might have noticed that it recently reached its stable v1 release! It came a long way, as it originally started out as an unofficial indie RevenueCat library by yours truly, called KobanKat. Since then, the library has matured a lot, and is now ready for prime time. Keep reading to learn more about how we built the SDK and some of the decisions we took along the way. 

What is Kotlin Multiplatform?

Kotlin Multiplatform (KMP) is a great technology that allows code sharing between virtually all platforms and operating systems. It’s incredibly versatile, and it’s truly native on all platforms it runs on. Kotlin code lives in the exact same memory space as code written in the platform’s primary language. There’s no VM or alternative runtime, and so calling platform APIs can be done directly. There’s no bridging layer needed to move objects to and from the shared memory space, as it’s the same memory space.

The technology has been in development by JetBrains since 2017, and had its first stable release in 2023. Google announced its official support for Kotlin Multiplatform at Google I/O 2024. A lot of Google’s AndroidX SDKs have already started supporting it, including Room, Paging, DataStore and even Compose (with the help of JetBrains).

The number of Kotlin Multiplatform libraries has doubled in the last 2 years. So the ecosystem is growing really fast, but there’s a notable omission: an in-app purchase and subscription solution. Until now, that is! Before the RevenueCat SDK, you’d have to build your own wrappers around Apple’s StoreKit and Google’s Play Billing Library. With the RevenueCat SDK for Kotlin Multiplatform, you’ll only have to write your subscription code once to start earning revenue on both Android and iOS. Not only that, implementing RevenueCat is far simpler than either StoreKit or the Play Billing Library! 

Decisions, decisions

As with every software project, there are tons of decisions we had to make. Here are some of the more interesting ones. 

Bottom-up or top-down?

The main decision we faced was around the architecture. As described, Kotlin Multiplatform is substantially different from other cross platform technologies such as Flutter or React Native, as it is far more flexible. Kotlin Multiplatform SDKs often are built bottom-up, where the lowest levels of the architecture and implementation are shared. Our existing cross platform SDKs are all built top-down, where the top level of the architecture, the public API, is cross platform, but all the implementation lives in the underlying platform SDKs. Since Kotlin Multiplatform gives us the option to do both, should we deviate from our top-down approach here?

These platform SDKs have nuances and bug fixes and can handle obscure edge cases that were built up over years. We definitely don’t want to rewrite that and risk not covering the same ground. See also our recently published Engineering Strategy

For this reason we chose to stay consistent with our existing cross platform SDKs, and build the Kotlin Multiplatform SDK top-down too. Fortunately, Kotlin Multiplatform is so flexible that it doesn’t really matter. If you’re interested in the overall architecture of this approach, check out our original blog post on this topic.

Type alias or wrapper?

So now that we decided that we want the implementation to keep living in the platform SDKs, we had to decide how the Kotlin Multiplatform SDK is going to provide access to this platform implementation. 

KobanKat started out by using type aliases as much as possible. This meant we declared an expect class in our common source set, and would provide an actual typealias in our platform source sets. The advantage of this is that there’s very little runtime overhead. The downside, however, is that this exposes the platform SDKs to the platform source sets of the implementing app. For example, if you’re accessing the RevenueCat SDK in your androidMain source set, you’d be able to import types from the Android SDK as well as from the Kotlin Multiplatform SDK. This is confusing, and besides that, means that we can never change this setup without making a breaking change, as developers might very well be using those platform types.

This was the definitive reason for us to decide to move away from type aliases, and towards wrappers. This way, we can keep evolving the implementation and architecture of the KMP SDK without changing the public API, and thus avoid breaking changes.

Batteries included

The Kotlin Multiplatform SDK has everything you need to start earning revenue with your KMP app, and then some. To start, there’s the purchases-kmp-core library, which contains all the same subscription and in-app purchases logic as our other SDKs. 

Besides that, the KMP SDK also supports Paywalls! Through the power of Compose Multiplatform, you can show Paywalls and Paywall footers on both Android and iOS with a single call. This functionality is included in the purchases-kmp-ui library. 

On top of that, there are some optional add-on modules which add convenience extensions:

  • purchases-kmp-datetime adds extension properties representing timestamps as kotlinx-datetime Instants. KMP doesn’t have a built-in way of dealing with date and time. Instead, JetBrains provides the first-party kotlinx-datetime SDK. So if you’re already using that, this is useful if you want to have a little nicer handling of time than milliseconds since the Unix epoch. 
  • If you like avoiding exceptions as control flow, and are using Kotlin’s built-in Result type for that, have a look at purchases-kmp-result. It adds variants to all existing suspending functions returning a Result. 
  • If you like avoiding exceptions as control flow and typed errors, and are using Arrow for that, have a look at purchases-kmp-either. This adds variants to all existing suspending functions returning Arrow’s Either type. 

How to get started

There are a few steps required to integrate the KMP SDK. The full details are in our docs, but here’s the gist:

  1. Start by declaring the purchases-kmp libraries you want to use. These days, you’re probably using version catalogs, so add them to your libs.versions.toml file.
  2. Opt in to ExperimentalForeignApi in your iOS source sets, as the SDK uses generated Kotlin cinterop bindings for native code on iOS.
  3. Finally, link the PurchasesHybridCommon[UI] framework to your iOS project.

After you’ve integrated the SDK, using it should feel extremely familiar if you’ve used RevenueCat on other platforms. Start by calling Purchases.configure() to configure the SDK, and then use Purchases.sharedInstance to get a reference to the singleton instance that provides access to all functionality.

Can’t wait? Head on over to our docs to install the SDK, and start earning revenue today!

You might also like

Share this post

Want to see how RevenueCat can help?

RevenueCat enables us to have one single source of truth for subscriptions and revenue data.

Olivier Lemarié, PhotoroomOlivier Lemarié, Photoroom
Read Case Study