The State of Kotlin Multiplatform

Alexey Gladkov

Mobile Developer at Tinkoff

In May 2023, the Yuztech Group team organized the Usetech Meetup “Mobile Development Trends” in Tomsk, where experts from the Russian IT market shared their experience. As a result of the event, we wrote a series of articles, each of which is devoted to topical issues and broadcasts the speech of one of the speakers. Let’s start with a speech by Alexey Gladkov, Mobile Developer at Tinkoff.

Many have heard about Kotlin Multiplatform (KMM), but not everyone has tried it. My team and I used it at work, and here I will talk about my experience. Perhaps now you will have an understanding of how to argue for a business why KMM is needed at all and how much of a working story it is now.

First, a few words about myself: my name is Alexey Gladkov, I work at Tinkoff, I teach at the Moscow Institute of Physics and Technology, I have been writing native applications for about 10 years, I run a YouTube channel about mobile development mobile developer.

The report with which I spoke as part of the meetup is called “The State of Kotlin Multiplatform”, since some new features are coming out all the time, and I am supplementing it. For me, this is, conditionally, a digest that I update regularly. Now I will talk about the current state of Kotlin around the beginning of April 2023.

Why even think about a multi-platform approach? In 2015 (the Apple Watch hadn’t even come out yet), we mobile developers were mostly focused on phones. Other developers focused on tablets and laptops. So there was a clear division. By 2023, the situation has changed. Now they can come to me and say: “We want to start on the TV” or “On the clock”. Quite a true story. Further, this trend will only develop – we will have:

  • new operating systems (Harmony, Aurora, etc.);

  • different form factors (stands, scanners, etc.).

For example, they came to us from Leroy with a request to make a stand – a self-service checkout. If you think about it, it’s just a huge screen that looks like a phone. On it, too, oddly enough, Android is spinning, and you can also make applications there.

Also, one of the trends is that people want to work omnichannel and do not want to depend on circumstances. Take, for example, Telegram: we are used to the fact that you can write something there, get in a taxi and continue to chat in it, then chat from your laptop at work, and when you come home, continue from your computer. You live in Telegram all the time and your User Experience is not much different. At the same time, wherever you are, you can open the desired application and continue working.

Therefore, our task is to move from Android or iOS to multi-platform development.

Apple also has a platform development, only it is specific. With it, you can make products for tablets and computers, but for tablets and Apple computers. Accordingly, there seems to be a multiplatform, but there seems to be none.

This raises the question: “Why Kotlin? There is also Flutter or React Native.” When we received a request from Leroy, we began to think about what technology to take.

Flutter Pros:

  • Works on all platforms (iOS, Android, Desktop, Web);

  • A large set of ready-made widgets (Google has done a great job to adapt all the widgets);

  • Google support;

  • Big community.

There are also disadvantages:

  • Dart. He needs to train ALL employees;

  • Lack of native look and feel;

  • Google support. Why did I write down this point in the minuses? Because there is such a “graveyard of Google projects.” Google is famous not only for developing some new things – it also easily digs them in. The same Dart: initially it was a web technology, then Google buried it, and then at some point got it again and resurrected it. Now, accordingly, she lives, but for how long is not clear;

  • No big success stories. I’m talking about mobile bank-level applications with hundreds of developers working on them. There are separate success stories from Yandex, Google, etc. What is special about giant applications? Hundreds of people work on them. Widgets and everything else is great, but when a huge team works on a product, other difficulties begin, believe me.

Let’s move on to React Native. Its advantages:

  • Works on all platforms (iOS, Android, Desktop, Web);

  • Large set of ready-made components;

  • A huge set of ready-made solutions and libraries;

  • Large base of ready engineers.

Minuses:

  • Poor performance;

  • Heavy upgrades;

  • Difficult migration of an existing application.

Now about the pros of Kotlin Multiplatform:

  • Easy integration into an existing application;

  • Large base of ready engineers;

  • A large number of ready-made solutions;

  • Works on Android, Desktop, iOS*, MacOS*. On iOS and MacOS, there are some nuances in the interface;

  • Native look and feel.

Cons of Kotlin Multiplatform:

  • The need to write a separate UI for the platform* (with an asterisk, because this is not a minus for large companies that have their own design systems, libraries, and a lot of things that are made for native UI);

  • fundamental Gradle.

