The journey of Lunabee Studio with KMM

Anthony Couhier
14 min readAug 2, 2022

--

At Lunabee Studio, we love building native apps, but what we love most of all is building great apps.

But the main issue with doing native apps is that some logic that don’t feel platform specific needs to be done twice, increasing the odds of having differences and also the development time.

To keep doing our lovely jobs and improve our working methods, we watch the Android and iOS sphere to be up-to-date on the latest technologies, best practices, etc… Thus, we couldn’t miss the emergence of Kotlin Multiplatform Mobile.

We already said that hybrid apps are crap, but KMM philosophy could suit us more, as it leaves the UI part out of the scope and promises performances as good as native apps.

Comparison between Native, cross-platform and KMM
Source official website : https://kotlinlang.org/lp/mobile/

We did a first dip of the toe more than a year ago, but the technology wasn’t mature at all at the time. But in the last few months, more and more companies speak about jumping to KMM, and the latest release note looked quite promising.

So we thought it was time to investigate the technology again. We started defining some phased objectives and goals that we would try to achieve through a simple but complete app.

🎯 Objectives

The objectives of Lunabee Studio that KMM could help us reach are :

  • Improve the quality of our applications
    - Since we would share the code base, the code would become more homogeneous between iOS and Android. Allowing us to have the same business logic between the two platforms, for sure.
    - As we only need to write code once, we could improve the development speed drastically (ideally, only UI has to be done twice).
  • Working on cool projects
    - With the development speed improvement, we could have more time to focus on the UI and animation. ✨
    - As we’re geek at heart after all. It’s always a plus to work on cutting edge technology.
  • Building the team
    - Shared code could be written and reviewed by anyone, leading to more interaction between teammates.
    - No longer have 2 separate iOS and Android teams, but gather everyone in one team, even if you’d still have more affinity with one platform.

🥅 Goal

The main goal of this project is to answer one question : can it help us delivery apps faster without degrading the development time and premium quality of our application?

To answer this big question, we asked ourselves smaller questions that could help us have a better understanding on the state of KMM. Questions such as :

  • What are the advantages and disadvantages of KMM?
  • Is it ready for production?
  • If not, what is missing? What do we need to keep a watch on before we can start using it?
  • How much of our code base can we share between platforms?

🧭 Roadmap

To reach our goal and evaluate independently which module could be replaced using KMM, we thought of small projects to test them step by step before doing the main project.

  1. The most simple project to use KMM, a lib with a function to check a string in it
  2. A project to replace the server calls
  3. The third project aim to see if we could replace the local database
  4. Finally, a real app to combine all 3 first projects

Moreover, each project will allow us to answer some questions we have. They will be our checkpoints on each project. And to compare between native and KMM we built 3 projects.

  • Android app
  • IOS app
  • KMM app

🔨 Tools

💻 IDE

👨‍🎨 UI

  • Android — Jetpack compose
  • IOS — SwiftUI

📚 Library KMM

🗒 First project

The goal of this project is pure logic. We wanted to know how can we use KMM to do pure logic like test a string to know if it’s a valid email address

Checkpoints

❓ How to debug KMM lib on Android and iOS ?

❓ How long did it take to set up the whole thing ?

❓ Can we create a template to be faster next time we have to set it up ?

❓ Will it work on multiple repository ? (sub-module ?)

❓ Impact on application size and build time

The first project is not rocket science, we just have to do like always, write a function !

KMM — Business logic

Android

Android manages to use it correctly with the String extension

Android — Use KMM business logic

IOS

IOS cannot use string extension because Kotlin native compiler is not advanced enough to handle class extension. Each function/extension that is not inside an object is placed at compile time in an object with the name of the files where the function is located. That’s why checkEmail has been put in an object.

IOS— Use KMM business logic

📊 Checkpoints

How to debug KMM lib on Android and iOS ?
On Android, there is no change, However, to debug Kotlin Multiplatform mobile on Xcode we have to download xcode-kotlin by Touchlab or use Kotlin Multiplatform Mobile for AppCode by JetBrains on appCode.

How long did it take to set up the whole thing ?
We had no difficulty in preparing the project, it was fast.

Can we create a template to be faster next time we have to set it up ?
It’s quite possible to create a template. In addition, there is already some template like moko-template or KaMKit.

Will it work on multiple repository ? (Submodule ?)
The advantage of KMM is to be free. You can have a repository with 3 part inside (androidApp, iosApp and KMM module) or you can have 3 repository (Android, IOS, and KMM repository) and import it.
On Android we can import it via Submodule or via dependencies (mavenCentral, Google, …)
On IOS, we can import it via Submodule, CocoaPods or SwiftPackage

Impact on application size and build time
Android
- Size There was no change in terms on size
- Build time It was the same duration
IOS
- Size It was much heavier with KMM. We have moved from 140 KB compressed, 290 KB uncompressed in natif to 489 KB compressed, 1.7 MB uncompressed in KMM
- Build time It was longer, we went from 2 to 5 seconds

🗒 Second project

The goal of this project is Network logic. We wanted to know how can we use KMM to do HTTP call like fetch some user from our API

Checkpoints

❓ performances/energy consumption? 📲

❓ timeouts? 📲

❓ Session manager (Connection pool)? 📝

❓ certificates pinning? 📲

❓ files download/upload? 📲

❓ background requests (iOS)? 📝

❓ Impact on application size and build time

To complete our goal on this project, we used Ktor by JetBrains because it is the most popular and is developed by the creator of KMM. In this project, we discovered the expect/actual keyword. The expect/actual mechanism is a way to have access to plateform-specific APIs. With this mechanism, the common source set defines an expected declaration, and platform source sets (Android, IOS, …) must provide the actual declaration that corresponds to the expected declaration. We have created an application that retrieves and displays a list of users from our API.

Creating our engine

To do HTTP call we have to create an engine. This engine is specific to the platform so we’re using the expect/actual keyword.

  • KMM
KMM — Expect client engine
  • Android
    We chose OkHttp as engine because we already know and use it.
Android — Create Ktor client engine (OkHttp)
  • IOS
    We chose Darwin because of its target (macOS, iOS, tvOS)
IOS— Create Ktor client engine (Darwin)

Creating our client

Once our engine was created, we could create our client with some options like transform Json to model, logs and expect success, there are many other options.

KMM — Create client

Do the HTTP network call

Our last step is to make an HTTP network call on our API.

KMM — Use Ktor client

We used the @Throws(Exception::class) annotation to Say “Hey this function gonna maybe throw something”. We must implement it because of IOS, without this annotation, if the function throw something the IOS app gonna crash (even with a try/catch 😱). IOS is not able to catch a throw from KMM if it is not specified. To use the HTTP GET method, we use its get method with our API path and get its body. The body method gonna transform the Json result into our model.

How to use it ?

  • IOS
    We used withCheckedContinuation()to convert completion handlers into asynchronous functions. getUsersList() is a suspend function that can be seen as an asynchronous function in Swift, so we need a way to listen the suspend function. We did it with withCheckedContinuation(). A suspend function is used with a completion handler to get the result. The Native Kotlin compiler has compiled this function so that if the function returns a value, it’ll be in the data variable and if it throws something, it’ll be in the error variable.
IOS — Use suspend function
  • Android
    On Android side it’s much easier, we just have to try/catch the getUsersList() function to get the result.
Android — Use suspend function

📊 Checkpoints

  • performances/energy consumption? 📲

Android

  • Natif
Android (Natif) — Performance
  • KMM
Android (KMM) — Performance

IOS

CPU

  • Natif
IOS (Natif) —CPU
  • KMM
IOS (KMM) — CPU

Energy

  • Natif
IOS (Natif) —Energy
  • KMM
IOS (KMM) — Energy

Memory

  • Natif
IOS (Natif) —Memory
  • KMM
IOS (KMM) — Memory
  • timeouts? 📲
    It is possible to define a general timeout for both OS. We must define it when we create our engine

Android

KMM (Android) — Ktor client with Timeout config

IOS

KMM (IOS) — Ktor client with Timeout config
  • ❓ Session manager (Connection pool)? 📝
    We can configure our engine with all the options offered by the object from the OS. So yes, there is a way to configure Connection Pool.
    IOS-NSURLSession | Android — OkHttp
  • ❓ certificates pinning? 📲
    We can configure our engine with all the options offered by the object from the OS. So Yes, there is a way to configure Certificates Pinning.
    IOS — NSURLSession
KMM (Android) — Ktor client with certificate pinning

Android — OkHttp

KMM (IOS) — Ktor client with certificate pinning

files download/upload? 📲
It is possible with Ktor. Upload / Download

background requests (iOS)? 📝
Same limitation as the OS. Possible to use Extend background Idle mode

Impact on application size and build time

Application size and build time

🗒 Third project

The goal of this project is to handle Local database logic in KMM.

Checkpoints

❓ performances/energy consumption ?

❓ Database encryption ?

❓ Impact on application size and build time

To complete our goal, we used SQLDelight because it is the most popular and used by JetBrains on its samples. We have created a note application where we can write a note, save it, view it and delete it.

Creating our DB

The first thing is to create our table and functions to exchange with the database

SQLDelight create table

Connect our DB to the app

  • KMM
Expect database module
  • Android
Android — actual database module
  • IOS
IOS — actual database module

We implement Koin to do dependency injection to help us meet the different requirements. Expect a module is much easier and understandable than expect the database or the driver.

Use the database

  • KMM
KMM — Create note repository

To use the database we have to use noteQueries which is created by SQLDelight at compile time. Each of our function that we have created above are inside the noteQueries.

  • IOS
    On the second project we got to use withCheckedContinuation() to manage suspend or flow function. This solution is a bit boilerplate, so we tried to use KMP-NativeCoroutines.
    This new library provides a new way, less boilerplate and a cancellation job. In this example, it create the function getNotesNative() from getNotes().
IOS — Use suspend function
  • Android
    Nothing change.
Android — Use suspend function

📊 Checkpoints

performances/energy consumption ?

Android

  • Natif
Android (Natif) — Perfomance
  • KMM
Android (KMM) — Perfomance

IOS

CPU

  • Natif
IOS (Natif) —CPU
  • KMM
IOS (KMM) — CPU

Disk

  • Natif
IOS (Natif) —Disk
  • KMM
IOS (KMM) — Disk

Energy

  • Natif
IOS (Natif) —Energy
  • KMM
IOS (KMM) — Energy

Memory

  • Natif
IOS (Natif) —Memory
  • KMM
IOS (KMM) —Memory

Database encryption ?
There are libraries to do encryption: Cipher Librairies There is also SQLDelight with SQLCipher

Impact on application size and build time

Application size and build time

📱 Complete app

The goal of this project is to create a complete app. We wanted to know how much KMM can help us in a real project

Checkpoints

❓ What version of IOS and Android can we use ?

Our goal on this application is to try to mix everything we have done so far and more. We have made a very simplified version of Tinder. During our research, we discovered moko-resources. It is a cross-platform Kotlin library that provides access to resources on iOS and Android with the support of the default system localization.
During this project we used:
- Ktor
- HTTP client
- SQLDelight
- Manage local database
- Koin
- Dependency injection
- Kermit
- Log
- moko-resources

🏗 Setup

HTTP Client

Like in the second project, we created an engine with the expect/actual mechanism. However, this time we set the timeout to 5 seconds.

KMM — Create Ktor client engine

Then with our engine, we created our client

KMM — Create Ktor client

Then we put it in a koin Module to get it easily and to created it once.

KMM — Network module (Koin)

Local database

Create

Like in our third project, we have created our database.

KMM — Create SQLDelight database

Link database to the app

With our database created, we were able to connect our database to our application.

KMM — Create database module (Koin)

Helper with SQLDelight

To help us with SQLDelight we created a helper to use the SQL function. We did this so that we don’t have to manage the queries and dispatch them every time.

KMM — Create SQLDelight helper

We put this helper in the user module.

KMM — User module (Koin)

Koin

The last thing to do is to manage all our data modules (HTTP and local database). This function is called when the application is launched. The app is able to enable network log and to add some module.

