Skip to main content

Getting Started with Multi-Platform Development

·4 mins

Exploring the Kotlin Multiplatform Wizard #

Start your multi-platform development journey with the Kotlin Multiplatform Wizard and unleash the potential of building applications for mobile, desktop, and server using a single code base.

This blog post explores the capabilities of the new Kotlin Multiplatform Wizard in detail. It provides an in-depth view of the possibilities and functionalities offered by the wizard.

Introduction #

I recently received an email notification about the latest release of Compose Multiplatform. The email introduced the Compose Multiplatform release 1.5.10, which caught my attention. The heading of the email read, “The Perfect Time To Get Started,” which made me curious about the new release.

In the past, I have set up Compose Multiplatform for Kotlin Multiplatform in the context of mobile-related projects. This process involved several steps and required significant effort. However, I never had the opportunity to explore the Desktop or Server aspects of Kotlin Multiplatform.

Therefore, I was really excited about the idea of creating a Kotlin Multiplatform (KMP) project using the Kotlin Multiplatform Wizard. The wizard seemed like a great tool to streamline the process and make it easier to get started with KMP development.

I was looking for a project that encompassed various platforms, including

  • Android and iOS with a shared Compose UI,
  • a desktop app,
  • and a server app.

However, it seems that I will need to exercise a bit more patience for the web application to be available.

After clicking on the “DOWNLOAD”-button, I proceeded to open the project using Android Studio.

Initially, the user is presented with the “Android” view, but I prefer the “Project” view.

#

Android #

I tried running the Android app for the first time without any issues by selecting the “composeApp” configuration.

No big surprises here. After building and starting the app, we are presented with a simple “Hello World!” application.

iOS #

Now, it was time for iOS. It still feels strange to launch an iOS app from Android Studio …

Unfortunately, this did not work out as planned.

/bin/sh -c /Users/tobiaswissmuller/Desktop/KotlinProject/build/ios/iosApp.build/Debug-iphonesimulator/iosApp.build/Script-F36B1CEB2AD83DDC00CB74D5.sh
** BUILD FAILED **

The following build commands failed:
	PhaseScriptExecution Compile\ Kotlin\ Framework /Users/tobiaswissmuller/Desktop/KotlinProject/build/ios/iosApp.build/Debug-iphonesimulator/iosApp.build/Script-F36B1CEB2AD83DDC00CB74D5.sh (in target 'iosApp' from project 'iosApp')
(1 failure)
/Users/tobiaswissmuller/Desktop/KotlinProject/build/ios/iosApp.build/Debug-iphonesimulator/iosApp.build/Script-F36B1CEB2AD83DDC00CB74D5.sh: line 7: ./gradlew: Permission denied
Command PhaseScriptExecution failed with a nonzero exit code

The solution was simple. We just need to set the permission to execute gradlew.

$ cd /Users/tobiaswissmuller/Desktop/KotlinProject
$ chmod +x gradlew

Once the code compiled, the iOS version of the “Hello World!” app was launched.

Desktop #

Now, let’s talk about something that was completely new to me in the world of KMP: launching a desktop app. This app can be used not only on my Mac, but also on any Windows or Linux machine.

To compile and launch the desktop application, simply click on the green “Play” symbol located next to the main function under composeApp/src/desktopMain/kotlin.

… or so the documentation said …

This time, I encountered the following error.

Cannot locate tasks that match ':composeApp:compileJava' as task 'compileJava' is ambiguous in project ':composeApp'. Candidates are: 'compileDebugAndroidTestJavaWithJavac', 'compileDebugJavaWithJavac', 'compileDebugUnitTestJavaWithJavac', 'compileReleaseJavaWithJavac', 'compileReleaseUnitTestJavaWithJavac'.

Fortunately, this is a known issue and there is a solution for it. You can find more information about the issue here.

Open a terminal and navigate to the root directory of the project. Then, run the corresponding Gradle target.

$ ./gradlew :composeApp:run

Once again, we have another “Hello World!” application, but this time it is a desktop variant.

Server #

Moving on to the last target, the server application.

Similar to the desktop app, we need to press the green play button next to the main function, but this time in the file server/src/main/kotlin/com.ramus.kmp/Application.kt.

This resulted in the following error message:

"/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java" -javaagent:/Applications/Android Studio.app/Contents/lib/idea_rt.jar=52601:/Applications/Android Studio.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-server-netty-jvm/2.3.6/dd89a079ea09ecc3ebf3d1d647ae8e16f0c4ebdb/ktor-server-netty-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-server-core-jvm/2.3.6/42724e1cd2883bc1d1867b67d2dff708836a84fc/ktor-server-core-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.9.20/e58b4816ac517e9cc5df1db051120c63d4cde669/kotlin-stdlib-1.9.20.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.4.11/54450c0c783e896a1a6d88c043bd2f1daba1c382/logback-classic-1.4.11.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.8.22/b25c86d47d6b962b9cf0f8c3f320c8a10eea3dd1/kotlin-stdlib-jdk8-1.8.22.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.8.22/4dabb8248310d833bb6a8b516024a91fd3d275c/kotlin-stdlib-jdk7-1.8.22.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-jdk8/1.7.1/31b0f471577d3c228d331fde355e14ccb071c90a/kotlinx-coroutines-jdk8-1.7.1.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.7/41eb7184ea9d556f23e18b5cb99cad1f8581fc00/slf4j-api-2.0.7.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec-http2/4.1.97.Final/893888d09a7bef0d0ba973d7471943e765d0fd08/netty-codec-http2-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.eclipse.jetty.alpn/alpn-api/1.1.3.v20160715/a1bf3a937f91b4c953acd13e8c9552347adc2198/alpn-api-1.1.3.v20160715.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-native-kqueue/4.1.97.Final/68268a71eb3061068a3c082ab1ecf77bee73b3a7/netty-transport-native-kqueue-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-native-epoll/4.1.97.Final/d569aa0dbbe7bccac689a69ca2a15c7eae2bc619/netty-transport-native-epoll-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-reflect/1.8.22/b52be44bc57cb6fd2169a29aefa4507c4e49c858/kotlin-reflect-1.8.22.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/com.typesafe/config/1.4.2/4c40a633e7994cfb0354244efb6d03fcb11c3ecf/config-1.4.2.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/23.0.0/8cc20c07506ec18e0834947b84a864bfc094484e/annotations-23.0.0.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.4.11/2f9f280219a9922a74200eaf7138c4c17fb87c0f/logback-core-1.4.11.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-server-host-common-jvm/2.3.6/9d59f7198bb61a0873ddc91e5546715344622307/ktor-server-host-common-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.7.1/63a0779cf668e2a47d13fda7c3b0c4f8dc7762f4/kotlinx-coroutines-core-jvm-1.7.1.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec-http/4.1.97.Final/af78acec783ffd77c63d8aeecc21041fd39ac54f/netty-codec-http-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-handler/4.1.97.Final/abb86c6906bf512bf2b797a41cd7d2e8d3cd7c36/netty-handler-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec/4.1.97.Final/384ba4d75670befbedb45c4d3b497a93639c206d/netty-codec-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport/4.1.97.Final/f37380d23c9bb079bc702910833b2fd532c9abd0/netty-transport-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-buffer/4.1.97.Final/f8f3d8644afa5e6e1a40a3a6aeb9d9aa970ecb4f/netty-buffer-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-common/4.1.97.Final/7cceacaf11df8dc63f23d0fb58e9d4640fc88404/netty-common-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-classes-kqueue/4.1.97.Final/29be6504ec6d9f5a173dfe562196998b2b365502/netty-transport-classes-kqueue-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-native-unix-common/4.1.97.Final/d469d84265ab70095b01b40886cabdd433b6e664/netty-transport-native-unix-common-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-classes-epoll/4.1.97.Final/795da37ded759e862457a82d9d92c4d39ce8ecee/netty-transport-classes-epoll-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-serialization-jvm/2.3.6/578d35a2dcc790fa0fcfb2f88c9a7fce98483ae5/ktor-serialization-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-events-jvm/2.3.6/7361b04c85d908d618848cf48851b92ece5e59ab/ktor-events-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-http-jvm/2.3.6/550659a94f8b9ce66c27cef5eddd599cd96443a8/ktor-http-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-utils-jvm/2.3.6/5deb03c1ee8ba69b200951c27358e0b812cb073e/ktor-utils-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.netty/netty-resolver/4.1.97.Final/cec8348108dc76c47cf87c669d514be52c922144/netty-resolver-4.1.97.Final.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-websockets-jvm/2.3.6/2445e00903b2d5ff718d2204ae454ed169ea6a6c/ktor-websockets-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-http-cio-jvm/2.3.6/562d00ab9b583e2251b297095784c317a56b7a75/ktor-http-cio-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-io-jvm/2.3.6/4b8aa5850072fa773ddd80104e5353e1325d2b6a/ktor-io-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-network-jvm/2.3.6/23ab56a7a6d3c6b1a32ccaf5a15ba8f9eda4916/ktor-network-jvm-2.3.6.jar:/Users/tobiaswissmuller/.gradle/caches/modules-2/files-2.1/org.fusesource.jansi/jansi/2.4.0/321c614f85f1dea6bb08c1817c60d53b7f3552fd/jansi-2.4.0.jar com.ramus.kmp.ApplicationKt
Error: Could not find or load main class com.ramus.kmp.ApplicationKt
Caused by: java.lang.ClassNotFoundException: com.ramus.kmp.ApplicationKt

Process finished with exit code 1

To build the corresponding files, you need to run Build / Rebuild Project.

After that, I pressed the play button again, opened my browser, and navigated to localhost:8080, which directed me to the following screen:

Conclusion #

The announcement for the new Compose Multiplatform delivered on its promises, despite a few hiccups. Jump-starting a KMP project has never been this easy.

If you have any further questions, need specific code examples, or require additional assistance, please don’t hesitate to leave a comment or send me a message.

Thank you for reading!

This tutorial was created with the assistance of Notion AI and contains affiliate links.

Resources #