Let’s say a few words about how it works. We have an application: if we take a purely architecture, then this is Data logic, some kind of business logic and a UI layer. You can fumble everything, including the volume of the model in some situations. You can even rummage through navigation, you can’t rummage only the UI itself.

This is an important point: in most cases, you can rummage around 90% of the application. Accordingly, KMM has the concept of SourceSets. Each target has its own SourceSet. Moreover, different SourceSets are also created for different processor architectures. They can be combined with special unifying sources that allow you to simplify certain things. For example, make iOS Main so as not to prescribe some things twice or thrice. But at compile time, all this will fall back and only Command Main and your target will remain. Everything will compile together and get your artifact.

How does it all happen? You have a Kotlin module. The compiler has several stages: Frontend of the compiler, Backend of the compiler, and there in the middle, respectively. In the middle, just the so-called interlude of reprezentative code, from which the Frontend of the Kotlin compiler takes what we wrote, expands and receives intermediate code that can be slipped to various other compilers. As a result, we get quite native .jar and .framework – native artifacts.

Let’s get back to applications. Any application consists of the following components:

We will now go through these components and see what the libraries have:

  1. KMM Awesome. It was made by Konstantin Tskhovrebov from JetBrains. Here you can find all the libraries for KMM. The main condition is that the ones that work on both Android and IOS are collected. On Desktop, at the same time, they may work or not. All libraries are divided into sections, very convenient.

  1. JetBrains made their own library for the network ktor client. Works on IOS, Android, Desktop and Web. It is configured in a declarative Kotlin way, since it is pure Kotlin. We create a Client, it is very easy to install any plugins there:

val client = HttpClient(CIO) {

    install(Logging) {

        logger = Logger.DEFAULT

        level = LogLevel.HEADERS

    }

}

After that, we can pull requests and configure the way you want:

client.get {

    url {

        protocol = URLProtocol.HTTPS

        host = "ktor.io"

        path("docs/welcome.html")

    }

}

Everything is configured declaratively: in those things that you do not prescribe, some default values ​​\u200b\u200bare prescribed. Very flexible and convenient, we switched to Ktor in all Android projects.

  1. SQL Delight – database, also works on IOS, Android, Desktop and Web. This is a Gradle plugin, we write:

sqldelight {

   databases {

       create("Database") {

          packageName.set("tech.mobiledeveloper") 

       }

   }

}

Everything we need is ready. Next, we need to register the SQL files:

// src/commonMain/sqldelight/data/daily.sq

CREATE TABLE HabitEntity (

 itemID INTEGER PRIMARY KEY AUTOINCREMENT,

 title TEXT NOT NULL,

 isGood INTEGER DEFAULT 0

);

Here, some Senior engineers may become stupor, because you have to write SQL files yourself and remember 🙂 This is the only drawback. To do this, you need to install a special plugin and you can again write nothing – very convenient.

  1. Navigation. Who wrote the navigation library? There are a lot of them – I also wrote my own, it seemed to me that it would be useful. Eat decompose is the standard in the KMM world for very large, complex projects. Why for big and complex? Because there is support for all platforms, there is State Saving – this is an architectural component in the first place. This is not quite navigation in the literal sense, but rather a touch on a full-fledged architecture, including navigation. It also has good documentation and works both with and without a declarative.

The disadvantage is that a lot of auxiliary classes are created there. Accordingly, if this is a small project, then you will write more auxiliary classes than enjoy the work. And yes, the downside is that this is not navigation in the literal sense, if you have an architecture, then this component will not suit you.

  1. Voyager – this is also a KMM library, but it is suitable for compose. There is also State Saving, support for ready-made UI components, good documentation. Minus – requires you to create a class for each composable function.

  1. Odyssey is my own library. It is also for compose, works on IOS, Android, Desktop, Web and MacOS. The only difference from Voyager is that you don’t need to create classes here. Of the minuses: poor deeplink support and no support for changing orientation.

  1. Now let’s move on to resources. Libres – it also works on all platforms. All you need is to configure the plugin:

// build.gradle.kts

libres {

   generatedClassName = "AppRes"

   generateNamedArguments = true

   baseLocaleLanguageCode = "en"

}

