Case study
June 8, 2022

Building our first Kotlin Multiplatform Mobile project

Building our first Kotlin Multiplatform Mobile project

Unflow is the next generation CMS built for mobile apps. Product managers, marketers, designers and developers use Unflow to create and publish onboarding carousels, engaging stories, product updates and NPS surveys using our visual editor - no code required. Our infrastructure lets you release changes instantly so there is no need for engineering involvement or an app store release. Advanced segmentation makes it easy to create cohorts of your users, while A/B testing makes it simple to experiment with when exactly you’d like content to be delivered.

Our team had experience using similar systems that had been built in-house when working on some of the largest consumer facing mobile products in the world (Monzo & Intercom), but sorely lacked the same tooling in apps we’d built since then. So, we set out to build a category defining product in the mobile space.

Mobile app revenues are booming. The market is expected to reach over $935bn by 2023, having grown 3x since 2017. This is largely driven by the emergence of viable subscription based business models. On its IPO debut, Duolingo's valuation surged 40%. Most of these businesses operate on a freemium basis, whereby their revenue is reliant on users upgrading to paid subscriptions. This means that the AARRR Funnel from Activation through to Referral is constantly being tracked and optimized by marketing and product teams. Unflow allows anyone to instantly update content, segment users and run A/B tests on their in-app experiences, to directly increase app revenues for customers.

In Autumn 2021, we set out to build Unflow with a very lean product team. Kotlin Multiplatform was the obvious choice for our cross-platform SDK given our team’s size and strong Android experience. As we’ve grown the teamWe now have a team of 4 engineers who routinely make contributions to Unflow’s KMM codebase.

Optimizing for moving quickly

Our entire product was built by an absurdly small team that included only one mobile engineer with little native iOS experience. We decided very early on to explore the idea of using KMM as a shared business-logic core. This meant we would only need to write platform-specific specific code to support the UI on iOS, significantly reducing our learning curve. Which in turn significantly reduced the amount of time needed to ship our MVP.

What was truly unique about KMM’s approach to multiplatform development was the reliance on native UI. As our customers wanted to recreate crucial in-app experiences (onboarding carousels, payment flows, etc) using Unflow, we needed to . React Native and web-based solutions didn’t offer the same performance or offline capabilities as a native alternative, two things that were critical to our use case.

Another benefit of delegating the UI to platform-specific code meant that we were able to choose the templating language that best suited our needs. As a greenfield project we chose to use the latest declarative UI languages from Apple and Google, SwiftUI and Jetpack Compose respectively. Declarative UI requires less code which translates to a reduced package size and is typed so it can be tested more easily. Two important benefits when developing an SDK.

By leveraging KMM from the very beginning, the Unflow team were able to release the first version of the Unflow SDK within a month of committing to the idea. Our customers had support for both their Android and iOS apps from the beginning. Using a web dashboard they could design and set carousel screens live which instantly appeared in the host app. Thanks to KMM’s XCFramework generator we were able to publish versions on CocoaPods, Swift Package Manager, as well as the Maven Central repository.

Early experiments with KMM

Our lead mobile engineer, Kavi, who had recently played with KMM in a personal side-project, albeit in an app and not an SDK (an important distinction that becomes apparent later on), initially suggested that it might be a good fit for what we are doing at Unflow. Kavi’s experience was with Android primarily and as he was the only mobile engineer on the team at the time, it made sense that we try out a Kotlin-based framework that could be easily reused across both platforms.

Our first exploration using KMM involved spiking a minimal implementation of the SDK to explore what it might look like on both platforms. We needed to figure out what our tech stack and architecture would compose of and, more crucially, how stable the framework was if it were to be a key component of our new product.

We time-boxed the MVP to a month and started writing some code, experimenting, and pushing the boundaries of our basic (at the time) KMM knowledge. After the month, we sat down to see whether we should continue with KMM or go down the more “traditional” route of building out two (three including React Native) entirely native and separate SDKs for each platform.

Some of the key questions we wanted to answer were:

  • Could we use KMM to produce libraries/SDKs?
  • How much code/time/effort duplication can we remove?
  • What’s the maintenance cost?
  • How stable is it?
  • Does it change anything for consumers?

We started at the bottom with the network and data layer, to get a feel for the kind of business logic that could be shared across platforms. Once happy with its capabilities, we pivoted to the other end, attempting to figure out how to publish SDK artifacts that could be consumed by customers of Unflow. This turned out to be much more challenging...

Most of the material online – documentation, samples, and guides – is tailored towards developing multiplatform apps, rather than libraries. Initially we struggled to figure out how to adapt our build tooling to do what we wanted. After absorbing lots of documentation, searching on the Kotlin Slack, digging through samples (s/o People in Space and KaMPKit), and plenty of trial and error, we eventually got to a point where we were able to build an .aar for Android and an XCFramework for iOS.

The next challenge was publishing. Even though publishing to Maven seems like a throwback to software development in the early 00s – or what we imagine it to be (no one on our team was developing software back then 😅) – it’s a known-quantity and there is plenty of help available online.

