Sharing Application Logic Using Kotlin Multiplatform

Danail Alexiev
October 5, 2022
Read: 5 min

What is Kotlin Multiplatform?

Ever since it was first introduced, Kotlin’s ability to target multiple different platforms has been one of its key benefits. With this approach the time spent implementing and maintaining the same code on multiple platforms can be greatly reduced, while still retaining the flexibility and benefits of native programming such as best-in-class performance, tailored APIs and common UI elements among others.

Kotlin Multiplatform is a tool with multiple potential use cases:

  • Sharing application logic between Android and iOS applications using Kotlin Multiplatform Mobile (a subset of Kotlin Multiplatform targeting the Android and iOS platforms);
  • Sharing application logic between mobile, web and/or desktop applications;
  • Sharing logic between the server side and the client side running in a browser. Kotlin/JVM is used to implement the server side and Kotiln/JS to implement the client app;
  • Creating multiplatform libraries that can be used in other Kotlin Multiplatform projects and applications;
  • Creating CLI tools for multiple target platforms.

To achieve all of this, Kotlin Multiplatform relies on some basic components that work together:

  • Common Kotlin can run on all platforms. It contains the language, core libraries and tools;
  • Multiplatform libraries help reuse common multiplatform logic between the common and platform-specific code. Common code can depend on libraries to handle common tasks like HTTP communication, serialisation, database access, managing coroutines, etc;
  • The platform-specific versions of Kotlin (Kotlin/JVM, Kotlin/JS, Kotlin/Native) are used to provide interoperability with host platforms. Platform-specific versions contain Kotlin language extensions, platform-specific libraries and tools;
  • Platform-native code (JVM, JavaScript and Native) can be accessed and used through the platform-specific versions.

What is Kotlin Multiplatform NOT?

It is vital to understand that Kotlin Mutliplatform is NOT a cross-platform app development framework like React Native or Flutter. Kotlin Multiplatform does not provide a way to share UI between platforms and its creators recommend using native UI as a best practice. Although Kotlin Multiplatform Compose is picking up speed, it is a separate project and is not mandatory to use with Kotlin Multiplatform.

Kotlin Multiplatform vs React Native

The difference between Kotlin Multiplatform and React Native is that React Native provides an engine to execute JS code and uses a middleware (bridge) to interact with the platform code. Kotlin Multiplatform is NOT an engine to execute Kotlin on different platforms; instead it directly produces platform specific-code.

Kotlin Multiplatform vs Flutter

When comparing Kotlin Multiplatform to Flutter, the similarities are obvious. Both frameworks produce native code: Flutter compiles to C\C++, and Kotlin Multiplatform is compiled to JVM bytecode for Android and to C\C++ for Native (iOS, macOS, watchOS, etc). The difference is that Flutter does its own UI rendering and Kotlin Multiplatform does not provide any way to share UI out of the box.

Most importantly, Kotlin Multiplatform is NOT a silver bullet. All pros and cons should be carefully weighed before moving forward with it. If the overhead of introducing Kotlin Multiplatform is greater than the benefits that come with it in a specific situation, then maybe it is not a good fit.

What should you share?

Every application, no matter how complex, can be broken down into three main layers: data access layer, business logic layer and presentation layer.

In a typical scenario, the data access layer and the business logic layer across different client applications have the same requirements: all clients use the same data sources, the business rules are the same and all of these must be covered by appropriate unit and integration tests. This makes the data access layer and the business logic layer ideal candidates to be shared using Kotlin Multiplatform. You can implement them once, have a single test suite to validate the implementation and get rid of the nasty bugs dealing with diverging implementations of the same feature on different platforms.

The presentation layer is where different platforms differentiate themselves the most. Usually, the presentation layer is broken down into different components according to an MV* architecture – MVC, MVP, MVVM, VIPER, etc. All the UI definitions, the UI rendering are highly platform-specific and that makes them incredibly hard to share. The Kotlin Multiplatform creators advise against using it to share the presentation layer, but there are also exceptions.

If we have an MVP architecture, we can define the component contracts in a common module and, if the presenter implementation depends solely on the view interface, then it can be shared as well. Contrary, if MVVM is used, view models are going to use very different mechanisms to expose the observable state to the view (LiveData/StateFlow on Android, Combine on iOS, RxJS on the web, etc). Trying to share that using Kotlin Multiplatform is not impossible, but it would be very challenging and intrusive, forcing developers to use non-platform idiomatic technologies.

Each application is different, so there is not a one-size-fits-all approach to deciding what to share. This should be decided after a careful evaluation of the requirements. My personal recommendation is to strive to share the data access layer and business logic layer and leave the presentation layer native.

A typical Kotlin Multiplatform project tech stack

