More and more people are coding with Kotlin. According to recent MongoDB research, Kotlin is the second-fastest-growing language when it comes to GitHub projects and discussions on Stack Overflow.
It's easy to understand why. Kotlin is pragmatic, modern, and statically-typed. It's winning the hearts of not just Android developers, but backend developers and, increasingly, Java developers too.
Matthew Bartos is X-Team's Kotlin guru. He's been handling Kotlin projects on both the backend and mobile side. In this article, he explores Kotlin's ambition to conquer more platforms.
One Language to Rule Them All
You may think of Kotlin as a Java-like JVM language, but JVM is only one of its available targets. Kotlin can be compiled/transpiled to JavaScript (and work with React) and to native platform code with Kotlin/Native. This means that Kotlin to iOS, MacOS, Linux, Windows, or even WebAssembly is possible.
Kotlin/Native lets you create an executable, a static or dynamic library with C headers, or an Apple framework. It can also use existing C, Swift, or Objective-C libraries.
In this article, I will create an Android + iOS multiplatform project. But you don't have to stop there, because you can do all of the following with Kotlin:
- Android app: Kotlin/JVM
- iOS app: Kotlin/Native
- Backend: Kotlin/JVM & Ktor or Spring Boot
- Frontend: Kotlin/JS & React
- Desktop app: Kotlin/Native
Is This Yet Another Flutter?
No, this is not just another Flutter. I understand that developers have mixed opinions about multiplatform mobile frameworks. While sharing code between platforms is generally considered as time-saving and convenient, a shared UI is only considered a good choice for early-stage projects where designs don't need to be pixel-perfect.
Multiplatform Kotlin is probably the closest-to-native app development approach.
In previous articles, I've written about my experience with Multi-OS Engine. It's a similar concept to Kotlin Multiplatform, but it uses a bundled ART VM for translating JVM code to native iOS code. This means that even a simple iOS app is very heavy. But with Kotlin/Native, you get light, native LLVM iOS bytecode.
Multiplatform Kotlin lets you share the logic between the platforms, not the UI.
The typical Kotlin Multiplatform Mobile (KMM) project consists of 3 modules:
- Android: Native JVM Android app with native Android UI
- iOS: Native LLVM iOS app with native iOS UI
- Common: Pure Kotlin logic, used by both Android and iOS apps
Kotlin Multiplatform Mobile
Kotlin Multiplatform Mobile is a set of tools that make it easier to create Android + iOS multiplatform apps. Just keep in mind that KMM is in alpha development and still somewhat unstable. When you install the official KMM plugin and create a new project, it'll automatically set up these modules:
androidMain
commonMain
commonTest
iosMain
Architecture
Following the MVP pattern, the app should have three layers of code:
- Model, responsible for data repositories like APIs or databases
- View, responsible for displaying data from Presenter and passing input to it
- Presenter, responsible for processing the data for View
You want to keep your Model and Presenter in the shared common
module and implement Views in both Android and iOS, where you want them to stay as small as possible.
Libraries
You cannot use Android's ViewModel
or LiveData
in the shared common
module, because it's written in Java. You also cannot use RxJava nor Retrofit. This module needs to contain pure Kotlin code, which complicates things a little bit.
But Kotlin Multiplatform has a solution for that in the form of two new keywords: expect
and actual
, which can easily be understood as declaration
and implementation
.
In your shared common
module, you can declare classes and functions marked with expect
. Then you need to properly implement them in both your androidMain
and iosMain
modules. For example:
- in
common
expect val uiDispatcher: CoroutineContext
- in
androidMain
actual val uiDispatcher: CoroutineContext
get() = Dispatchers.Main
- in
iosMain
actual val uiDispatcher: CoroutineContext
get() = IosMainDispatcher
IosMainDispatcher
is your custom class, which uses: dispatch_async(dispatch_get_main_queue())
Bonus: Testing
By extracting your logic to the shared common
module, you're now able to write blazing-fast unit tests that can cover a lot of successful and unsuccessful use cases. Use mocked API responses and create scenarios that will cover both platforms at the same time!
In Summary
Kotlin Multiplatform holds a lot of promise, but it's still in its early days.
Pros (+)
- Native performance - no additional runtime in your app bundle
- No legacy code - you can start integrating KMM anytime
- No shared UI - pixel-perfect native views
- It's easy to just add iOS to your existing Android app
Cons (-)
- Not many pure Kotlin libraries available in the shared
common
module - Still in alpha, not production-ready