Cross-platform development has always been considered the best cost-effective solution to create and maintain the same software on multiple platforms, at least in theory. In practice, the experience of each team has been different, with mixed results.
This year we decided to challenge ourselves and experience first-hand what it is to develop a mobile app with Kotlin Multiplatform Mobile (KMM in short) for a new greenfield project, Calliope.
Presenting Calliope
Calliope is an audio recorder app with editing capabilities.
For rookie podcasters that need to share their story in a easy and fast way, Calliope is a mobile app that allows the user to record and import an audio segment and, compared to Voice Memo Apps, our product builds and shares audio compositions and facilitate podcast production.
Project kickstart
The general architecture of this project was:
- an iOS app that contains just the UI code, based on SwiftUI
- an Android app that contains just the UI code, based on Jetpack Compose
- a library shared between the two apps, containing the majority of the business logic, based on Kotlin Multiplatform
Thanks to our experience with Jenkins, macOS, fastlane and ansible, we were able to configure the CI to build, test and deploy all components. This allowed us to run tests or release a new app update in few minutes, from day one.
We started with 3 developers, one for each component: iOS app, Android app, and shared library. Our bet was that over time each developer would become knowledgeable enough to work on the shared library regardless of their main focus (iOS or Android).
Active development
Thanks to a good set of 3rd party libraries, the initial bootstrap of the app was fast: Coroutines, Serialization, Ktor, and SQLDelight are already a solid foundation.
Every time a 3rd party library would prove its limit, we resorted to platform-specific APIs to achieve what we wanted.
Sadly, while these bridges were straightforward with the Android APIs, they proved to be more complex on iOS. APIs that we know and use regularly in Swift or Objective-C are translated in Kotlin to be consumed in the shared library. This translation sometimes makes them very hard to deal with.
As the product began iterating quickly, both in identity and features to be added, we revised our multi-platform commitment and decided to focus just on the platform that suited better our target market: iOS.
Technical retrospective
After few months of development, taken into consideration the good and the bad things, we held a meeting to discuss and re-evaluate our initial choice.
The biggest pain point was the complexity of dealing with asynchronous code (both in development and debug), especially between coroutines (on KMM) and Combine (on iOS), also because of object freezing.
Also, the constantly evolving shape of the project and the choice of focusing on only one platform in this first phase removed almost all the benefits of using KMM in the short/medium term.
In general, everything was doable, but it was more complex than necessary, consuming more time and energies we had planned for this project.
Since the pains were more than the gains, we agreed to stop using KMM and slowly removed it from the app.
What we learned about KMM
Kotlin Multiplatform is a very good tool, with a lot of room to grow in the future. As of today, it still limits us on these key areas:
- interoperability of asynchronous code between Kotlin and Swift, mostly because of the current implementation of the multi-threaded coroutines
- debugging of the shared library when running the iOS app
For us, KMM did not work well enough: it was an extra layer of complexity that slowed us down, with no concrete benefits for our project.
It doesn’t mean it will for you too! Each team and project is different, so if you have the chance, take it for a spin! It’s worth a try already!
What we learned about us
Failure IS an acceptable option, especially when we are pushing ourselves out of the comfort zone, trying new things. We made a bet on KMM and this time we lost it. At Spreaker, this is totally acceptable.
We are always transparent about our choices and how they are performing. When we realize something is not going as planned, we discuss about it and find together a solution. We work as a team, always!
That’s how we roll here at Spreaker.