Kotlin Multiplatform Mobile: Native Apps entwickeln mit Multiplattform-Technik

Seite 2: In Kotlin programmiert, aber Zugriff auf native APIs

Inhaltsverzeichnis

Die Dateien müssen immer unter demselben Pfad wie commonMain gespeichert sein, sonst findet der Compiler die jeweilige Implementierung nicht. Da die Kotlin-Klasse Plattform.kt in commonMain unter kotlin/de.heise/ definiert ist, muss sie es auch unter androidMain und iosMain sein. In der Plattform.kt unter iosMain ist im nächsten Schritt der in Listing 4 aufgezeigte Code unterhalb der Zeile sechs zu schreiben.

actual fun getDeviceName(): String {
    return UIDevice.currentDevice.name
}

Listing 4: iOS-Implementierung der getDeviceName-Funktion

Obwohl die Programmierung in Kotlin stattfindet, ist der Zugriff auf native APIs möglich. Das Codebeispiel 4 greift insbesondere auf die plattformspezifische UIDevice-Klasse zu. Zu Beginn dürfte diese Vorgehensweise gewöhnungsbedürftig sein, zudem fehlt eine Kotlin-spezifische Dokumentation für Spezialfälle. Das soll sich jedoch laut YouTrack-Issues auf Dauer ändern. Dasselbe Vorgehen ist nun für die Plattform.kt unter androidMain anzuwenden. Hier muss man den unter Zeile vier in Listing 5 dargestellten Code einfügen. Die plattformspezifische Implementierung ist damit abgeschlossen.

actual fun getDeviceName(): String {
    return android.os.Build.MODEL
}

Listing 5: Android-Implementierung der getDeviceName-Funktion

Abschließend ist für den shared-Code die Businesslogik anzupassen. Als Erstes gilt es hierfür die automatisch generierte Greeting.kt im commonMain-Teil zu öffnen. Unter der bereits vorhandenen Funktion greeting() lässt sich die in Listing 6 dargestellte Funktion helloDevice() einfügen. Die Funktion erzeugt zuerst eine neue Instanz der Plattform.kt und ruft anschließend auf dieser Instanz die getDeviceName()-Methode auf. Der Rückgabewert der getDeviceName-Funktion ist ein String, der eine Begrüßung sowie das Ergebnis der getDeviceName()-Methode enthält.

fun helloDevice(): String {
    val platform = Platform()

    return "Hello my Name is ${platform.getDeviceName()}"
}

Listing 6: Implementierung der geteilten Businesslogik

Der nächste Schritt bindet die erstellte und geteilte Businesslogik in die jeweiligen nativen Projekte ein. Hierfür ist zuerst der Ordner "androidApp" zu öffnen. Die Android-App hat einen einfachen Aufbau. Der Unterordner src/main/java/de.heise.android/ enthält die Datei MainActivity.kt. Sie definiert die Hauptklasse, die die App beim Starten aufruft. Das zugehörige Layout der MainActivity.kt befindet sich in src/res/activity_main.xml. Der Aufbau des Layouts ist leicht verständlich. Die XML-Datei besteht aus einem Textfeld, das später den Gerätenamen anzeigen soll. Das Projekt generiert sowohl die MainActivity-Klasse als auch das Layout beim Neuerstellen standardmäßig. Um die zuvor im shared-Code erstellte Logik einzubinden, muss man nun die Kotlin-Klasse MainActivity.kt öffnen und Zeile 18 durch den in Listing 7 dargestellten Code ersetzen. Die Implementierung der Android-App ist hiermit abgeschlossen. Über die in Android-Studio zur Verfügung stehenden Möglichkeiten lässt sich nun die App im Simulator oder auf dem Gerät starten. Abbildung 2 stellt das Endergebnis auf der Android-Plattform dar.

val greeting = Greeting()
tv.text = greeting.helloDevice()

Listing 7: Android-Aufruf der geteilten Businesslogik

Die fertige Android-App startet (Abb. 2)

