Exploring Kotlin Multiplatform Mobile: A Comprehensive Overview
Authorship Disclaimer: This blog post was co-authored by David Aldorf and Tomáš Král.
The realm of multi-platform development has received a remarkable addition with JetBrains' new framework known as Kotlin Multiplatform Mobile (KMM), which has evolved to be Kotlin Multiplatform (KMP) since September 2023. The primary objective of KMP is to facilitate code sharing across various platforms, effectively curbing development costs. In this article, we dive into KMP, its unique features, challenges faced during implementation, and its applicability to real-world projects.
So why did we opt for Kotlin Multiplatform? We were driven by the aspiration to maximize code sharing, and with Compose UI now available on iOS, we chose to embark on this path to achieve our goal. This decision promised not only efficient cross-platform development but also the potential to leverage the modern Compose framework seamlessly across iOS and other platforms.
Understanding KMP: How It Works
Kotlin Multiplatform (KMP) extends beyond Android to encompass platforms like iOS, web, and desktop. It unifies a shared codebase for internal API access across iOS, Android, web, and desktop, with a standout feature being its native compilation for each target platform. This is made possible by the Kotlin/Native compiler for iOS and desktop and the Kotlin/JVM compiler for Android. Notably, for iOS, the Kotlin/Native compiler translates Kotlin code into LLVM bytecode, seamlessly integrating with Swift runtime components and resulting in iOS app bundles.
The goal is to write to a shared module and especially to commonMain as much code as possible. This part is shared with all platforms. androidMain and iosMain in the shared module are used for platform-specific code. It is written in Kotlin but it can work with platform-specific APIs and libraries. This is pure magic, especially on the iOS side. androidApp and iosApp modules are standard Android/iOS applications using the shared module as a library.
This PoC was written from scratch and it makes sense to us to have just one shared module/library. But in real life, it is possible to make more of them. For example, one for the data layer, one for data storage, etc. Also, a database-shared module can be used the same way on all platforms regardless if it is Room on Android or Realm on iOS. These platform-specific things will be in the Android/iOS part of the shared module. Android and iOS applications will just call a simple facade API.
We use it just for Android and iOS but it can be used for Web and Desktop as well. JetBrains devs are going even further and their idea is to share the same Domain models and services between Backend and Frontend.
Our Target: Proof-of-Concept App
Our goal is clear: to create a prototype app that showcases a grid of movies and series. When users tap on an item, they'll see detailed info about it and be able to start watching. To achieve this, we'll download the assets and make sure users can play videos from their source URLs.
Project Setup and Implementation
Before we start KMP development, we need to set up Environment and Project. Xcode, Android Studio, and the Kotlin Multiplatform Mobile plugin are essential. A helpful tool named Kdoctor aids in setting up and validating the development environment. CocoaPods are optional, even JetBrains says to avoid them if your project is not using CocoaPods dependencies. Because it is a more complicated setup.
Good information on how to start can be found here:
Crafting the Project Structure
Android Studio has a wizard-driven setup, resulting in separate iOS and Android apps that share a common codebase. Although this setup appears unified and it is good for getting familiar with KMP,. Iin reality, Android app, iOS app, and KMP module/library (which is in our case called shared) will be in separate repositories.
You can check this wizard if you want some inspiration for the build.gradle setup. It especially helps with setting up gradle for specific platforms and dependencies.
It is awesome how simple it is to run an iOS app from Android Studio. The iOS one is created and configured the same way the Android one is. All devices from Xcode can be selected as an execution target.
Compose Multiplatform: Unifying UI Development
Jetpack Compose has transformed UI development, especially on Android, offering a seamless and robust solution. Compose Multiplatform takes this innovation a step further, extending its benefits without drawbacks. It empowers developers to craft captivating interfaces seamlessly on Android.
For iOS, Compose Multiplatform employs Skiko for a unique Canvas-style interpretation, breathing creativity into UI development. While it differs from the UIKit-based approach, it ensures UI interoperability via iOS widgets/UIKit and SwiftUI. By embedding native UI components in shared UIs through elements like UIKitView and ComposeUIViewController, platforms blend effortlessly.
Notably, Compose Multiplatform doesn't cover tvOS, but Kotlin Multiplatform (KMP) remains reliable here. This demonstrates KMP's flexibility in diverse contexts.
For an in-depth look, explore the Compose Multiplatform GitHub repository, a hub of collaborative innovation driving cross-platform UI development.
Key Features and Libraries
There are a lot of multiplatform libraries available these days, so you can easily avoid platform-specific implementation. Still, there is room for improvement. In our case, the library for the video player was missing. Even so, the number of KMP libraries is growing every day.
From an iOS developer's viewpoint, adopting KMP can entail a shift to a new IDE (Android Studio), potentially complicating the entry process. While syntax similarities might ease the transition, debugging in Xcode and adapting to Compose's alpha stage on iOS could present challenges. Business logic and networking layers are prime candidates for code sharing, whereas platform-specific concerns often linger.
Android developers have it way simpler. The only big differences between pure Android and KMP projects are build.gradle and project modules structure. Even Compose UI stays almost untouched.
To call a platform-specific API in the shared module, it is necessary to step out from commonMain to androidMain and/or iosMain. It is possible to do it simply by Interface or the new JetBrains construct expect/actual can be used. Expect/actual will force programmers to implement new features on all platforms. To use it, just annotate the desired value, variable, function, or class in commonMain by expecting the keyword to tell the compiler that the actual implementation will be in the platform-specific part of the shared module. Then annotate the real implementation with ‘actual’ in androidMain and iosMain.
Navigating the iOS landscape introduces some complexities, yet KMP comes to the rescue. We tap into iOS internal APIs like Foundation, UIKit, AVFoundation, and SwiftUI, and here, KMP lends a hand by allowing us to utilize these APIs through Kotlin. For instance, we opted for AVPlayer. The process is relatively straightforward: we initialize AVPlayer much like on iOS, with the only distinction being the encapsulation within a UIKitView.
In such cases, the Alpha version of Compose Multiplatform introduces a prototype for seamless UI interoperability. With UIKitView, you can easily integrate advanced platform-specific elements such as maps, web views, media players, and cameras into your shared user interface. Conversely, through ComposeUIViewController, you can smoothly incorporate Compose Multiplatform screens into your SwiftUI-based iOS applications, making the transition to Compose Multiplatform a gradual and convenient process. In this case, you can use Compose Multiplatform alongside your current UI layer. You can read more about it in this article: Compose Multiplatform for iOS Is in Alpha.
While KMP excels in facilitating multi-platform development, it's essential to note that iOS compile times might experience a slight increase during KMP app development within Xcode. Therefore, the run-time seems the same as a native app.
Challenges and Debugging
While KMP presents a seamless approach to multi-platform development, it does come with some challenges. Debugging on iOS involves separate debuggers for iosApp and shared components. Setting up Xcode Command line tools and integrating CocoaPods might get complicated. Finding help online (StackOverflow, etc.) can be tough since KMP is relatively new. Finding a solution may require going through public repositories.
Kotlin Multiplatform, now called Kotlin Multiplatform (KMP), offers a revolutionary way to build apps for multiple platforms. It lets you share code and easily connect to internal APIs, making development efficient and cost-effective. However, using it for full multiplatform UI development can be challenging, especially on iOS, where Compose Multiplatform is in its early stages, and tvOS is unsupported. KMP excels at sharing business logic, data layers, and domain models, making testing simpler. We recommend giving it a try and gradually integrating it into your project, starting small, and not needing a major code overhaul. You can begin with one feature or API calls and expand from there to share more code with your fellow developers. The key is that you don't have to rewrite everything at once to benefit from KMP.