Why you should use Kotlin Multiplatform Mobile
Kotlin is a programming language initially based on the Java Virtual Machine. Widely used in the Android world, it has evolved and now supports many other platforms through the Kotlin Multiplatform SDK also known as KMP.
KMP is a framework that allows developers to use Kotlin to develop projects or features that can run on the most used platforms:
- Android
- iOS, macOS, watchOS, tvOS
- Windows
- Linux
- Web
In this article, you will learn about the mobile part of KMP which is called Kotlin Multiplatform Mobile, also known as KMM.
Table of contents
How do I know if I should use Kotlin?
Wait! Kotlin was made for Android developers, right? I’ve been using Swift for years. Is this article really for me?
Yes, it is! Kotlin has a very low learning curve for iOS developers. Both Kotlin and Swift are built on top of the modern programming approach and software design patterns.
What are KMM’s supported platforms?
KMM is supported on Android, iOS, and Web (Javascript).
So is this yet another cross-platform mobile framework?
Yes and no! KMM is about cross-platform code sharing of course, but unlike Cordova, Flutter, or React Native it doesn’t provide UI. The UI development is delegated to the platform code: Android will still use native Jetpack Compose, iOS will still use SwiftUi and web will still use frontend frameworks like React or Angular.
Ignoring UI makes KMM easy to integrate into an existing stack, without needing all the mobile developers to learn a new framework, often associated with a new language.
KMM shares business logic and domain code
Companies with mobile development teams (Android and iOS) often have multiple existing projects. Sometimes one of those projects calls for implementing a new complicated algorithm in the application. Without KMM, both Android and iOS teams would need to separately implement this algorithm, write tests for it, integrate it into their projects, fix any issues, and add new features. With KMM a lot of work can be mutualized. The algorithm code only needs to be written, tested, fixed, and maintained once.
Some of the benefits of KMM include:
- The development phase work is shared between mobile teams
- The algorithm runs the same way within all mobile apps
- Corner cases are handled the same way
We have several shared algorithms written in C++, but recently we are increasingly using shared Kotlin code. It is easier to approach and is usually preferable when performance is not critical. In addition, more developers can contribute, not just our C++ experts.
Okay, so KMM is good for sharing models and basic classes, but can I collaborate on a whole feature with different teams?
Yes, you can! KMM is not only about data classes and loops. The official KMM team and community are constantly releasing production-ready libraries that enforce the power of the framework.
A lot of the work in your project (or feature) layers can be shared, including:
- HTTP clients can consume APIs in a common way (for example Ktor is compatible with all KMM-supported platforms and so is the official Serialization library).
- Persistence is also covered by several libraries, offering the ability to write a complete platform-agnostic database-to-model module. SQLDelight is a candidate among others. For simple preferences (key/value persistence), Multiplatform-settings is compatible with all platforms.
- OKIO is a fully KMM-compatible file system abstraction library.
- Reactivity is no exception (Reaktive, RxCommon)
- Dependencies injection has multiple choices: Kodein, PopKorn, …
- Even Bluetooth Low Energy!
- And also many everyday elements of a project: date management, testing, analytics, and many more.
Additional libraries may or may not be compatible with all the KMM-compatible platforms (Android, iOS, and Web), but the ones listed above are.
Looks powerful, but what if any small piece of my feature/project is not compatible? I assume that means I can’t use KMM?
Fortunately, KMM projects are organized around a shared source set, but they can also access platform-specific APIs or code.
The expect / actual mechanism has been created for this purpose: the shared source set can declare a method or an object without providing the implementation. Then, platform-specific source sets will be responsible for providing the actual implementations of the expected declaration.
This works for most Kotlin declarations, such as functions, classes, interfaces, enumerations, properties, and annotations.
Rules for expected and actual declarations:
- An expected declaration is marked with the
expect
keyword; the actual declaration is marked with theactual
keyword. - expect and actual declarations have the same name and are located in the same package (have the same fully qualified name).
- expect declarations never contain any implementation code and are abstract by default.
Quoting KMM documentation
“Use expected and actual declarations only for Kotlin declarations that have platform-specific dependencies. Implementing as much functionality as possible in the shared module is better, even if doing so takes more time”
Can we finally see some code?
First of all let’s start with a basic function to generate a random UUID.
Here we first declare the expected side in the shared source set. Every actual declaration will have to be in the same com.example.kmm package. Like an interface method, an expect method has no body.
1// Common source set
2package com.example.kmm
3
4expect fun randomUUID(): String
Then you add the native declaration. Android actual declaration uses native UUID
…
1// Android source set
2package com.example.kmm
3
4import java.util.UUID
5
6actual fun randomUUID() = UUID.randomUUID().toString()
… and iOS actual declaration uses native NSUUID
1// iOS source set
2package com.example.kmm
3
4import platform.Foundation.NSUUID
5
6actual fun randomUUID(): String = NSUUID().UUIDString()
Wait! I’m an Android developer, from what I understood, I need to use the expect / actual mechanism in order to have some view state data classes, because @Parcelize
doesn’t exist on iOS…
In Android, you can easily serialize and deserialize classes using Parcels. In Kotlin, a serializable class extends the Parcelable
interface and is annotated with the Parcelize
annotation. This is a pure Android concept and using it in the shared source set will of course lead to a compilation error, as the shared code must be platform-agnostic.
In this case, how can you get shared parcelable classes? You do need expect / actual but not on the data class…
Step 1: The Parcelable interface
In order to use the Parcelable
annotation in the shared code, we have to declare a shared version of it. The shared version of Parcelable
will use the expect keyword as follow:
1// Common source set
2package com.example.kmm
3
4expect interface KmmParcelable
In iOS we just don’t care about Parcels, so the declaration is pretty straightforward:
1// iOS source set
2package com.example.kmm
3
4actual interface KmmParcelable
For Android, we will not declare a new interface as we want to use the real one, so we provide the Android actual declaration as a simple typealias:
1// Android source set
2package com.example.kmm
3
4import android.os.Parcelable
5
6actual typealias KmmParcelable = Parcelable
We can now use @KmmParcelable
in the shared source set.
Step 2: The Parcelize annotation
This is based on the same principle, except that we don’t have to provide the iOS actual declaration, thanks to KMM’s @OptionalExpectation
annotation.
This annotation is only applicable to expect annotation classes in multi-platform projects and marks that class as “optional”. Optional expected class is allowed to have no corresponding actual class in all the platforms.
Let’s start with the shared part, target and retention are the same as the @Parcelize
annotation’s ones:
1// Common source set
2package com.example.kmm
3
4@OptIn(ExperimentalMultiplatform::class)
5@OptionalExpectation
6@Target(AnnotationTarget.CLASS)
7@Retention(AnnotationRetention.BINARY)
8expect annotation class KmmParcelize()
Then the Android actual declaration:
1// Android source set
2package com.example.kmm
3
4import kotlinx.parcelize.Parcelize
5
6actual typealias KmmParcelize = Parcelize
Thanks to the OptionalExpectation
annotation, we don’t need to provide an actual declaration for iOS.
The resulting data class is fully KMM-compatible, and lives in the shared source set:
1// Common source set
2package com.example.kmm
3
4@KmmParcelize
5data class ViewState(
6 val someVal: Any
7) : KmmParcelable
Conclusion
We have seen that Kotlin Multiplatform Mobile is a great tool for sharing code between several mobile platforms. It also offers the ability to delegate some pieces of code to native implementations, while keeping the core logic of a multi-app project into a single source set.
A lot of libraries are available, the shared code doesn’t have to be simple model classes. It can be a real feature that only needs to be developed once.