In diesem Leitfaden wird beschrieben, wie Sie stringbasierte Routen durch serialisierbare Kotlin-Typen ersetzen, um die Kompilierzeitsicherheit zu erhöhen und Laufzeitabstürze zu vermeiden, die durch Tippfehler oder falsche Argumenttypen verursacht werden.
Voraussetzungen
Prüfen Sie vor Beginn der Migration, ob Ihr Projekt die folgenden Anforderungen erfüllt:
- Navigationsversion: Update auf Jetpack Navigation 2.8.0 oder höher
- Kotlin-Serialisierungs-Plug-in:
- Fügen Sie das Plug-in zu
libs.versions.tomlhinzu:
[libraries]
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
- Fügen Sie die Abhängigkeiten der Datei
build.gradle.ktsauf oberster Ebene und der Dateibuild.gradle.ktsauf Modulebene hinzu.
Schritt 1: Ziele definieren
Ersetzen Sie Ihre konstanten Routenstrings durch @Serializable-Objekte und -Klassen.
- Für Bildschirme ohne Argumente: Verwenden Sie ein
data object. - Für Bildschirme mit Argumenten: Verwenden Sie eine
data class.
Vorher (String-basiert):
const val ROUTE_HOME = "home"
const val ROUTE_PROFILE = "profile/{userId}"
Nachher (typsicher):
import kotlinx.serialization.Serializable
@Serializable
object Home
@Serializable
data class Profile(val userId: String)
Schritt 2: NavHost-Konfiguration aktualisieren
Aktualisieren Sie NavHost, um die neuen generischen Typen in den Funktionen composable und dialog zu verwenden.
Vorher:
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(...) }
composable("profile/{userId}") { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
ProfileScreen(userId)
}
}
Nachher:
NavHost(navController, startDestination = Home) {
composable<Home> {
HomeScreen(...)
}
composable<Profile> { backStackEntry ->
// The library automatically handles argument extraction
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(profile.userId)
}
}
Schritt 3: Typsichere Navigationsaufrufe implementieren
Ersetzen Sie String-interpolierte Navigationsaufrufe durch Klasseninstanzen.
Vorher:
navController.navigate("profile/user123")
Nachher:
navController.navigate(Profile(userId = "user123"))
Schritt 4: Auf Argumente in ViewModels zugreifen
Wenn Sie ein ViewModel verwenden, können Sie das Routenobjekt jetzt direkt aus dem SavedStateHandle extrahieren.
Implementierung:
class ProfileViewModel(
savedStateHandle: SavedStateHandle
) : ViewModel() {
// Automatically parses arguments into the Profile class
private val profile = savedStateHandle.toRoute<Profile>()
val userId = profile.userId
}
Schritt 5 (erweitert): Benutzerdefinierte Typen verarbeiten
Wenn Sie komplexe Datenklassen (nicht nur Primitiven) übergeben müssen, müssen Sie eine benutzerdefinierte NavType definieren.
- Benutzerdefinierten Typ erstellen:
```kotlin
val SearchFilterType = object : NavType
(isNullableAllowed = false) { override fun get(bundle: Bundle, key: String): SearchFilter? = Json.decodeFromString(bundle.getString(key) ?: return null)
override fun parseValue(value: String): SearchFilter =
Json.decodeFromString(Uri.decode(value))
override fun put(bundle: Bundle, key: String, value: SearchFilter) {
bundle.putString(key, Json.encodeToString(value))
}
}
2. **Register it in the Graph**:
```kotlin
composable<Search>(
typeMap = mapOf(typeOf<SearchFilter>() to SearchFilterType)
) { ... }
Best Practices und Tipps
- Versiegelte Hierarchien: Bei großen Apps sollten Sie Ihre Routen mit einer versiegelten Schnittstelle oder Klasse gruppieren, um die Navigationsstruktur zu organisieren.
- Objektinstanzen: Verwenden Sie für Routen ohne Parameter immer
objectanstelle vonclass, um unnötige Zuweisungen zu vermeiden. - Nullable-Typen: Die neue API unterstützt Nullable-Typen (z. B.
data class Search(val query: String?)) und stellt automatisch Standardwerte bereit. - Tests: Mit
navController.currentBackStackEntry?.hasRoute<T>()können Sie das aktuelle Ziel während der UI-Tests typsicher prüfen.