Für die iOS-Variante ist das Vorgehen ganz ähnlich. Für die folgenden Schritte ist ein Mac notwendig. Das ist nicht KMM, sondern der restriktiven Firmenpolitik von Apple geschuldet, die das Erstellen von iOS-Apps nur auf Apple-Geräten erlaubt. Das iOS-Projekt lässt sich nicht über Android-Studio, sondern nur mittels Xcode öffnen. Xcode ist eine von Apple kostenlos zur Verfügung gestellte Entwicklungsumgebung zum Erstellen von Applikationen für Apple-Endgeräte. Es ist aus dem App Store installierbar. Nach dem erfolgreichen Einrichten von Xcode ist das Projekt im Ordner "iosApp" auffindbar. Dort ist die Datei iosApp.xcworkspace zu öffnen. Würde man statt der Xcworkspace- die Xcodeproj-Datei öffnen, so ließen sich die CocoaPods nicht korrekt laden.

Um Funktionen wie die Autovervollständigung zu nutzen, empfiehlt es sich, eingangs das Projekt zu kompilieren. Der Trigger zum Kompilieren befindet sich in Xcode in der Menüleiste im Menüpunkt Product unter der Aktion Build. Das ist auch notwendig, wenn sich der shared-Code geändert hat, und in der Architektur von Kotlin Multiplatform Mobile begründet. Der in Kotlin erzeugte Code wird nativ für die jeweiligen Plattformen zur Verfügung gestellt. Das geschieht jedoch nicht "on the fly", sondern durch einen aktiven Trigger – in diesem Falle das Auslösen eines Builds. Es ist auch möglich, das Generieren direkt über Gradle anzustoßen. Aus Gründen der Komplexität spielt das an dieser Stelle allerdings keine Rolle.

Haben Entwickler das Erzeugen des shared-Codes vergessen, äußert sich das in zahlreichen Syntaxfehlern. Ein erneutes Erstellen des Projekts behebt die Probleme. Das iOS-Projekt wird standardmäßig in SwiftUI geschrieben, und die Haupt-View lässt sich unter iOSApp/ContentView.swift finden. Die Anpassungen in der iOS-Version sind marginal: Die vorhandene Zeile let greet = Greeting().greeting() ist durch let greet = Greeting().helloDevice() zu ersetzen. Abschließend lässt sich die iOS-App entweder auf dem Gerät oder in dem in Xcode zur Verfügung stehenden Simulator starten (siehe Abbildung 3).

So sieht die fertige iOS-App aus (Abb. 3).

Ist die Entwicklung der iOS- und Android-App in verschiedene Personengruppen getrennt und es gilt nur eine Kleinigkeit wie die Backend-URL zu ändern, so benötigen Android-Entwickler keine Kenntnisse über den iOS-Code. Die Änderung des Kotlin-Codes genügt. Im Anschluss muss man die App nur noch kompilieren. Ein weiterer elementarer Vorteil ist die einfache Wiederverwendbarkeit des Businesscodes. Soll zu einem späteren Zeitpunkt neben einer iOS- oder Android-App auch noch eine Web-App entstehen, lässt sich aus dem Kotlin-Code nativer JavaScript-Code erzeugen. In solch einem Fall ist die build.gradle-Datei nur um die Konfiguration des KotlinJS-Blocks zu erweitern. Mithilfe von KotlinJS lässt sich neben JavaScript- auch TypeScript-Code erzeugen. Letzteres befindet sich jedoch noch in der Beta-Phase. Den erzeugten Code bindet man über ein lokales npm-Paket ein. Das hat den Vorteil, dass sich die Web-App mit jeglichem Webframework entwickeln lässt.

Ein neuer Weg für das Erstellen von Web-Apps ist der Einsatz von Jetpack Compose im Browser. Hierdurch ergibt sich eine noch größere Wiederverwendbarkeit, da Jetpack Compose auch bei der Android-Entwicklung zum Einsatz kommen kann. Die Umwandlung von Kotlin- in nativen JavaScript-Code läuft jedoch nicht immer problemlos ab. Besonders die Umwandlung von Kotlin-Coroutines in das JavaScript-Äquivalent bedarf einiger Tricks im nativen Teil des shared-Codes. Auch ist die erzeugte JavaScript-Bundle-Größe noch nicht optimal. JetBrains arbeitet derzeit aktiv an all diesen Punkten. Interessierte können sich auf der öffentlich verfügbaren Roadmap über den Entwicklungsstand informieren. Durch den Open-Source-Ansatz von Kotlin besteht zudem die Möglichkeit, direkt über das JetBrains-Projektmangement-Tool YouTrack Probleme zu melden und Hilfe zu erhalten.