Publishing a library for iOS, particularly one built with KMM, was much more daunting (especially for an Android engineer). We should use CocoaPods, right? But isn’t Swift Package Manager the new go-to? Can we do both? Turns out we can!

Two major points of confusion arose:

  • KMM allows you to specify dependencies for iOS using CocoaPods but it can only be used for internal dependencies, not for exporting your own project as a pod dependency.
  • SPM support was initially only available via an (admittedly good) third-party plugin – although better XCFramework generation came to KMM natively soon after.

Suffice to say we managed to work around each of our issues and the resulting SDK was shipped 4 weeks after we first started our KMM project.

Working with KMM day-to-day

We’ve been happily using KMM day-to-day for over 6 months now and during this time have become much more comfortable and familiar with it, validating our initial decision to adopt KMM. Using a multiplatform framework, we’ve been able to develop and release a number of pretty complex features, such as Push Notifications, Stories, and Segmentation, simultaneously on both Android and iOS without having to duplicate effort too much, allowing us to focus on the native implementations and the product features themselves.

KMM is currently used in the product as the base layer for both the Android & iOS SDKs for Unflow. Using a form of clean architecture, we have used KMM to implement the networking, data persistence, and domain layers of our SDK in a shared Gradle module. On Android this shared Gradle module is added as an implementation dependency to our Android SDK module. On iOS the shared KMM code is exported as an XCFramework that is used by our iOS SDK. All of this lives within a single monorepo so cross-platform development is truly as simple as calling git pull on a single repository.

We chose to limit our use of KMM to code that stops just short of the “UI layer” – which we consider to include presenters/view models – as this is where we believe KMM to be most useful. For the UI layer of the Unfow SDKs we have chosen to rely more heavily on the native framework implementations – e.g. ObservableObject ViewModels for SwiftUI on iOS and Jetpack ViewModels on Android. As well as native SwiftUI and Jetpack Compose constructs.

There is a strong existing Kotlin ecosystem that we are able to lean on, and we have used a number of popular libraries in our implementation to make our lives much easier:

  • Ktor – Networking
  • SQLDelight – Caching and data storage
  • Koin – Dependency injection

Although KMM has been pretty great to work with so far, there are definitely some areas for improvement that I expect will get better over time. Some of these include:

  • Better inter-operability with modern Swift – for example a bridge from Kotlin suspend functions and Flows to Swift’s async/await and async stream.
  • Simplified memory model – this is already happening!
  • Stability and observability – it’s often difficult to debug issues that occur either in the KMM framework or as a result of some code written using KMM, particular on iOS stack traces.

In order to align with the principle of not “fighting the framework”, Unflow’s UI layer remains platform-native and we use KMM to share data operation, business logic, and domain object related code, for which it is much better suited. We’ve found immeasurable success using it in this way but it is unlikely we will expand our use of KMM outside this remit. In fact, as we grow our mobile engineering team (we’re hiring btw), and have more bandwidth to really optimize the SDKs for each platform, we may even consider pushing more and more of the shared logic back into the native SDKs... The likely reason for this would be the relative instability of KMM and lack of mature helper libraries and tooling, as opposed to the native Android and iOS platforms.

Currently the shared Gradle KMM module is primarily developed and maintained by Kavi, our mobile engineer with the most Kotlin experience. However all the engineers on our team have contributed at some point, which shows just how accessible and easy-to-understand it is. Even our iOS engineers have been happy to leave Xcode for a bit and get involved!

Working with KMM as an iOS engineer

When I joined Unflow, I came into a project that was slowly maturing. It had the majority of its business logic handled inside a KMM module, with only the iOS UI being handled by Swift. This meant I really didn't have to think about the backend code, I just got to focus on the parts that make an app really feel special like interactions and animations by leaning on the native SDK.

As new features come along, we go about the process exactly like a standard mobile team would, except we don’t have to break off and do things twice. The backend is handled by our talented Android developer, whilst I focus on SwiftUI.

On the odd occasion I've needed to dig into the KMM codebase, it has been really easy to understand. Kotlin and Swift are the closest the two platforms have ever been, so reading the code is a breeze. Contributing to the shared module was painless, and it helps enable cross platform collaboration that we just wouldn’t get with two separate codebases.

Conclusion

As a small team KMM has been invaluable to us in achieving our goals. Despite initial difficulties setting the project up we still estimate having saved ourselves nearly 2 months build time when releasing our first version, a massive edge at our early stage when trying to acquire our first customers as quick as possible. The unique approach to multiplatform development has meant that our small team can deliver an exceptionally high quality product without compromising on performance or native capabilities. Given the Kotlin community support it’s clear that KMM will only become a more obvious choice for small teams looking to increase productivity and unlock multiplatform opportunities for their apps. We’re excited to see all the improvements that will help us build an even better product long into the future.

What are you waiting for?

Make apps smarter,
with Unflow

Thanks for your interest!
Something's gone wrong while submitting the form