A Developer’s Roadmap to Mastering Kotlin Multiplatform

New
10 min read
Jaewoong E.
Jaewoong E.
Published November 10, 2024 Updated November 8, 2024

In modern mobile development, cross-platform frameworks are gaining popularity because they offer key advantages, such as reducing the resources needed to develop separate native apps and maintaining code consistency across different platforms.

By allowing developers to write shareable code, these frameworks enable faster development and easier maintenance, making them attractive not only to startups and large companies but also to individual developers. With a single codebase, it's possible to build multiple platform-specific apps, making cross-platform solutions highly efficient for both time and cost management.

Kotlin Multiplatform (KMP) is another rising star in cross-platform development driven by the collaboration between JetBrains and Google. It allows developers to share business logic seamlessly across multiple platforms, including Android, iOS, Desktop, and Web, all while using Kotlin. Compared to other cross-platform solutions like React Native or Flutter, Kotlin Multiplatform offers performance that is closer to native, making it an attractive choice for developers seeking efficiency and native-like performance without sacrificing the benefits of cross-platform development.

In this article, you'll explore the Kotlin Multiplatform (KMP) ecosystem using the Kotlin Multiplatform Developer Roadmap as your guide. The roadmap is designed to offer a comprehensive overview of the current KMP ecosystem, which provides suggested learning paths to help you better understand the various concepts involved in KMP development.

Kotlin Multiplatform Architecture

Kotlin Multiplatform doesn't inherently provide a UI framework. Instead, it focuses on sharing business logic across multiple platforms while enabling developers to create platform-specific UIs for Android, iOS, and other targets. The core idea is to write common logic in Kotlin once and reuse it across platforms like Android, JVM, iOS, macOS, JavaScript, and Linux, while maintaining full control over each platform's native UI. This approach provides flexibility, as demonstrated in the illustration below:

Let’s jump into the architecture with some code exploration! Before setting up your first Kotlin Multiplatform (KMP) project, ensure the following prerequisites:

  • Install the Kotlin Multiplatform Plugin in Android Studio.
  • Launch Xcode at least once and accept the terms of use if you plan to build iOS apps.

After meeting these requirements, open the Kotlin Multiplatform Wizard. This will generate a project structure similar to the one below, giving you a foundation for cross-platform development.

After selecting Android, iOS, and Web, the Kotlin Multiplatform Wizard downloads a project pre-configured with a multiplatform architecture. You can then open the composeApp module’s build.gradle.kts file to view the platform setup. This file outlines each targeted platform’s configuration, dependencies, and shared code structure, helping you better understand the cross-platform architecture and how shared business logic integrates with platform-specific code below:

kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "composeApp" browser { val rootDirPath = project.rootDir.path val projectDirPath = project.projectDir.path commonWebpackConfig { outputFileName = "composeApp.js" devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { static = (static ?: mutableListOf()).apply { // Serve sources to debug inside browser add(rootDirPath) add(projectDirPath) } } } } binaries.executable() } androidTarget { @OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } } listOf( iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> iosTarget.binaries.framework { baseName = "ComposeApp" isStatic = true } } }

In the code above, you can configure target platforms and create hierarchical structures by grouping similar platforms, like iosX64 and iosArm64. This setup allows you to share common code between similar platform targets, simplifying maintenance and reducing redundancy.

You can set up the targets below to configure KMP project platform architecture:

  • applyDefaultHierarchyTemplate(): Configures a default shared source set hierarchy, simplifying setup by establishing common dependencies and configurations across platform targets, like Android, iOS, and JVM. It enables code reuse by creating shared source sets with logical hierarchies.

  • androidTarget(): Adds Android as a target platform, allowing the KMP project to compile Android-specific code. For library modules, publishLibraryVariants("release") can be used to specify which build variants are published.

  • iosX64(), iosArm64(), iosSimulatorArm64(): Configures targets for different iOS CPU architectures, supporting iOS on x64 (for simulator), arm64 (for devices), and Simulator Arm64. The baseName field can specify a framework name, like “ComposeApp,” for the output.

  • macosX64(), macosArm64(): Targets for macOS, allowing the code to be compiled for macOS on both x64 (Intel) and Arm64 (Apple Silicon) architectures. This setup supports building shared code for macOS applications.

  • jvm(): Compiles the shared code for the Java Virtual Machine (JVM), which is compatible with desktop and Android applications, providing easy integration with Java libraries and environments.

  • wasmJs(): Configures the project to target WebAssembly with JavaScript interop, enabling shared Kotlin code to execute as WebAssembly (WASM) in a web environment. This is especially useful for creating performant web apps.

  • linuxX64(), linuxArm64(): Targets for Linux on x64 and Arm64 architectures, allowing the shared code to run on Linux environments, which is helpful for server-side applications or embedded Linux devices.

Each target broadens the reach of your KMP project by allowing the core business logic to run across different operating systems and architectures. This flexibility helps maximize code reuse while supporting the specifics of each platform’s native capabilities.

If you’re ready to dive into building your first Android and iOS mobile applications with Kotlin Multiplatform, check out this guide: Build Your First Android and iOS Mobile App With Kotlin Multiplatform.

Share Code

Kotlin Multiplatform lets you define a common interface specification across multiple platforms, unifying the interface hierarchy while allowing platform-specific implementations. To achieve this, you can use expected and actual declarations, which enable access to platform-specific APIs from Kotlin Multiplatform modules. This approach allows you to maintain platform-agnostic APIs in the common code, simplifying cross-platform development.

The expect keyword is used in the shared (common) code to declare a function, class, or property without defining its implementation. This acts as a placeholder, allowing you to specify the API that platform-specific modules will need to implement. For example, if you want to declare a shared function that logs across different platforms, you can declare a function called log like the code below:

kt
1
2
// In common module expect fun log(message: String)

The actual keyword is used in platform-specific modules to provide the implementation of the expect declarations. For each platform (e.g., Android, iOS), you create the actual implementation of the expected functionality. For instance, if you want to provide the actual implementation for the expected log function, you can define it differently for each specific platform, as shown in the example below:

kt
1
2
3
4
5
6
7
8
9
// In Android module actual fun log(message: String) { Log.d("TAG", message) } // In iOS module actual fun log(message: String) { NSLog(message) }

Now, you can use the log function across the common module. This approach provides flexibility by allowing platform-specific code without altering the common code. Additionally, it enables you to maintain platform-independent logic in the common module, maximizing code reuse. For more information about the expect and actual declarations, check out Expected and actual declarations.

Compose Multiplatform

You’ve discovered that Kotlin Multiplatform is designed to share business logic across platforms but doesn’t include any UI solutions by default. This can be confusing, as most cross-platform frameworks provide UI components or layers that enable building screens with a unified codebase across different platforms.

A great solution for cross-platform UI in Kotlin Multiplatform is Compose Multiplatform, which builds on Kotlin Multiplatform and enables developers to share UI code written in Jetpack Compose across various platforms. JetBrains has forked the Jetpack Compose library (for Android) into the compose-multiplatform repository and created compatible Compose UI clients for multiple platforms, including iOS, Desktop, and WebAssembly (WASM).

With Compose Multiplatform, you can develop both Android and iOS applications using a unified UI implementation while sharing business logic written in Kotlin. Currently, Android and Desktop (Windows, macOS, Linux) support is stable, iOS is in beta, and Web is in alpha. For more details on future plans, check out the Kotlin Multiplatform Development Roadmap for 2025.

AndroidX Library Compatibility

If you’re an Android developer, you might be wondering about the best ways to minimize migration costs from native Android to Kotlin Multiplatform, and that must be using the same tech stacks as much as possible, such as Jetpack libraries. The Android team is aware of this and has begun officially supporting KMP for several Jetpack libraries, as listed below:

Ready to integrate? Our team is standing by to help you. Contact us today and launch tomorrow!
  • Lifecycle: Lifecycle-aware components that react to changes in the lifecycle state of other components.
  • Room Database: A persistence library that provides an abstraction layer over SQLite, enabling robust database access with the full power of SQLite.
  • DataStore: Enables asynchronous, consistent, and transactional data storage, addressing limitations of SharedPreferences.
  • Paging: Facilitates gradual and efficient data loading.
  • Annotation: Provides metadata to enhance code understanding for tools and developers.
  • Collection: Optimizes memory usage for small collections.

Jetpack library releases for Android and iOS meet strict quality and compatibility standards. As Jetpack expands its Kotlin Multiplatform (KMP) support to other platforms, however, tooling and infrastructure are still developing. Check out the sample project on GitHub for KMP-supported Jetpack libraries.

According to the official documentation, Jetpack libraries categorize platform support into three tiers:

  • Tier 1: Android, JVM, iOS
  • Tier 2: macOS, Linux
  • Tier 3: watchOS, tvOS, Windows, WASM

For additional information on Kotlin Multiplatform support for Jetpack libraries, refer to the Kotlin Multiplatform Overview.

Asynchronous

Asynchronous solutions are essential in mobile development due to limited resources compared to desktops. Both Android and iOS use the main thread (or UI thread) to handle all UI-related tasks, such as rendering elements, dispatching events, and managing interactions within the user interface.

For I/O or computationally intensive tasks like network requests or database queries, it’s best to offload them to a worker thread. This keeps the main thread free for rendering and user interactions, ensuring a responsive UI. In Kotlin, Coroutines provide a powerful asynchronous solution, supported at the language level and enhanced through libraries, making it ideal for efficient concurrency.

Coroutines fully support Kotlin Multiplatform, allowing you to use them seamlessly across multiple platforms. They offer a lightweight concurrency solution with robust error-handling APIs, providing greater flexibility than traditional threads. This makes Coroutines one of the most promising asynchronous solutions for Kotlin Multiplatform development.

If you're an avid ReactiveX user, consider exploring Reaktive on GitHub, which brings ReactiveX extensions to Kotlin Multiplatform.

Network

On Android, Retrofit and OkHttp are the go-to solutions for handling type-safe HTTP network requests, streaming, and more. However, they don't support Kotlin Multiplatform. Instead, there is another excellent HTTP asynchronous library Ktor, designed for creating multiplatform microservices and HTTP clients. Ktor is lightweight, flexible, and fully compatible with Coroutines, making them ideal for Kotlin Multiplatform projects.

Ktor is an asynchronous framework created by JetBrains for building applications and microservices in Kotlin. It supports both client-side and server-side development, making it versatile for building HTTP clients, REST APIs, web applications, and microservices across multiple platforms, including Android, JVM, JavaScript, and iOS. The key features of Ktor are:

  1. Asynchronous by Design: Built with coroutines in mind, Ktor is fully asynchronous, leveraging Kotlin's coroutines to provide high concurrency with minimal overhead.
  2. Multiplatform Support: Ktor can be used in Kotlin Multiplatform projects, allowing you to write HTTP clients that work across different platforms (Android, iOS, JVM, etc.) with the same codebase.
  3. Extensible and Modular: Ktor is modular, allowing you to add only the features you need, such as authentication, serialization, WebSocket support, and more, by including individual dependencies.
  4. Flexible Routing: Ktor’s routing system is flexible, supporting path parameters, query parameters, and more. It enables organized API endpoints in server applications.
  5. Built-In Serialization: Ktor integrates with Kotlin's serialization library, making it easy to handle JSON, XML, and other serialization formats.

On the other hand, a solution called Ktorfit is built on top of Ktor. Ktorfit is an HTTP client and Kotlin Symbol Processor designed for Kotlin Multiplatform, inspired by Retrofit. It offers a similar interface-based approach to define HTTP request methods, making it familiar for those who have used Retrofit.

Image Loading

Loading images from the network is essential in modern mobile development for displaying user profiles, thumbnails, and more. However, rendering UIs differs across platforms like Android and iOS, requiring a solution that supports both Kotlin Multiplatform for core functionality and Compose Multiplatform for UI integration.

Building an image-loading system from scratch is costly, as it requires implementing numerous features, such as downloading images from the network, resizing, caching, rendering, and memory management. Fortunately, there are several image-loading solutions available that support Kotlin Multiplatform:

  • Coil: As of version 3.0.x, Coil supports Kotlin Multiplatform, allowing you to seamlessly integrate it into projects that already use Coil.
  • Landscapist: A highly optimized, pluggable image-loading library for Jetpack Compose and Kotlin Multiplatform that supports features like placeholders, shimmer effects, blurring, and animations to enhance the user experience.
  • Kamel: An asynchronous media-loading and caching library designed for Kotlin Multiplatform and Compose.
  • compose-imageloader: A lightweight image-loading library for Compose with Kotlin Multiplatform support.

Consider which solution best aligns with your project’s requirements, and choose the one that fits your needs.

Local Storage

Local storage is essential in modern Android development for caching information on devices to enhance user experience. Fortunately, several Jetpack libraries now support Kotlin Multiplatform—especially DataStore and Room Database, as mentioned above—making them ideal choices since they’re already familiar to many Android developers.

  • Room Database: A persistence library offering a powerful abstraction layer over SQLite, enabling reliable and efficient database access.

  • DataStore: Provides asynchronous, consistent, and transactional data storage, overcoming the limitations of SharedPreferences. DataStore is highly effective for persisting key-value based data, making it a lightweight alternative to managing and querying large datasets in a full-fledged database.

Another excellent option outside of Jetpack libraries is SQLDelight, which generates type-safe Kotlin APIs from your SQL statements. It performs compile-time checks for your schema, statements, and migrations, and offers IDE features like autocomplete and refactoring, making SQL development and maintenance straightforward and efficient. It basically supports Kotlin Multiplatform.

Serialization & Date-Time

If you’ve been using Gson or Moshi for serialization/deserialization on Android, you may have concerns since they don’t support Kotlin Multiplatform. Fortunately, JetBrains has developed their own solutions for serialization and date-time handling in Kotlin Multiplatform, as shown below:

  • kotlinx.serialization: A Kotlin serialization library with a compiler plugin that generates serialization code for classes, a core runtime library with serialization APIs, and support libraries for various formats.

  • kotlinx-datetime: A Kotlin Multiplatform library for handling date and time, designed to address common challenges developers encounter when working with dates and times.

Conclusion

In this article, you’ve explored Kotlin Multiplatform’s platform architecture, library ecosystem, and various solutions for Kotlin Multiplatform development. Kotlin Multiplatform shows great promise, with active support from JetBrains and Google, and a growing number of companies adopting it for production-level projects.

While the Kotlin Multiplatform ecosystem is still in its early stages and has room to grow, its biggest advantage is that Android developers can easily dive in, as it’s entirely built in Kotlin. This familiarity makes it an accessible and appealing option for those interested in cross-platform development, allowing them to leverage their existing skills.

If you have any questions or feedback on this article, you can find the author on Twitter @github_skydoves or GitHub if you have any questions or feedback. If you’d like to stay up to date with Stream, follow us on Twitter @getstream_io for more great technical content.

As always, happy coding!

Jaewoong

Ready to Increase App Engagement?
Integrate Stream’s real-time communication components today and watch your engagement rate grow overnight!
Contact Us Today!