Superhelden in Compose

Ein modernes Kotlin Multiplatform Projekt mit Ktor, Arrow-Kt, Kotest & Clean Architecture

In diesem Blogbeitrag wird ein Best-Practice-Projekt vorgestellt, das mit Compose und Kotlin Multiplatform umgesetzt wurde. Das Ziel ist es, eine App zu entwickeln, die Superhelden-Daten aus der Marvel API lädt und anzeigt. Dabei kommen verschiedene moderne Kotlin-Technologien wie Ktor, Compose Multiplatform, Arrow-Kt, Kotest und Turbine zum Einsatz. Zudem findet ein Vergleich der beiden Architekturmuster MVVM und MVI statt. Der Fokus liegt dabei auf einer vollständigen Kotlin-basierten Lösung, um ein echtes multiplattformfähiges und modernes Architekturbeispiel zu schaffen. 

Projektüberblick

Das Projekt besteht aus vier Schichten: API-Client, Repository, ViewModel und UI. Die Marvel API liefert Superhelden-Daten, die mithilfe von Ktor geladen, mit Arrow-Kt funktional behandelt, im ViewModel über StateFlow verwaltet und in einer Compose UI dargestellt werden. Die Tests basieren auf Kotest und Turbine für eine moderne und asynchrone Teststrategie. 

Ktor – Die API-Anbindung

Ktor ist ein moderner HTTP-Client, der sich hervorragend in Kotlin-Projekte integriert. In diesem Projekt wird Ktor für die Kommunikation mit der Marvel API verwendet. Ktor erlaubt die Konfiguration von Standard-Parametern, Logging, JSON-Serialisierung und Fehlerbehandlung auf elegante Weise. 

Bild zeigt eine Website mit einer Ktor API Anbindung

Ktor ist vollständig Kotlin-basiert und bietet First-Class-Support für Multiplatform, was es ideal für Compose Multiplatform macht. Durch die gute Erweiterbarkeit und modulare Struktur kann es flexibel an verschiedene Anwendungsfälle angepasst werden. 

Arrow-Kt – Funktionale Fehlerbehandlung

Fehlerbehandlung ist ein kritischer Teil jeder App. Arrow-Kt bringt funktionale Programmierkonzepte nach Kotlin. In diesem Projekt wird die Raise-API und der either-Block von Arrow verwendet, um Fehler klar und typisiert zu behandeln. 

Bild zeigt eine Arrow-Kt funktionale Fehlerbehandlung

Diese Herangehensweise sorgt für eine saubere Trennung von Logik und Fehlerhandling und vermeidet verschachtelte Try-Catch-Blöcke. Sie trägt dazu bei, die Fehlerbehandlung explizit und nachvollziehbar zu gestalten, was sich positiv auf die Wartbarkeit auswirkt. 

Repository-Schicht

Das Repository kapselt die Datenlogik. Diese kommuniziert mit der API und liefert ein Flow von Superhelden. Dadurch bleibt die Datenquelle austauschbar und testbar. 

Die Daten werden paginiert geladen. Sobald alle Daten geladen sind, wird der Flow abgeschlossen. Die Verwendung von Flow erlaubt eine reaktive Verarbeitung der Daten und eine klare Trennung zwischen Datenquelle und UI. 

ViewModel mit StateFlow

Das ViewModel verwaltet den UI-Zustand mithilfe von StateFlow. Die Funktion fetchCharacters() startet einen asynchronen Ladevorgang und aktualisiert den State entsprechend: 

Dies ermöglicht eine saubere Trennung von Logik und UI, erleichtert das Testen und sorgt für konsistentes State-Handling. 

Die UI mit Compose Multiplatform

Mit Compose Multiplatform wird die Benutzeroberfläche deklarativ aufgebaut. Der aktuelle state wird beobachtet und reagiert auf Änderungen. Die Darstellung erfolgt über LazyColumn, Image, Text und weitere Compose-Komponenten. 

Durch die plattformübergreifende Funktionalität von Compose kann dieselbe UI auf Android, Desktop und auch auf iOS (mittlerweile stable) genutzt werden – ein großer Vorteil für Teams, die eine konsistente Nutzererfahrung über Plattformen hinweg schaffen wollen. 

Tests mit Kotest und Turbine

Kotest ist eine expressive Testbibliothek für Kotlin, während Turbine speziell für das Testen von Flows gedacht ist. Mit beiden Tools lassen sich StateFlow-basierte ViewModels präzise und asynchron testen. 

Ein Beispieltest mit Turbine: 

Diese Kombination erlaubt eine vollständige Abdeckung und Kontrolle asynchroner Zustände. Turbine erleichtert das Testen von Flow-Emissionen durch eine intuitive API, während Kotest durch seine DSL eine bessere Lesbarkeit der Tests ermöglicht. 

Warum diese Bibliotheken?

Für ein modernes Compose Multiplatform-Projekt ist es essenziell, plattformunabhängige und idiomatische Tools zu verwenden: 

Ktor: Ein schlanker, vollständig in Kotlin geschriebener HTTP-Client mit Multiplatform-Support. Ideal für API-Kommunikation auf allen Plattformen. 

Arrow-Kt: Bringt funktionale Programmierparadigmen nach Kotlin, erlaubt elegantes Error-Handling mit typsicheren Exceptions. 

Icon Konzeption

Kotest & Turbine: Mächtige, moderne Testwerkzeuge mit perfekter Integration für Coroutines und Flows. Sie bieten ein hohes Maß an Kontrolle und Präzision bei der Testentwicklung. 

Compose Multiplatform: Ermöglicht UI-Entwicklung über Plattformen hinweg mit nur einer Codebasis.

Alle gewählten Technologien stammen aus dem Kotlin-Ökosystem und bieten damit bestmögliche Integration und Wartbarkeit in einem Compose Multiplatform-Projekt. 

MVVM vs. MVI – Ein Architekturvergleich

Dieses Projekt wurde initial mit dem Model-View-ViewModel (MVVM)-Architekturmuster umgesetzt. Eine Alternative dazu ist das Model-View-Intent (MVI)-Muster, das auf einem einheitlichen, unidirektionalen Datenfluss basiert. Beide Muster bieten Vor- und Nachteile: 

MVVM – Aktuelle Implementierung

MVVM verwendet ein ViewModel, um UI-Status über StateFlow zu verwalten. Die UI beobachtet diesen Status und reagiert darauf. Änderungen fließen bidirektional. Dieses Muster ist leichtgewichtig und intuitiv für viele Entwicklerteams.

MVI – Alternative Umsetzung

MVI nutzt einen zentralen State, der nur durch definierte Intents verändert wird. Jede Nutzerinteraktion erzeugt einen Intent, der dann im ViewModel verarbeitet wird. Die UI selbst ist rein reaktiv. Das Muster basiert auf einem unidirektionalen Datenfluss und verwendet häufig ein „Reducer“-Konzept zur State-Veränderung.

Vergleich

Kriterium  MVVM  MVI 
Datenfluss  Bidirektional  Unidirektional 
Testbarkeit  Bei komplexen Zuständen fehleranfälliger  Deterministischer State 
Komplexität  Geringe Einstiegshürde  Höhere Komplexität, mehr Boilerplate Code 

Fehleranfälligkeit 

Eventuell inkonsistente Zustände  Klare Trennung durch reducer-Funktion 
State Historie  Nicht automatisch rückverfolgbar  Historie lässt sich leichter rekonstruieren 

Wann welches Muster?

Tastatur Icon

MVI ist besonders hilfreich bei größeren Projekten mit komplexem UI-Zustand, da der deterministische Datenfluss das Debuggen erleichtert und die Logik besser strukturiert. Besonders bei hohem Anspruch an Nachvollziehbarkeit und Testbarkeit empfiehlt sich MVI. 

Icon Konzeption

MVVM eignet sich besonders für kleinere bis mittelgroße Anwendungen, bei denen die Anzahl an Zuständen und Übergängen überschaubar bleibt. Es bietet eine gute Lesbarkeit und ist einfacher umzusetzen. 

Fazit

Dieses Projekt zeigt die Stärken von Kotlin und Compose Multiplatform bei der Entwicklung moderner Apps. Die Kombination aus Ktor, Arrow-Kt, StateFlow, Compose Multiplatform und Kotest ermöglicht eine robuste und skalierbare Architektur. 

Besonders hervorzuheben ist die klare Trennung der Schichten, die funktionale Fehlerbehandlung mit Arrow-Kt und die vollständige Testabdeckung durch Kotest & Turbine. In Verbindung mit MVVM (oder optional MVI) ergibt sich ein modernes, produktionsreifes Fundament für Kotlin-basierte Anwendungen über alle Plattformen hinweg. 

Das ganze Projekt ist hier zu finden: https://github.com/fabian-rump/cmpsuperheroes 

Die MVI-Lösung dazu befindet sich in einem separaten Branch: https://github.com/fabian-rump/cmpsuperheroes/tree/feature/mvi-architektur 

Quellen

https://www.jetbrains.com/compose-multiplatform/ 

https://kotest.io/ 

https://github.com/cashapp/turbine 

https://arrow-kt.io/ 

https://ktor.io/

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Fill out this field
Fill out this field
Bitte gib eine gültige E-Mail-Adresse ein.
You need to agree with the terms to proceed

×
Telefon

Sie sind auf der Suche nach einem Experten im Bereich App-Entwicklung? Wir freuen uns auf Ihre Nachricht!

+49 231 99953850
×