Amongst the clamor of “machine learning” and “artificial intelligence”, there was another clear message that Apple wanted to hammer home in its WWDC24 Developer State of the Union: "…the best apps are built with Native SDKs".

Slide from Apple’s Developer State of the Union @ WWDC 24
Slide from Apple’s Developer State of the Union @ WWDC 24

This is certainly true from a developer’s perspective. Native SDKs integrate into a given operating system exactly as the platform expects, and in doing so are trusted to be the most performant. Native SDKs are also well-documented and better understood as they have a long history of adoption. Additionally, as platforms evolve, apps built with Native SDKs are the most reliable. They need far fewer refactors and are apt for implementing new platform features quickly and painlessly.

From a user’s perspective, Native SDKs offer a best-in-class app experience by providing a familiar interface to interact with. This is crucial to build trust amongst users of your app. The last thing you want is a user to feel uncomfortable seeing an unfamiliar payment experience leading them to abandoning your app.

Native SDKs solve this issue by providing out-of-the-box solutions for common problems like implementing a checkout flow through trusted systems like Apple or Google Pay. Using these solutions greatly expedites the time needed to build commonly implemented features by minimizing the need to rebuild components from scratch.

Native SDKs offer an ideal user experience, and are generally the most straight-forward means to build an app. So, how can a company like Fueled reconcile this optimal experience with the realities of economically shipping an app on an ever increasing list of consumer platforms?

Enter Kotlin Multiplatform

As an organization with a history of specializing in mobile development, Fueled has worked alongside clients to develop the best apps that suit their needs. This includes native iOS and Android apps, as well as cross-platform apps using frameworks such as Flutter and React Native.

But, for clients whose applications demand a best-in-class user experience, alongside the need to support advanced platform specific features, we have found Kotlin Multiplatform (KMP) to work really well. KMP also works really well for clients who have an in-house team of native app developers that plan to maintain the app in the long run.

KMP has been designed from the ground up to simplify cross-platform development while retaining the flexibility and benefits of native mobile development. It does this in two key ways:

  1. Code Sharing Choice: Flexibility in choosing what parts of a codebase need to be shared across platforms to suit the specific needs of the project

  2. Native Interop: Powerful tools to enable interoperability with platform-specific APIs

Let’s analyze each of these in some detail.

1/ Code Sharing Choice

Modern mobile app codebases are often segregated into different layers or modules like data (cache + networking), domain, presentation, and ui. In a purely native codebase, these modules would all be written in the platform’s native language (Kotlin for Android, Swift for iOS). In a KMP codebase, developers have the ability to configure exactly which modules they want to be shared across the platforms they are supporting. At Fueled, we’ve had projects where we decided to share only the data layer between Android and iOS. This helped increase the project’s velocity by cutting down developer effort writing networking logic by a half.

More recently, we worked with Six Flags to build their Android and iOS apps from scratch. Six Flags came to Fueled asking for industry-leading Android and iOS apps that were identical in their feature set. Additionally, Six Flags wanted their apps to have the ability to support cutting-edge technologies like AR and VR down the line.

After evaluating cross-platform frameworks such as Flutter, React Native (RN), and KMP, we decided that KMP was the perfect fit for this project. While Flutter and RN are great options, they fall short while building mobile apps that rely heavily on platform specific APIs.

Furthermore, a unique challenge in the Six Flags project was managing data from various third-party vendors for their mobile apps. One vendor handled mobile food ordering, another managed park pass purchases, and there were discussions about adding a new vendor to develop an interactive map experience for the parks. Based on our evaluation, KMP would efficiently address this challenge by allowing us to write shared networking code to abstract the complexities of dealing with multiple vendors, providing a uniform API for the project’s domain layer to consume.

As a consequence, for the Six Flags project, we decided to share the data, domain, and presentation layers between Android and iOS. These three layers were written (and tested!) just once using Kotlin and provided as a shared module to the Android and iOS platform teams. From there, the platform teams developed the ui layer of the apps independently using technologies they felt comfortable using. iOS used SwiftUI and Android used Jetpack Compose.

Full control over code-sharing
Full control over code-sharing

This setup helped us leverage extensive code-sharing without limiting functionality and scalability on the user-facing front of the apps.

2/ Native Interop

Code-sharing choice in KMP is about choosing which parts of your codebase are shared and which aren’t. The parts that are shared are implemented in Kotlin and the rest are implemented using native code. Building on top of this paradigm, KMP also allows developers to make any part of their shared code, native. Let’s see how this works with an example.

As part of the Six Flags project, we were required to implement app analytics using Firebase. Unfortunately, Firebase did not have a KMP SDK we could use. KMP’s way to navigate this issue is by using its (fantastic) support for native interoperability.

Developers can define functions or classes prefixed with a special expect keyword within the shared portion of a project’s codebase. These expect functions or classes work similarly to interfaces in Java or protocols in Swift. Their shape or signature is defined in the shared layer of the project, but the actual implementation needs to be defined per-platform using—you guessed it—the actual keyword.

In our analytics example, the shared layer expected an AnalyticsHelper class to be made available by each platform. Each platform defined an actual AnalyticsHelper implementation which interacted with the native Android and iOS Firebase SDKs to log analytics on Firebase.

<code>expect</code> and <code>actual</code> usage in KMP
expect and actual usage in KMP

Closing

Cross-platform development is all the rage these days. Who doesn’t want to minimize effort by having one codebase spit out multiple apps?!

Amongst the various cross-platform frameworks that exist today, Kotlin Multiplatform with its mission to simplify cross-platform development while retaining the flexibility and benefits of native mobile development offers something truly unique. For this reason, it isn’t surprising that Google publicly announced their backing of the KMP project as part of this year’s Google IO.

KMP is here to stay and we plan on talking more about our experience of working with it in upcoming blog posts. Keep your eyes locked in on The Cache so you don’t miss what we have in store!

If you have any thoughts on what you read, feel free to hit me up on X @ratixyz.

Related articles: