Kotlin Multiplatform Mobile: Part 2— The How

Daniel Dimovski
10 min readDec 7, 2022

In the previous article we talked about the whats and the whys of Kotlin Multiplatform Mobile. Now that we know what KMM is and why we should use it, it is time to talk about how it works. In this article I will guide you through the process of creating a mobile application for both iOS and Android by using Kotlin Multiplatform Mobile.

Note: This article is going to be more technical and way longer than the previous part, because there is so much to talk about when it comes to KMM (and this doesn’t even begin to cover it!)

For that purpose, I will dust off one of my old applications - Chess Clock. In chess, especially in high-speed games, opponents use a clock to speed things up and compete on a higher level. In essence, this is what the app helps the players with. The application was written in Kotlin and was only targeting Android devices. But with the powers of KMM, we will convert it to an app that will target both iOS and Android.

Getting Started

In order to start working with Kotlin Multiplatform Mobile, you will need a few things:

If you are an Android developer, you’ve already got most of this ready to go (if not, you’ve got some setting up to do!). The only thing you probably are missing is the KMM plugin, but that’s easy. Go to Preferences -> Plugins & search for Kotlin Multiplatform Mobile. Install the latest version, and you are ready to go!

While you are there, I suggest you also install the SqlDelight plugin. We will use SqlDelight for persistence, as it works perfectly well with Kotlin Multiplatform.

Creating a KMM project is really simple after installing the plugin, when creating a new project in Android Studio, you will see the option to create a Kotlin Multiplatform app. Select this option and the rest is pretty much the same as creating a regular Android project, with a few differences. More about that can be found in the official guide.

The structure

Let’s take a look at how the newly created project is structured. To do that, let’s switch to ‘Project’ view in Android studio.

You will notice the three main folders here that represent the essence of KMM: shared, androidApp & iosApp. I believe the names of the folders speak enough about their contents. Android specific code goes in the androidApp folder, iOS specific code in the iosApp folder and the shared code goes in, you guessed it, the shared folder.

If you see the contents of the shared folder, you will notice several modules. Some of them being commonMain, androidMain & iosMain. As I mentioned before, the shared code goes here, to be more specific, in the commonMain. But if you need to access any platform specific APIs, you can do that in the platform-relevant folder.

From Kotlin to Kotlin Multiplatform

In order to speed up the process and not go into too many details about setting up the environment, but rather talk about the actual process of making a Kotlin Multiplatform application, I will utilise Touchlab’s starter kit, which makes for a nice starting point when developing a Kotlin Multiplatform application. After removing some of the placeholder code, it’s time to get our hands dirty.

The ChessClock Android application was separated in two modules: app and core. Core mostly contains the business logic, while the app module contains the framework-specific stuff. And this makes a great starting point for the KMM transition — we already know what is going where. Obviously the contents of the core module will be shared between both platforms, while the platform specifics will go in the android folder.

Shared code

After moving the contents, there are some problems. For example, the TimeExtensions.kt file makes use of JDK-specific code, like the TimeUnit class, or the String.format() method. Looks like I will have to change the implementation to be in pure Kotlin. In this case, the change was easy due to the simplicity of the logic contained in this extension function.

Takeaway #1: You can’t use JDK-specific APIs in the shared code.

We have the models, use cases and repositories in the shared code. Great! But, I also want to share the business logic — I don’t want to implement the rules of the game separately for each of the platforms. That means moving the ViewModel to the shared code, too. This causes several problems:

First and foremost, the ViewModel class is not available in the shared module, because it is an Android-specific class. And here we will make use of one of the coolest features of KMM — accessing platform specific APIs. The starter kit already has the ground works laid out, but let’s take a better look and try to understand how it works.

In the shared/commonMain, we’ll create a ViewModel class:

Make note of the expect modifier before the class name. This modifier is native to KMM and indicates that this class expects to be implemented in platform-specific code. We are just providing the signature here & leave the actual implementation to the platforms.

Let’s provide an android implementation in shared/androidMain

And here, we can notice the actual modifier that indicates that this is the actual implementation of the class for this platform. We can actually access Android-specific APIs and classes, so we can make use of the androidx.lifecycle.ViewModel. Pretty convenient.

Let’s create an equivalent ViewModel for iOS:

Takeaway #2: Use expect/actual modifiers to access platform-specific APIs from the shared code

Now that we have the ViewModel class available for both platforms, we notice another problem. The ViewModel uses LiveData, which is Android-specific and not suitable for multiplatform. We can use Flows instead. So, let’s change our implementation to use flows, it’s easy! While there, I also moved around some of the logic from the ChessGame model to the ViewModel, to make the ChessViewModel the single source of truth.

Takeaway #3: Use Flows instead of LiveData when working with KMM

Next, dependency injection! The original application uses Dagger, which again, is not available for Kotlin Multiplatform (Dagger operates on the JDK, which is not very multiplatform-y). What are our alternatives? Dagger Hilt? Of course not, it’s built on Dagger. Next? Koin.

Yes, Koin works perfectly well with Kotlin Multiplatform, since it’s 100% written in Kotlin. You can use Koin Inject as an alternative. I prefer Koin, so I will use it.

