Erkundung des Kotlin Multiplatform Wizard

Starte deine Multi-Platform-Entwicklungsreise mit dem Kotlin Multiplatform Wizard und entfessle das Potenzial, Anwendungen fuer Mobile, Desktop und Server aus einer einzigen Codebasis zu bauen.

Dieser Blogbeitrag untersucht die Faehigkeiten des neuen Kotlin Multiplatform Wizard im Detail und bietet einen vertieften Blick auf die Moeglichkeiten und Funktionalitaet, die er bietet.

Einfuehrung

Ich habe kuerzlich eine E-Mail ueber die neueste Compose Multiplatform-Veroeffentlichung erhalten. Die E-Mail stellte Compose Multiplatform 1.5.10 vor und die Betreffzeile “The Perfect Time To Get Started” weckte mein Interesse.

In der Vergangenheit habe ich Compose Multiplatform fuer Kotlin Multiplatform in mobilen Projekten eingerichtet. Dieser Prozess erforderte mehrere Schritte und erheblichen Aufwand. Allerdings hatte ich nie die Gelegenheit, die Desktop- oder Server-Aspekte von Kotlin Multiplatform zu erkunden.

Daher war ich begeistert, ein Kotlin Multiplatform (KMP) Projekt mit dem Kotlin Multiplatform Wizard zu erstellen. Der Wizard schien ein grossartiges Tool zu sein, um den Prozess zu rationalisieren und den Einstieg in die KMP-Entwicklung zu vereinfachen.

Ich wollte ein Projekt, das Folgendes enthielt:

  • Android und iOS mit einer gemeinsamen Compose UI,
  • eine Desktop-App,
  • und eine Server-App.

Die Webanwendungsunterstuetzung war noch nicht verfuegbar, also musste ich dort geduldig sein.

Nach dem Klicken auf “DOWNLOAD” oeffnete ich das Projekt in Android Studio.

Android Studio zeigt anfaenglich die “Android”-Ansicht; ich bevorzuge die “Project”-Ansicht.

Android

Ich fuehrte die Android-App zum ersten Mal ohne Probleme aus, indem ich die “composeApp”-Konfiguration auswaehte.

Keine Ueberraschungen - nach dem Bauen und Starten der App sehen wir eine einfache “Hello World!”-Anwendung.

iOS

Als Naechstes probierte ich iOS. Es fuehlt sich immer noch seltsam an, eine iOS-App von Android Studio aus zu starten…

Leider funktionierte es anfangs nicht.

/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

Die Loesung war einfach: gradlew ausfuehrbar machen.

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

Danach wurde die iOS “Hello World!”-App kompiliert und gestartet.

Desktop

Jetzt etwas voellig Neues fuer mich in KMP: Eine Desktop-App starten. Diese App kann auf macOS, Windows oder Linux laufen.

Um die Desktop-Anwendung zu kompilieren und zu starten, klicke auf das gruene “Play”-Symbol neben der main-Funktion in composeApp/src/desktopMain/kotlin.

…oder so sagte die Dokumentation.

Dieses Mal bekam ich einen Fehler:

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

Dies ist ein bekanntes Problem mit einem Workaround. Weitere Details hier.

Oeffne ein Terminal im Projektstammverzeichnis und fuehre den Gradle-Task direkt aus:

$./gradlew:composeApp:run

Das startete ein weiteres “Hello World!” - die Desktop-Variante.

Server

Schliesslich die Server-Anwendung.

Wie bei der Desktop-App kannst du den gruenen Play-Button neben der main-Funktion druecken - diesmal in server/src/main/kotlin/com.ramus.kmp/Application.kt.

Dies erzeugte die folgende Fehlermeldung:

"/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

Um die notwendigen Dateien zu bauen, fuehre Build / Rebuild Project aus.

Danach drueckte ich erneut den Play-Button, oeffnete meinen Browser und navigierte zu http://localhost:8080/, was den folgenden Bildschirm anzeigte:

Fazit

Die Ankuendigung fuer das neue Compose Multiplatform hielt ihre Versprechen, trotz einiger Huerden. Der Schnellstart eines KMP-Projekts war noch nie einfacher.

Vielen Dank fuers Lesen!

Ressourcen