Skip to main content

Kotlin and Compose Multiplatform 

A happy person surrounded by multiple monitors
Blog Team
Software Intensive Systems
August 18, 2025 | 3:00 pm CDT
A happy person surrounded by multiple monitors
Blog Team
Software Intensive Systems

By Fulgencio RuizRubiom, software engineer 

A variety of mobile cross-platform frameworks can be found in the marketplace. The most popular ones, Flutter and React Native, are well established and are used by major technology companies and other large organizations. They have evolved over the years and can be used for desktop and web applications too. But there are new promising frameworks emerging: Kotlin Multiplatform and Compose Multiplatform. Let’s set the stage to understand where this new technology comes from.

Background 

Since its beginning, Android development was accomplished with Java — that is, until 2010, when Oracle sued Android for its use.1 Google then tried to distance Android from Java, and did so in two ways: first, they embraced Kotlin [https://kotlinlang.org/], a JVM language created by JetBrains that can interoperate with Java, becoming the recommended language for Android in 2017. Secondly, they created Dart, a programming language, and Flutter, the multiplatform framework powered by Dart [https://flutter.dev/]. Clearly, they did not want to find themselves in the same situation. They wanted to have more than one option to see what would stick, but this time retaining full control over one of them.

Both succeeded in their own applications. Kotlin for Native Android development was a clear improvement over Java, and Flutter for multiplatform has been adopted by big companies and has established itself as the premier framework for mobile multiplatform. However, JetBrains continued development of Kotlin and expanded its capabilities for other targets (compiling to native binaries and JS) and created Kotlin Multiplatform, a framework that allowed developers to write the non-UI logic, i.e., business logic, in Kotlin and share it in iOS, Android, Web and desktop. The UI code then would be native for each target.

After some more time, JetBrains also ported Google’s Jetpack Compose, a new Kotlin-based declarative UI framework for Android, to multiplatform. They named it Compose Multiplatform [https://www.jetbrains.com/compose-multiplatform/]. This, paired with Kotlin Multiplatform, allows for a full multiplatform framework like Flutter.

In other words, Compose Multiplatform is an optional layer for Kotlin Multiplatform that allows building and sharing declarative UIs too, all written in Kotlin.

Kotlin Multiplatform (KMP) 

Kotlin Multiplatform, initially built for iOS and Android and later expanded to desktop and web targets, was designed to simplify the development of cross platform projects. It focused on the business logic while keeping the UI native for each platform. It’s open source and maintained by JetBrains.

Kotlin can compile to different targets: JVM, Kotlin/JS (compiles to JavaScript), Kotlin/Wasm (web assembly) and Kotlin/Native (native binaries, i.e., for iOS). This is what allows the multiplatform capabilities. It’s stable for iOS, Android, desktop, server and web (based on Kotlin/JS), and in Alpha for web based on Kotlin/Wasm at the time of writing this article. As mentioned, it can also be used for the server using the JVM and share some code with the client targets such as DTOs, validation logic or utility functions.

When using libraries in KMP, they should support your targets in order to be able to use them. Otherwise you have to provide for each of the non-supported targets in your own platform-specific code or a platform-specific library. It’s also possible to write platform-specific code that uses platform-specific libraries — for example, when interacting with the target APIs. This separation is accomplished with the following architecture:. This separation is accomplished with the following architecture: 

  • Common module: This is the shared codebase that contains the business logic, data models and other platform-agnostic code. It’s only possible to use multiplatform libraries. It's written in Kotlin and is compiled to a platform-agnostic intermediate representation (IR). 
  • Platform modules: These are modules for each target platform. Here you interact with the platform’s UI components, APIs and other dependent logic. One platform module exists for each target platform, e.g., Android, iOS or desktop.
  • Expect/actual mechanism: This is a mechanism that allows the common module to declare expectations (function, interfaces or classes) that are implemented by each of the platform modules. This enables the common module to call platform-specific code without knowing the implementation details. For example: 

// commonMain  

package utils

expect fun getPlatformVersion(): String 

 

// androidMain  

package utils  

import android.os.Build  

  
actual fun getPlatformVersion(): String {  

     return “Android ${Build.VERSION}”  

}  

 

// iosMain  

package com.sharedmodule  

  
import platform.UIKit.UIDeviceactual   

  
fun getNameOfPlatform(): String {  

    return "${UIDevice.currentDevice.systemVersion()}"  

}  

When compiling for a specific platform, the common module is compiled along with that platform-specific code.   

Compose Multiplatform 

Compose Multiplatform is also open source and developed by JetBrains. As mentioned before, it’s based on Jetpack Compose, a UI framework for Android. Compose Multiplatform decouples the dependencies to Android-only APIs from Jetpack Compose, like the Android life cycle to allow its use in other frameworks. This is possible due to Google’s decision to make the library modular so it could be easily ported to other platforms. This means that it’s almost the same as developing the UI for Android. In fact, the documentation is the Jetpack Compose documentation, which can be confusing at first.

The UI is rendered using Skia, a 2D graphics library developed by Google that is also used in Flutter. To be more precise, Compose Multiplatform uses Skiko, a Kotlin wrapper for Skia. The framework is declarative, meaning that the code expresses the logic of a computation without describing its control flow. This is done via composable functions called Composables. They are functions that are run whenever the component they represent must be rendered that can contain other Composables, creating a hierarchy.

@Composable  
fun MatchTitleDisplay(title: String) {  
    Column(  
        modifier = Modifier.padding(vertical = 15.dp).fillMaxWidth(),  
        horizontalAlignment = Alignment.CenterHorizontally,  
    ) {  
        Text(  
            text = title,  
            style = MaterialTheme.typography.headlineMedium,  
            color = MaterialTheme.colorScheme.primary  
        )  
    }  
}  

Example of a simple Composable function. “Column” and “Text” are also Composables. 

class HomeScreen : Screen {  

    @Composable  

    override fun Content() {  

        Scaffold(  

            topBar = {  

                CenterAlignedTopAppBar(title = { Text(text = "Europark") })  

            }  

        ) { padding ->  

            Column(  

                // modifier = Modifier...  

            ) {  

                Text(text = competition?.name ?: "Loading...")  

                Row{  

                    Button(  

                        onClick = { navigator.push(GroupsScreen())},  

                    ) {  

                        Text(text = "Groups")  

                    }  

                    Button(  

                        onClick = { navigator.push(MatchListScreen())},  

                    ) {  

                        Text(text = "Matches")  

                    }  

                }  

                CompetitionsView()  

            } 

This offers various strengths. The first one, as implied by the name “Compose,” is composition. This paradigm defines how small pieces can create bigger pieces together. The function approach increases its flexibility and steers away from class inheritance that can be limiting and increase complexity when mixing multiple code units. Another strength is the ease of encapsulation. Composable functions use the parameters to construct the view that can in turn pass more data along to its Composables descendants. They can hold state and propagate state changes down to the parents by defining callbacks as parameters. Lastly, a third strength is recomposition: the re-render of the element accomplished by calling the composable function. The hierarchy of Composables does not require the recalculation of every element with every change, instead only those that need updating.

KMP is a great alternative to Flutter for cases in which using native UI components on each platform is preferred while still sharing the rest of the code to prevent duplication. This is a common occurrence since many companies prefer the better performance of the native UI or simply maintain different looks that match the standard for each target.

It is relatively new, reaching stable in November 2023,2 not long from the moment of writing this article. All targets are stable except web for Kotlin/Wasm, which is in alpha, due to Kotlin/Wasm itself being in alpha.

The multiplatform official libraries provided by JetBrains cover most of the basic functions you would expect, and the gaps are covered by the community. The community is growing, and Google has been adding KMP support to some of their Android libraries. In fact, they recently announced at Google I/O 2024 that they intend to add support to all of the Jetpack libraries. Google has realized that KMP is becoming a strong contender for multiplatform, and it benefits both to expand on it.

In terms of compilation, it currently does not support cross-compilation for desktop targets. That means, for example, compiling an .exe must be done in a Windows host machine. This is an inconvenience for CI/CD that might change in the future.

CMP, on the other hand, is in an earlier stage of development. As of July 2024, iOS support is in beta and Web support in alpha. But, once both are stable and settled, the combination promises to be an interesting choice that no other framework provides as it lets you to choose what and how much code you want to share: part of the business logic, all the business logic, all the UI, only UI for some targets and native for others, etc.

Tooling is available for Android Studio via plugins: one for KMP and one for CMP. However, I have encountered some inconveniences. For example, import suggestions are not aware of the module you are in, so suggesting libraries that are not available in the module can be confusing. The suggested Gradle commands for building or testing also don’t match the actual commands that can be executed, and you have to resort back to the command line in some cases. Lastly, a noticeable inconvenience is the lack of the preview feature in Android Studio that is available for Jetpack Compose, allowing you to see a preview of the Composables without the need for running an emulator. It is supported in Fleet, JetBrains’ new VS Code competitor that suggests it will eventually come to Android Studio as well.

To summarize, here are some takeaways. 

Positives:  

  • Kotlin is a great language: concise, modern and overall fun to write. It’s also interoperable with Java that makes it easy to transition to (it can co-live with Java, progressive transition) 
  • KMP + CMP has a lot of flexibility in terms of deciding what to share between targets, differentiating itself from other multiplatform frameworks.
  • KMP is being supported by Google and is their recommended way to share business logic. For sharing UI though, Flutter is still their recommendation.
  • Compose is a great UI framework and an incredible improvement over XML layouts in Android.
  • Multiple open-source sample projects, with different levels of code sharing and libraries.3

Negative aspects: 

  • Android studio does not have a KMP wizard integrated, instead you have to use one online that downloads a Zip with the project files.4
  • Documentation can be confusing. Be aware that for most cases you must consult Jetpack Compose documentation when writing CMP because it's build on top of it, so 99% is equivalent. The official CMP website does not mention it. Issues with setup and library conflicts can be hard to search for and lack examples. Searching for Compose Multiplatform or Kotlin Multiplatform brings up topics for Jetpack Compose or Kotlin for Android, respectively, that is not always relevant to your problem.
  • Libraries currently are difficult to add when targeting non-stable platforms: they lag behind in support. This will not be an issue once every target is stable.

Conclusion and thoughts

Working with KMP/CMP is a rewarding experience. Coming from Android development using Java and XML, the development experience nowadays is orders of magnitude better, mostly due to Kotlin and Compose. The experience of KMP and CMP is not that different from modern native Android development, using Kotlin and Jetpack Compose. The main differences in terms of development are the libraries.

The news at Google I/O 2024 about Google’s decision to add support for KMP to the Jetpack libraries will enrich the library ecosystem and could accelerate its maturity. In the past, Google’s support for Kotlin for native Android development meant significant growth for the language and libraries. The future seems promising for KMP, and even though it won’t replace native development, it could become a good alternative to Flutter for multiplatform.

In terms of maturity, it is still heavily in development. Some targets are not stable. Some libraries do not support all targets and have side effects in different platforms. For example, I experience an inconsistency when utilizing Coil, an image processing library, between Android and desktop environments: some images did not load in one platform, while others did not load on the other. This might be because of the underlaying decoding of each image format. They use different native implementations under the hood that might formats the same way.

Within UL Solutions we provide a broad portfolio of offerings to many industries. This includes certification, testing, inspection, assessment, verification and consulting services. In order to protect and prevent any conflict of interest, perception of conflict of interest and protection of both our brand and our customers brands, UL Solutions has processes in place to identify and manage any potential conflicts of interest and maintain the impartiality of our conformity assessment services.