Ist Kotlin Multiplatform die Zukunft der plattformübergreifenden Entwicklung? Tipps zum Einstieg

Veröffentlicht: 2021-01-29

Heutzutage können wir in der mobilen Entwicklung einen Trend beobachten, Apps schneller zu veröffentlichen. Es gab viele Versuche, die Entwicklungszeit zu verkürzen, indem gemeinsame Codeteile zwischen verschiedenen Plattformen wie Android und iOS geteilt wurden. Einige Lösungen haben bereits an Popularität gewonnen, während andere noch in der Entwicklung sind. Heute möchte ich auf einen der neuesten Ansätze aus der zweiten Gruppe eingehen – Kotlin Multiplatform Mobile (kurz KMM).

Was ist Kotlin Multiplattform Mobile?

KMM ist ein SDK, das in erster Linie darauf abzielt, Geschäftslogik zwischen Plattformen zu teilen – der Teil, der in den meisten Fällen ohnehin gleich sein muss. Dies wird dank eines Satzes mehrerer Compiler für ein gemeinsam genutztes Modul erreicht. Beispielsweise verwendet das Android-Ziel eine Kotlin/JVM-Variante, und für iOS gibt es eine Kotlin/Native-Variante. Ein gemeinsames Modul kann dann zu typischen nativen App-Projekten hinzugefügt werden, und Entwickler, die für die Benutzeroberfläche verantwortlich sind, können sich darauf konzentrieren, den Benutzern das beste Erlebnis in einer ihnen vertrauten Umgebung zu bieten – Android Studio für Android und Xcode für iOS.

Kotlin Multiplattform gegen Flutter

Eine der derzeit beliebtesten Lösungen für die plattformübergreifende App-Entwicklung ist Flutter. Es konzentriert sich auf die Regel „Schreibe eine App und führe sie überall aus“ – die funktioniert, aber nur für einfache Apps. In realen Szenarien müssen Entwickler ohnehin oft nativen Code für jede Plattform schreiben, um die Lücken zu füllen , zum Beispiel wenn ein Plugin fehlt. Mit diesem Ansatz sieht die App auf verschiedenen Plattformen gleich aus, was manchmal wünschenswert ist, aber in einigen Fällen gegen bestimmte Designrichtlinien verstoßen kann.

Symbol für plattformübergreifende Entwicklungsdienste

Sind Sie bereit, Ihre eigene App zu erstellen?

Wählen Sie Flattern

Obwohl sie ähnlich klingen mögen, ist Kotlin Multiplatform keine plattformübergreifende Lösung – es versucht nicht, das Rad neu zu erfinden. Entwickler können weiterhin Tools verwenden, die sie kennen und mögen. Es vereinfacht lediglich den Prozess der Wiederverwendung von Codeteilen , die zuvor mehrfach hätten geschrieben werden müssen, wie z. B. das Senden von Netzwerkanforderungen, das Speichern von Daten und andere Geschäftslogik.

Vor- und Nachteile von Kotlin Multiplatform

Vorteile von KMM :

  • Die entwickelte App ist für jede Plattform zu 100 % nativ – sie lässt sich einfach in aktuell verwendeten Code und Bibliotheken von Drittanbietern integrieren
  • Einfach zu bedienen – fast alle Android-Entwickler verwenden bereits Kotlin, sodass für den Einstieg nur sehr wenig zusätzliches Wissen erforderlich ist
  • Die Benutzeroberfläche kann für jede Zielplattform aufgeteilt werden – die App fühlt sich mit jedem gegebenen Ökosystem konsistent an
  • Gemeinsame Logik ermöglicht es Entwicklern, neue Funktionen hinzuzufügen und Fehler auf beiden Betriebssystemen gleichzeitig zu beheben

Nachteile von KMM :

  • Viele Komponenten befinden sich noch im Alpha-/Beta-Stadium und können möglicherweise instabil sein oder sich in Zukunft ändern

Welche Unternehmen nutzen KMM?

Laut der offiziellen Seite interessieren sich Unternehmen zunehmend für diese Technologie und die Liste wird kontinuierlich länger und länger. Darunter sind so bekannte Marken wie Autodesk, VMWare, Netflix oder Yandex.

Wie fange ich mit Kotlin Multiplatform an?