Define the common dependencies in the commonMain module & initialise koin with them. If you need platform-specific dependencies, use expect/actual implementation:

And provide the actual implementation in the corresponding androidMain or iosMain module:

And if you need to provide a dependency from the non-shared modules (app/ios), you can use the appModule parameter of the initKoin method:

And for iOS this requires a bit more work. First, we need to define a method in Kotlin, that will be called from iOS and add the dependencies we want to instantiate as parameters. Then we call the initKoin method and instantiate the appModule.

Now we need to provide these dependencies from Swift and actually call the method we defined above:

And finally, we will call this function in theAppDelegate’s application function.

Takeaway #4: You can’t use Dagger(/Hilt) for DI — use Kotlin-based dependency injection framework instead

In the original ChessClock app, I’ve used Room for persisting data. That is a good choice, as long as you are targeting an Android-only application. I’ve said this several times now and I will repeat it again — we can’t use Android-specific libraries for Kotlin Multiplatform. So, what to use instead?

I already hinted at SqlDelight at the very beginning of this (long) article (about an hour ago) & now it’s time to bring it up again. We’ll use it for our persistence layer. You might have already heard about SqlDelight before, since this is not a particularly new technology — in fact it has been around since 2016. But it is really starting to gain traction with Kotlin Multiplatform. It is totally compatible with KMM & pretty easy to work with. SqlDelight is kind of the opposite of Room — in Room you create the model and (some) SQLs are created for you automatically, while with SqlDelight you write the queries, and get the models in return. I won’t go in details on how to setup SqlDelight, as the official documentation does a good job at that. You can find it here. But, don’t worry, you can find the complete codebase at the end of the article, where you can see how exactly it is implemented by example.

Takeaway #5: Use SqlDelight for the persistence layer

With this, we have our shared layer. There was quite some configuring to do, but it’s not too bad. Now, let’s try to use it!

Native UI

We’ll start with the iOS code and we’ll use the native iOS framework SwiftUI. But, before we proceed, I have to warn you that I haven’t used SwiftUI before and I apologise to iOS developers in advance for what you are about to see. I’ve just applied my knowledge from Jetpack Compose to make this (barely) work.

iOS

The application has three screens: game picker screen, a screen to create/edit a game and a game screen (presented at the end of this article). For simplicity sake, I will walk you through the process of creating the ChessGameScreen, which is where the user can use the chess clock. First, we create an observable model to use in the UI.

We only need the ChessGameViewModel from the shared code. It exposes everything we need for the UI — the state & the methods. we don’t have to bother about their implementation. And getting the ViewModel is a piece of cake:

let chessViewModel = KotlinDependencies.shared.getChessGameViewModel(game: game)

From here, we just expose the viewModel’s methods to the view by defining the corresponding functions. Now, let’s create a view to utilise this model:

This is the basic screen. It has two ChessGamePlayerComponents that represent the two players in chess. Let’s create a view for these components:

These two components will show the remaining time and the number of moves made by a player. As shown above, we are getting this data from the ViewModel in the shared code and presenting it — that’s all. The ViewModel will take care of the business logic.

The important thing to note is the .activate() callback on the observable object when the screen appears — we need to call it to initialise the model.

Another interesting thing to take notice of is the TimeUtilsKt.formatTime() — I already mentioned the TimeUtils before and if you remember, this is a utility class in the shared module. But, it is named TimeUtils in Kotlin…

Takeaway #6: You need to add the Kt suffix to the Kotlin class name in order to access it from the iOS application

In order to keep this article readable, I will stop with the swift code here — you get the gist — use the view model to get data and trigger events & create the UI the way you want it to without worrying about what’s happening under the hood. What we are still missing in the code examples here are the play/pause/reset buttons, but you can check out the git repo to see the full code.

Android

For Android, we are going to use the native UI-framework Jetpack Compose. It is rather similar to SwiftUI, but it’s even easier to use with KMM. Let’s take a quick look at the same screen, only in Compose.

No need to define a new model or anything, we just initialise the ViewModel in the Composable method & observe the chessGameState from the ViewModel. We have our state, now let’s create the components.

Takeaway #7: Use the shared code in an Android application as you would use Android native code — no special configurations are needed

I am omitting the rest of the screen for the same purpose of maintaining readability in the article, but once again, you get the idea — use the state from the ViewModel to render the UI, use the callbacks to trigger events. Works like magic!

The Result

So, there we have it, one shared codebase for the business logic and two native UI frameworks to make everything look & work properly on each platform!

Two fully-functional applications, one per platform. They both look different on purpose — to showcase that you can do any kind of UI per platform using the same shared business logic (and partially because I was too lazy to recreate a similar picker screen on iOS).

iOS

Android

Phew, this was one long article! If I kept your attention to this point, I assume you are interested in Kotlin Multiplatform :)

And if you scrolled all the way down here just to see the result, here’s a link to the repo:

And another link to the original application for Android, so you can compare:

Feel free to clone the repository and try running the code. If you have any suggestions or see any room for improvements, feel free to let me know, I am still learning KMM myself and would appreciate any feedback.

--

--