Kotlin Multiplatform is getting more mature every day and there are already some libraries and frameworks that are considered as a standard and can be found in almost every multiplatform project out there. Some of them include:

  • The Kotlin standard library: contains the essentials for everyday work with Kotlin;
  • Ktor: a multiplatform HTTP client (also supports server side developments targeting Kotlin/JVM);
  • Kotlin Serialization: a library that deals with JSON serialisation / deserialisation;
  • SQLDelight: a framework for managing local persistence targeting; Kotlin/JVM, Kotlin/JS and Kotlin/Native;
  • Kotlin Coroutines: an approach to dealing with concurrency;
  • kotlin-test: a library that supports implementing multiplatform unit and integration tests.

Implementing common code

The source code in a Kotlin Multiplatform project is distributed in different modules. The commonMain module contains the common code that is shared across all target platforms. The common code cannot depend on platform-specific code directly and can only depend on multiplatform libraries. The platform-specific code is located in the platform-specific modules (androidMain, iosMain, jsMain, etc). Finally, the platform-specific modules depend on the common module.

Handling HTTP communication

When dealing with HTTP communication in common code, using Ktor has become a defacto standard. It provides a configurable HTTP client and a wide variety of plugins that support all the basic functionality that might be required. Additional customisation can be done using request and response interceptors.

Ktor has per platform HTTP engines that must be created in the platform-specific code modules. All HTTP communication is done in the background using coroutines (more on them later). Ktor client also integrates seamlessly with kotlinx.serialization to deal with JSON serialisation/deserialisation.

Handling data persistence

SQLDelight has established itself as the go-to choice when it comes to dealing with persistence in Kotlin Multiplatform projects. It is a framework based on SQLite and supports Kotlin/JVM, Kotlin/JS and Kotlin/Native.

SQLDelight generates a typesafe kotlin API based on SQL queries. It also provides compile-time checks for verification for schemas, queries and migrations and has IDE support for auto-complete. Not that when targeting multiple platforms, SQLDelight relies on platform-specific database drivers that must be initialised in platform-specific code modules. It does not deal with concurrency out of the box.

Handling concurrency

Coroutines are the preferred way of dealing with concurrency in Kotlin and Kotlin Multiplatform. They are not a part of the standard library, but an official library that complements it. Coroutines take away the complexity of dealing with threads and passing data between them when work has to be offloaded from the main thread by leveraging the async/await paradigm. Be sure to check out the coroutines documentation if you are not familiar with them. A Kotlin Multiplatform common module should expose suspend functions in its API. Those suspend functions should be safe to call on any thread and should not hardcode any particular dispatcher or coroutine context.

NB: If the project is using a Ktor version prior to 2.0.0, dependency on the native-mt version of the coroutines library should be forced, since it is required to be able to use coroutines on Native correctly.

One downside of using coroutines is that they are really Kotlin-specific and calling them from other platforms like iOS or Java Script, while possible, is highly unpleasant and error-prone. This is why my advice to you is to always create a platform-idiomatic interface and use it in the native part of the applications. But more on this later.

This is Part 1 of a blog series on Kotlin Multiplatform. Stay tuned for Part 2 where we are going to introduce you to the intricacies of interfacing with platform-specific code. See you soon on the Infinite Lambda blog.

More on the topic

Everything we know, we are happy to share. Head to the blog to see how we leverage the tech.

event-driven architectures for data-driven apps
Make Data-Driven Apps with Event-Driven Architectures
The rise of cloud computing and cloud-native technologies enabled the emergence of new age companies. A digital-native breed of businesses that truly operate 24/7 across...
November 23, 2022
Fivetran Regional Innovation Partner of the Year for EMEA 2022
Infinite Lambda Named Fivetran Regional Innovation Partner of the Year for EMEA
We are thrilled to announce that we have been named Fivetran Regional Innovation Partner of the Year for EMEA. We are twice as happy to...
October 20, 2022
dbt Labs Platinum Partnership and Certification Award
Infinite Lambda Named dbt Labs Platinum Partner
We are thrilled to announce that Infinite Lambda has been named a Platinum partner to dbt Labs. We have been using dbt since the very...
October 18, 2022
Apache Airflow start_date and execution_date explained
Airflow start_date and execution_date Explained
Despite Airflow’s popularity in data engineering, the start_date and execution_date concepts remain confusing among many new developers today. This article aims to demystify them. Basic...
June 15, 2022
Breaking Some Myths about the Use of Dual-Track Agile
Bringing both flexibility and transparency, the Dual-Track Agile methodology is increasingly popular. With a growing number of teams that decide to try it out, it...
June 10, 2022
Creating a PostgreSQL to BigQuery Sync Pipeline Using Debezium and Kafka
Many companies today use different database technologies for their application and their data platform. This creates the challenge of enabling analytics on application data without...
June 1, 2022

Everything we know, we are happy to share. Head to the blog to see how we leverage the tech.

Seraphinite AcceleratorOptimized by Seraphinite Accelerator
Turns on site high speed to be attractive for people and search engines.