Der beste Ort, um nach detaillierten Informationen zu tauchen, ist der offizielle Leitfaden, aber in diesem Artikel möchte ich ein Beispiel zeigen, das ziemlich einfach, aber interessanter ist als nur ein „Hello World“, das das Abrufen und Anzeigen von Apps wäre der neueste Comic von Randall Munroe (lizenziert unter CC BY-NC 2.5) mit seinem Titel von xkcd.com API.

Abzudeckende Funktionen:

  • Projektaufbau
  • Netzwerken im gemeinsamen Modul
  • Einfache Benutzeroberfläche für Android und iOS

Hinweis: Ich wollte, dass dieses Beispiel sowohl für Android- als auch für iOS-Entwickler so einfach zu lesen ist, daher habe ich an einigen Stellen absichtlich einige plattformspezifische bewährte Verfahren weggelassen, nur um zu verdeutlichen, was vor sich geht

Projektaufbau

Stellen Sie zunächst sicher, dass Sie die neuesten Versionen von Android Studio und Xcode installiert haben, da beide für die Erstellung dieses Projekts erforderlich sind. Installieren Sie dann in Android Studio das KMM-Plugin. Dieses Plugin vereinfacht viele Dinge – um ein neues Projekt zu erstellen, klicken Sie einfach auf Neues Projekt erstellen und wählen Sie KMM-Anwendung.

Erstellen Sie ein neues Kotlin Multiplatform Mobile-Projekt

Navigieren Sie nach dem Erstellen des Projekts zur Datei build.gradle.kts im freigegebenen Verzeichnis . Hier müssen Sie alle erforderlichen Abhängigkeiten angeben. In diesem Beispiel verwenden wir ktor für die Netzwerkschicht, kotlinx.serialization zum Analysieren von json-Antworten vom Backend und kotlin-Coroutinen, um alles asynchron zu erledigen.

Der Einfachheit halber stelle ich unten Auflistungen bereit, die alle Abhängigkeiten zeigen, die zu den bereits vorhandenen hinzugefügt werden müssen. Wenn Sie Abhängigkeiten hinzufügen, synchronisieren Sie einfach das Projekt (eine Eingabeaufforderung wird angezeigt). Fügen Sie zuerst das Serialisierungs-Plug-in zum Plug-in-Abschnitt hinzu.

 Plugins {
   kotlin("plugin.serialization") Version "1.4.0"
}

Fügen Sie dann Abhängigkeiten hinzu.

 Quellensätze {
   val commonMain durch Abrufen von {
       Abhängigkeiten {
           implementierung("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
           implementierung("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9-native-mt-2")

           implementierung("io.ktor:ktor-client-core:1.4.1")
           implementierung("io.ktor:ktor-client-json:1.4.1")
           implementierung("io.ktor:ktor-client-serialisierung:1.4.1")
       }
   }
   val androidMain durch Abrufen von {
       Abhängigkeiten {
           implementierung("io.ktor:ktor-client-android:1.4.1")
       }
   }
   val iosMain durch Abrufen von {
       Abhängigkeiten {
           implementierung("io.ktor:ktor-client-ios:1.4.1")
       }
   }
}

Erwähnenswert ist, dass es zum Zeitpunkt der Erstellung dieses Artikels einige Probleme mit der stabilen Version der Coroutines-Bibliothek auf iOS gibt – daher trägt die verwendete Version die Endung native-mt-2 (was für natives Multithreading steht). Sie können den aktuellen Status dieses Problems hier überprüfen.

Netzwerken im gemeinsamen Modul

Zuerst brauchen wir eine Klasse, die die Antwort darstellt – diese Felder sind in json vorhanden, das vom Backend zurückgegeben wird.

 import kotlinx.serialization.Serializable

@ Serialisierbar
Datenklasse XkcdResponse(
   val img: Zeichenkette,
   Val-Titel: Zeichenfolge,
   Werttag: Int,
   Wert Monat: Int,
   val Jahr: Int,
)

Als Nächstes müssen wir eine Klasse erstellen, die die API mit einem HTTP-Client darstellt . Falls wir nicht alle in json vorhandenen Felder bereitgestellt haben, können wir die Eigenschaft ignoreUnknownKeys verwenden, damit der Serialisierer fehlende Felder ignorieren kann. Dieses Beispiel hat nur einen Endpunkt, der durch eine angehaltene Funktion dargestellt wird. Dieser Modifikator teilt dem Compiler mit, dass diese Funktion asynchron ist. Ich werde es mit plattformspezifischem Code genauer beschreiben.

 import io.ktor.client.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.request.*

Klasse XkcdApi {
   private val baseUrl = "https://xkcd.com"

   privater Wert httpClient = HttpClient() {
       install(JsonFeature) {
           serializer = KotlinxSerializer(
               kotlinx.serialization.json.Json {
                   IgnoreUnknownKeys = wahr
               }
           )
       }
   }

   Spaß aussetzen fetchLatestComic() =
       httpClient.get<XkcdResponse>("$baseUrl/info.0.json")

}

Wenn unsere Netzwerkschicht bereit ist, können wir zur Domänenschicht wechseln und eine Klasse erstellen, die das lokale Datenmodell darstellt. In diesem Beispiel habe ich einige weitere Felder übersprungen und nur den Comic-Titel und die URL zum Bild hinterlassen.

 Datenklasse ComicModel(
   val imageUrl: String,
   Val-Titel: Zeichenfolge
)

Der letzte Teil für diese Schicht besteht darin, einen Anwendungsfall zu erstellen, der eine Netzwerkanforderung auslöst und dann die Antwort dem lokalen Modell zuordnet.

 Klasse GetLatestComicUseCase (privater Wert xkcdApi: XkcdApi) {
   Spaßlauf aussetzen () = xkcdApi.fetchLatestComic ()
       .let {ComicModel(it.img, it.title) }
}

Einfache Benutzeroberfläche für Android

Zeit, in das androidApp Verzeichnis zu wechseln – hier wird die native Android-App gespeichert. Zuerst müssen wir der anderen build.gradle.kts -Datei, die sich hier befindet , einige Android-spezifische Abhängigkeiten hinzufügen . Auch hier zeigt die folgende Auflistung nur Abhängigkeiten, die zu den bereits vorhandenen hinzugefügt werden sollten. Diese App verwendet die Model-View-ViewModel-Architektur (die ersten beiden Zeilen) und Glide, um ein Comic-Bild von der zurückgegebenen URL (die zweiten beiden Zeilen) zu laden.

 Abhängigkeiten {
   implementierung("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
   implementierung("androidx.lifecycle:lifecycle-livedata-ktx:2.2.0")
   implementierung("com.github.bumptech.glide:glide:4.11.0")
   annotationProcessor("com.github.bumptech.glide:compiler:4.11.0")
}

Standardmäßig sollte ein neu erstelltes Projekt MainActivity und seine Layoutdatei activity_main.xml enthalten. Lassen Sie uns einige Ansichten hinzufügen – eine TextView für den Titel und eine ImageView für den Comic selbst.

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
   Android:
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:gravity="center"
   android:orientation="vertikal">

   <Textansicht
       Android:
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>

   <Bildansicht
       Android:
       android:layout_width="match_parent"
       android:layout_height="wrap_content"/>

</LinearLayout>

Dann brauchen wir eine Darstellung des App-Zustands – es kann entweder das Laden eines neuen Comics sein, es anzeigen oder wir können beim Laden auf einen Fehler stoßen.

 versiegelte Klasse Zustand {
   Objekt Laden: State()
   class Success (val result: ComicModel) : State()
   Objektfehler: Zustand ()
}

Lassen Sie uns nun ein minimales ViewModel hinzufügen, das die zuvor erstellte Geschäftslogik verwendet . Alle Klassen können importiert werden. MutableLiveData ist ein beobachtbares Feld – die Ansicht beobachtet Änderungen daran und aktualisiert sich entsprechend. viewModelScope ist ein Koroutinenbereich, der mit dem Lebenszyklus des Ansichtsmodells verknüpft ist – falls die App geschlossen wird, bricht sie automatisch ausstehende Aufgaben ab.

 Klasse MainViewModel : ViewModel() {
   privater Wert getLatestComicUseCase = GetLatestComicUseCase(XkcdApi())
   val comic = MutableLiveData<State<ComicModel>>()

   Spaß fetchComic() {
       viewModelScope.launch {
           comic.value = State.Loading()
           runCatching {getLatestComicUseCase.run()}
               .onSuccess { comic.value = Zustand.Erfolg(it) }
               .onFailure { comic.value = State.Error() }
       }
   }
}

Eine letzte Sache – MainActivity, um alles zu verkabeln.

 Klasse MainActivity: AppCompatActivity (R.layout.activity_main) {
   privates val viewModel: MainViewModel von faul {
      ViewModelProvider(this).get(MainViewModel::class.java)
   }

   Spaß überschreiben onCreate(savedInstanceState: Bundle?) {
      super.onCreate (gespeicherterInstanzzustand)
      viewModel.comic.observe(this) {
         wann (es) {
            ist State.Loading -> {
               findViewById<TextView>(R.id.titleLabel).text = "Laden"
            }
            ist State.Success -> {
               findViewById<TextView>(R.id.titleLabel).text = it.result.title
               Glide.with(this)
                  .load(it.result.img)
                  .into(findViewById(R.id.image))
            }
            ist State.Error -> {
               findViewById<TextView>(R.id.titleLabel).text = "Fehler"
            }
         }
      }
      viewModel.fetchComic()
   }
}

Fertig ist die Android-App!

Eine mit KMM entwickelte Android-Anwendung

Einfache Benutzeroberfläche für iOS

Alles oben wurde in Android Studio gemacht, also lasst uns für diesen Teil zu Xcode wechseln, um es bequemer zu machen. Öffnen Sie dazu einfach Xcode und wählen Sie das iosApp-Verzeichnis aus – es enthält ein vorkonfiguriertes Xcode-Projekt. Standardmäßig verwendet dieses Projekt SwiftUI für die GUI, also bleiben wir der Einfachheit halber dabei.

Das erste, was Sie tun müssen, ist, eine grundlegende Logik zum Abrufen von Comic-Daten zu erstellen. Genau wie zuvor brauchen wir etwas, um den Staat zu repräsentieren.

 Aufzählungsstatus {
    Fall laden
    Fallerfolg (ComicModel)
    Fall Fehler
}

Als Nächstes bereiten wir erneut ein ViewModel vor

 Klasse ViewModel: ObservableObject {
    let getLatesteComicUseCase = GetLatestComicUseCase(xkcdApi: XkcdApi())
        
    @Published var comic = State.loading
        
    drin() {
        self.comic = .laden
        getLatestComicUseCase.run { fetchedComic, Fehler in
            if fetchedComic !=nil {
                self.comic = .success(abgerufenerComic!)
            } anders {
                self.comic = .Fehler
            }
        }
    }
}

Und schließlich die Aussicht.

Hinweis: Der Einfachheit halber habe ich die SwiftUI-Komponente RemoteImage verwendet, um das Bild anzuzeigen, genau wie ich Glide auf Android verwendet habe.

 struct ContentView: Ansicht {
 
    @ObservedObject private(set) var viewModel: ViewModel
    
    var body: some View {
        comicView()
    }
    --
    private func comicView() -> some View {
        switch viewModel.comic {
        Fall .Laden:
            return AnyView(Text("Laden"))
        Fall .Ergebnis (lassen Sie Comic):
            return AnyView(VStack {
                Text(comic.titel)
                RemoteImage(url: comic.img)
            })
        Fall .Fehler:
            return AnyView(Text("Fehler"))
        }
    }
}

Und fertig ist auch die iOS-App!

Eine mit KMM entwickelte iOS-Anwendung

Zusammenfassung

Um abschließend die Frage aus dem Titel zu beantworten: Ist Kotlin Multiplatform die Zukunft der plattformübergreifenden Entwicklung? – es hängt alles von den Bedürfnissen ab. Wenn Sie gleichzeitig eine kleine, identische App für beide mobilen Plattformen erstellen möchten, dann wahrscheinlich nicht, da Sie die erforderlichen Kenntnisse zur Entwicklung für beide Plattformen haben müssen.

Produktsymbol freigeben

Entwickeln Sie Ihre nächste App mit unseren Experten

Ein Angebot bekommen

Wenn Sie jedoch bereits ein Team von Android- und iOS-Entwicklern haben und die beste Benutzererfahrung bieten möchten, kann dies die Entwicklungszeit erheblich verkürzen . Wie im bereitgestellten Beispiel wurde dank eines gemeinsam genutzten Moduls die Anwendungslogik nur einmal implementiert und die Benutzeroberfläche vollständig plattformspezifisch erstellt. Warum also nicht ausprobieren? Wie Sie sehen können, ist der Einstieg einfach.

Neugierig auf plattformübergreifende Entwicklung aus geschäftlicher Sicht? Lesen Sie unseren Artikel über die Vorteile der plattformübergreifenden Entwicklung.