After that, you work strictly as in Android:

// src/commonMain/libres/strings/strings_en.xml

<resources>

   <string name="app_name">JetpackComposeDemo</string>

   <string name="days_today">Today</string>

   <string name="title_font_size">Font size</string>

   <string name="title_font_size_small">Small</string>

   <string name="title_font_size_medium">Medium</string>

   <string name="title_font_size_big">Big</string>

</resources>

// src/commonMain/kotlin/SomeScreen.kt

Text(

   modifier = Modifier.padding(top = 24.dp),

   text = AppRes.string.compose_success_add,

   style = JetHabitTheme.typography.body,

   color = JetHabitTheme.colors.primaryText

)

For IOS, the only thing that changes is the addition of:

// iOS

AppRes.shared.string.compose_success_add

It’s the same with pictures: you don’t need to do anything, upload pictures and go:

// src/commonMain/libres/images/ic_calendar.svg

// src/commonMain/libres/images/ic_calendar.png

TabConfiguration(

   title = "Daily",

   selectedIcon = painterResource(AppRes.image.ic_calendar),

   unselectedIcon = painterResource(AppRes.image.ic_calendar),

)

In IOS, respectively, like this:

// iOS

AppRes.shared.image.ic_calendar
  1. D.I. Lately I’m not a fan of DI frameworks, but Kodein is a handy tool for not writing DI yourself. Very simple, easy to set up and use.

What about everything else? We talked about navigation, UI, network, database, resources and DI, but we also have firebase, custom systems, analytics, cryptography and so on. This also needs to be somehow wrapped in KMM, and there are no ready-made solutions for this. Here I want to show the way we did it. With it, you can wrap almost any native solution in KMM. For example, we need to do analytics: analytics on firebase, which is not in the KMM libraries. We are making an interface in Common code, this is purely Kotlin code, which has one function – trackEvent (we could insert any other functions here, but we only need trackEvent).

// src/commonMain/kotlin/analytics/…

interface AnalyticsTracker {

   fun trackEvent(event: AnalyticsEvent)

}

interface AnalyticsEvent

Next, we write AnalyticsEvent into a special AnalyticsEventFB:

abstract class AnalyticsEventFB : AnalyticsEvent {

   abstract val name: String

   open var params: Map<String, Any> = emptyMap()

   override fun toString(): String {

       return "AnalyticsEventFB(name="

name", params=" class="formula inline">params)"

   }

}

After that, we create our events, already for specific screens, redistribute the parameters – this is still pure code:

sealed class WebViewScreenEvents(

   override val name: String,

   override var params: Map<String, Any> = emptyMap()

) : AnalyticsEventFB() {

   data class WebApplicationInitialized(...) : WebViewScreenEvents(

       name = "web_app_initialized",

       params = hashMapOf(...)

   )

}

In fact, we have already implemented all the events. We now just need a provider to add this to Firebase. Here, just, we make a special class on the Firebase Tracker platform, in it we simply put our provider and define the track function:

class FirebaseTracker  constructor(

private val application: Application

) : AnalyticsTracker {

 private val firebaseAnalytics: FirebaseAnalytics by lazy { 

FirebaseAnalytics.getInstance(application.applicationContext)

 }

   fun track(event: AnalyticsEventFB) {

	// Send event to firebase here

   }

}

As a result, everything works. And all we had to do for this was to write one single class.

Now let’s get back to the cons of Kotlin Multiplatform, namely, the need to write a separate UI for the platform. This is where Compose Multiplatform comes to the rescue – this is a separate framework. For those who are interested, you can come see.

Compose Multiplatform Features:

  • Works using Skiko (mpp bindings for Skia);

  • Complete copy of Jetpack Compose;

  • Full interop with platforms.

RESULTS:

  • Most things already work out of the box (web, database, navigation, etc.);

  • It is possible to write an application entirely in commonMain;

  • There is a choice when implementing the UI layer (you can use Compose, you can use Native, whichever suits you);

  • Compose is being actively completed (I really hope that by the end of this year we will see a beta version on iOS or even a release);

  • Kotlin is a beautiful, user-friendly language. I think many will agree with me.

That’s all for me. Share your opinions in the comments!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *