Mit der Veröffentlichung von Jetpack Compose Navigation 2.8.0-alpha08 am 01.05.2024 wurde die Möglichkeit eingeführt, Typen in der Navigation zu verwenden. Dadurch entfällt die Notwendigkeit, Strings wie in der stabilen Version zu übergeben, wodurch der Code weniger fehleranfällig wird und Entwickler:innen von der Typisierung und den Vorteilen des Linters profitieren können. Wenn die Navigation in Compose noch neu ist, empfiehlt es sich, den früheren Artikel über die Grundstrukturen der Compose-Navigation zu lesen, siehe https://www.adesso-mobile.de/android-app-programmierung/navigation-mit-android-jetpack-compose.
Mit dem Update auf Version 2.8.0-alpha08 oder höher und der Integration des Kotlin-Serialisierungs-Plugins können Klassen serialisiert und damit für das Navigations-Framework nutzbar gemacht werden.
Inhaltsverzeichnis
- Implementierung der typsicheren Navigation mit dem Kotlin-Serialization-Plugin
- Typsichere Navigationspfade für Screens in Compose Navigation definieren
- Migration des Navigationscodes zu Jetpack Compose Navigation 2.8.0
- Multimodulare Architektur und typsichere Verschachtelung der Navigation
- Übertragung komplexer Datenklassen mit Compose Navigation
- Vorteile der typsicheren Navigation in Compose Navigation
- Best Practices für die Navigation in Compose Navigation
- Fazit
Für die Implementierung wird das Kotlin Serialization Plugin verwendet, um serialisierbare Klassen bereitzustellen. Damit können sowohl einfache Datenobjekte als auch komplexere Datenklassen als Screens definiert werden. Dabei ist die @Serializable -Annotation entscheidend, um die Klassen für die Übergabe von Argumenten vorzubereiten.
Im Gegensatz zu anderen Konvertern wie Gson, die auf Java basieren, wurde Kotlin Serialization speziell für Kotlin entwickelt und funktioniert nahtlos mit der Sprache. Außerdem generiert Kotlin Serialization den Serialisierungscode zur Kompilierungszeit, während z.B. Gson auf Laufzeitreflexion (runtime-reflection) angewiesen ist. Dies erhöht die Sicherheit, da eine Serialisierung zur Kompilierungszeit Laufzeitprobleme und Abstürze präventiv verhindert.
Ab Compose Navigation 2.8.0 können Screens als Datenobjekte und Datenklassen definiert werden. Die Kapselung der verschiedenen Screens in einem Sealed Interface bietet jedoch folgende Vorteile:
- Bessere Übersichtlichkeit der möglichen Navigationspfade
- Generalisierung der Navigationspfade
- Vermeidung von Fehlern durch die versehentliche Angabe falscher Pfade
Innerhalb des Sealed Interface können einfache Datenobjekte für die Navigation zu Screens ohne Argumente und Datenklassen mit Parametern für die Navigation mit Argumenten verwendet werden.
In den Versionen vor 2.8.0 mussten die Argumente für die Navigation zu einem neuen Screen aus Strings extrahiert werden, was fehleranfällig und unübersichtlich war.
Alle Parameter können nun direkt aus der entsprechenden Datenklasse des Screens abgerufen werden. Zudem wurde im Vergleich zur Vorgängerversion eine signifikante Menge an Boilerplate-Code entfernt, wodurch die Navigationslogik auf das Wesentliche konzentriert und der Quellcode lesbarer und wartbarer wird.
Die Angabe der startDestination
im NavHost
-Konstruktor akzeptiert jetzt neben Strings auch benutzerdefinierte Typen, sodass das Datenobjekt oder die Datenklasse direkt angegeben werden kann.
Der Destination-Pfad der Composable kann nun im Gegensatz zu früher als generischer Typ mit einem Custom Type angegeben werden.
In modernen mobilen Apps ist es ab einer gewissen Projektgröße sinnvoll, von einer monolithischen zu einer modularen Architektur überzugehen. Dabei kann jede Composable-Funktion sowohl innerhalb eines Moduls als auch modulübergreifend navigieren und Daten sicher zwischen den Modulen austauschen. Die neue typsichere Navigation unterstützt diesen Ansatz und erleichtert die Verwaltung von Navigationsrouten.
Ein häufiges Problem vieler Screens ist die Unübersichtlichkeit der Navigation. Hier bietet es sich an, die Screens innerhalb der jeweiligen Module in separate Modulgraphen aufzuteilen, um eine übersichtlichere Struktur zu gewährleisten. Der NavGraphBuilder
sollte dann jeweils mit einem eindeutigen Namen für das Navigationsmodul der Screens verwendet werden.
Bei größeren Projekten ist es aus Gründen der Übersichtlichkeit und Nachvollziehbarkeit sinnvoll, die typsicheren Navigationspfade ebenfalls modulgranular in eigene Sealed Interfaces auszulagern. Diese sollten durch ein identisches Präfix definiert werden, um eine einfache Wiederverwendung zu gewährleisten. Darüber hinaus sollten alle modulgranularen Sealed Interfaces von einem gemeinsamen Sealed Interface erben.
Um neben primitiven Datentypen (wie String, Int, Boolean etc.) auch komplexe Datenklassen übergeben zu können, muss die entsprechende Datenklasse, die als Parameter übergeben werden soll, sowohl die @Serializable
-Annotation als auch die @Parcelize
-Annotation besitzen und zudem die Parcelable
-Schnittstelle implementieren.
Damit das Compose Navigation Framework versteht, wie diese komplexen Datenklassen serialisiert und deserialisiert werden sollen, wird ein benutzerdefinierter Mapper vom Typ NavType
benötigt. Es besteht die Möglichkeit, einen generischen Mapper zu erstellen, der, einmal implementiert, für die Navigation aller unterschiedlichen komplexen Datenklassen wiederverwendet werden kann.
Die Informationen können dann, wie bei primitiven Datentypen, über die NavBackStackEntry
abgefragt und weiterverarbeitet werden.
- Verringerung der Fehleranfälligkeit: Die Verwendung von Typen anstelle von Strings vermeidet Tippfehler und erleichtert die Fehlersuche.
- Übersichtlichkeit und Wartbarkeit: Die zentrale Deklaration der Navigationspfade erleichtert die Nachvollziehbarkeit und Änderbarkeit.
- Wiederverwendbarkeit: Einmal definierte Navigationspfade können einfach wiederverwendet werden, was die Fehleranfälligkeit reduziert und die Nachvollziehbarkeit erhöht.
- Zentrale Definition der Navigationspfade: Argumente und Pfade werden an einer Stelle definiert und geändert, was die Notwendigkeit verringert, Anpassungen an verschiedenen Stellen in der Navigationslogik vorzunehmen.
- ViewModels nicht in stateless Composable Screens einbinden: ViewModels sollten nicht in stateless Composables enthalten sein, um die UI-Logik testbar zu halten. Die logische Trennung zwischen der UI und der ViewModel-Logik hilft dabei, die Tests einfacher und effektiver zu gestalten.
- NavController nicht in stateless Composable Screens verwenden: Die Navigationslogik sollte strikt von der Benutzeroberfläche getrennt sein. Stattdessen sollte die Navigation über Methoden des ViewModels ausgelöst werden, um die Testbarkeit und Wartbarkeit des Codes zu verbessern.
- Minimierung der Logik im Navigationsgraphen: Im Navigationsgraphen sollten nur die unbedingt notwendigen Daten (z.B. IDs) übergeben werden. Die detaillierte Datenverarbeitung und das Laden der Inhalte sollten auf den Zielbildschirmen erfolgen.
- Entkoppelte Navigation: Die Navigationslogik sollte von den Abhängigkeiten der Feature-Module entkoppelt werden. Dies verhindert komplexe zirkuläre Abhängigkeiten und verbessert die Modularität der Anwendung.
- Verwendung von verschachtelten Graphen in multi-modularen Projekten: In Projekten mit mehreren Modulen sollte jedes Modul durch einen eigenen Navigationsgraphen repräsentiert werden. Dies sorgt für eine klare Trennung der Verantwortlichkeiten und reduziert die Komplexität.
- Zentrale Verwaltung der Navigationsrouten: Die Routen für die einzelnen Module sollten zentral in einem Navigationsmodul definiert werden. Verwenden Sie dafür Sealed Interfaces, um die Routen zu generalisieren. Ein Präfix wie z.B. Navigation<ModulName> kann für die Interfaces verwendet werden, die dann von einem gemeinsamen Sealed Interface erben.
Fazit
Jetpack Compose Navigation 2.8.0 bringt wesentliche Verbesserungen für Entwicklerinnen und Entwickler und erhöht den Reifegrad der Bibliothek. Die Einführung der typsicheren Navigation und die Möglichkeit, Datenklassen und -objekte zu verwenden, reduzieren die Fehleranfälligkeit und erhöhen die Wartbarkeit sowie Skalierbarkeit von Android-Apps. Mit den neuen Funktionen können Navigationspfade klar und robust gestaltet werden, was die Strukturierung in der Entwicklung insgesamt erleichtert.
Obwohl sich Jetpack Compose Navigation 2.8.0 noch in der Beta-Phase befindet, ist eine Veröffentlichung einer stabilen Version jederzeit möglich.
Bei adesso mobile setzen wir uns stets frühzeitig mit den neuesten Entwicklungen der Jetpack-Bibliotheken auseinander. Wir evaluieren die Mehrwerte und Risiken und prüfen den individuellen Einsatz in Projekten sorgfältig.