Ce guide explique comment remplacer les routes basées sur des chaînes par des types Kotlin sérialisables pour assurer la sécurité au moment de la compilation et éliminer les plantages d'exécution causés par des fautes de frappe ou des types d'arguments incorrects.
Prérequis
Avant de commencer la migration, vérifiez que votre projet répond aux exigences suivantes :
- Version de Navigation : passez à Jetpack Navigation 2.8.0 ou version ultérieure.
- Plug-in de sérialisation Kotlin :
- Ajoutez le plug-in à
libs.versions.toml:
[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" }
- Ajoutez les dépendances à votre fichier
build.gradle.ktsde premier niveau et à votre fichierbuild.gradle.ktsau niveau du module.
Étape 1 : Définissez vos destinations
Remplacez vos chaînes de route constantes par des objets et des classes @Serializable.
- Pour les écrans sans arguments : utilisez un
data object. - Pour les écrans avec des arguments : utilisez un
data class.
Avant (basé sur une chaîne) :
const val ROUTE_HOME = "home"
const val ROUTE_PROFILE = "profile/{userId}"
Après (type sécurisé) :
import kotlinx.serialization.Serializable
@Serializable
object Home
@Serializable
data class Profile(val userId: String)
Étape 2 : Mettez à jour la configuration NavHost
Mettez à jour votre NavHost pour utiliser les nouveaux types génériques dans la fonction composable et dialog.
Avant :
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(...) }
composable("profile/{userId}") { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
ProfileScreen(userId)
}
}
Après :
NavHost(navController, startDestination = Home) {
composable<Home> {
HomeScreen(...)
}
composable<Profile> { backStackEntry ->
// The library automatically handles argument extraction
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(profile.userId)
}
}
Étape 3 : Implémenter des appels de navigation avec sûreté du typage
Remplacez les appels de navigation interpolés par des instances de classe.
Avant :
navController.navigate("profile/user123")
Après :
navController.navigate(Profile(userId = "user123"))
Étape 4 : Accéder aux arguments dans les ViewModel
Si vous utilisez un ViewModel, vous pouvez désormais extraire l'objet route directement à partir de SavedStateHandle.
Implémentation :
class ProfileViewModel(
savedStateHandle: SavedStateHandle
) : ViewModel() {
// Automatically parses arguments into the Profile class
private val profile = savedStateHandle.toRoute<Profile>()
val userId = profile.userId
}
Étape 5 : (Avancé) Gérer les types personnalisés
Si vous devez transmettre des classes de données complexes (et pas seulement des primitives), vous devez définir un NavType personnalisé.
- Créez le type personnalisé :
```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)
) { ... }
Bonnes pratiques et conseils
- Hiérarchies scellées : pour les grandes applications, regroupez vos routes à l'aide d'une interface ou d'une classe scellée afin de maintenir la structure de navigation organisée.
- Instances d'objet : pour les routes sans paramètres, utilisez toujours
objectau lieu declasspour éviter les allocations inutiles. - Types pouvant être nuls : la nouvelle API accepte les types pouvant être nuls (par exemple,
data class Search(val query: String?)) et fournit automatiquement des valeurs par défaut. - Test : utilisez
navController.currentBackStackEntry?.hasRoute<T>()pour vérifier la destination actuelle de manière type-safe lors des tests d'UI.