KMM — initKoin

📭 Use case

To fetch data, like in the second project we use client.get() with some query. We use the body method to transform the Json response to our model. Moreover, we use a try/catch to get error like timeout, response error, … To use the database we use the helper.

KMM — Use Ktor client

All there is to do is to call those function to do an action like retrieve the last user without a like/dislike.

KMM — Use case

💬 Share resources

Our last step share the resources via KMM module. We use it to share English and French string. There is no difficulty to implement it with Android. On the other hand, with IOS… there were many problems. At first, we thought that we didn’t have to use the Info.plist, we thought that putting the data correctly in Xcode would be enough but no way ! After a while we created our Info.plist and everything worked ! We could change the language between French and English.

🧪 Unit test

One of the advantages of KMM is that it saves us time that we can put into unit testing.

KMM

Once again, the expect/actual mechanism help us. Thanks to it, we can test everything as our DB. To test a suspend function, we use runTest {}

KMM — Unit test

Moreover, we can test them on the target that we want like Android or IOS.

KMM — Unit test OS

IOS

KMM (IOS) — Database unit testing

Android

KMM (Android) — Database unit testing

📊 Checkpoints

❓ What version of IOS and Android can we use ?
The libraries tell us the minimum version we can use, they are the ones that set the limit.

👀 Observation

At first glance, KMM appears to be very powerful. What we noticed in these projects is that it does not limit, it is versatile, thanks to the expect/actual mechanism. Moreover the performance seems to be equal.

About Android, There is no difficulty to implement it. Except for the libraries, nothing changes.

Concerning IOS, the Kotlin/Native compiler does not use the full power of Swift (Like enum or extension), and, we think, that the way to use suspend or flow function is a bit boilerplate even if the KMP-NativesCoroutines provides less boilerplate code and cancellation support.

Here is some trouble that we got:

Developing on iOS: It can be quite challenging to develop the KMM part and iOS compared to the Android one. Debugging is also quite tedious even with the plugin for Xcode. AppCode is a good replacement but it has it’s trade-off too.

Throw something: At the beginning we wanted to throw an error and get it back on the native app side. We tried, on Android there was no problem, but on IOS there was a crash, we could not catch the error. So after some research we find the @Throws(Exception::class) that corrected our mistakes.

Memory management: we had to use the new Kotlin/Native Memory Manager. We had some problems with variable freezing, like changing a variable we got from KMM.
The comes from an experimental feature that has yet to reach Beta

# https://github.com/JetBrains/kotlin/blob/master/kotlin-native/NEW_MM.md
kotlin.native.binary.memoryModel=experimental kotlin.native.binary.freezing=disabled

Android studio commonMain- expect/actual fix

# https://kotlinlang.org/docs/multiplatform-hierarchy.html kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.native.enableDependencyPropagation=false

🔬 Assessment

KMM

On the level of performance, it is irreproachable.

On the portability level, it is very versatile, you can use it to implement what you want (Data layer, Data + Domain Layer, Network module, local module, pure logic, ….). In short it is really modular.

On the Community level, it is small but invested.

In terms of libraries, there are a few (just the most useful ones), most of them are in alpha or beta phase.

Unit test has some trouble, Android Studio Chipmunks doesn’t find actual from Android unit test KMM folder (androidTest)

  • 💡 fix Use the Android Studio Dolphin to fix this bug.

Android

No issues detected

IOS

A function that is not in an object in the KMM module, is put in an object created made from the file where it is.

the Kotlin/Native compiler does not allow to benefit from the full power of Swift.

The native function to use suspend or function is a bit boilerplate

We must to use xcode-kotlin if we want to debug the KMM part on xcode

Conclusion

As impressive as the evolution of KMM has been this past year, we still think KMM is not ready to be used to build a whole Android and iOS app for production just yet. JetBrains knows it too as the SDK is still in Alpha version even though they advertise a lot on it (Probably to get a maximum of feedback from the community).

With that being said, we’ll keep an eye on its evolution as it looks really promising and we most certainly will be using it for developing small libraries that has a restricted perimeter and can be easily tested.

So this is a case to follow very closely!

--

--