Este guia descreve o processo de substituição de rotas baseadas em strings por tipos Kotlin serializáveis para alcançar segurança no tempo de compilação e eliminar falhas no tempo de execução causadas por erros de digitação ou tipos de argumentos incorretos.
Pré-requisitos
Antes de iniciar a migração, verifique se o projeto atende aos seguintes requisitos:
- Versão do Navigation: atualize para o Jetpack Navigation 2.8.0 ou mais recente.
- Plug-in de serialização do Kotlin:
- Adicione o plug-in a
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" }
- Adicione as dependências aos arquivos
build.gradle.ktsde nível superior e do módulo.build.gradle.kts
Etapa 1: definir seus destinos
Substitua as strings de rotas constantes por objetos e classes @Serializable.
- Para telas sem argumentos: use um
data object - Para telas com argumentos: use um
data class
Antes (com base em string):
const val ROUTE_HOME = "home"
const val ROUTE_PROFILE = "profile/{userId}"
Depois (com segurança de tipo):
import kotlinx.serialization.Serializable
@Serializable
object Home
@Serializable
data class Profile(val userId: String)
Etapa 2: atualizar a configuração do NavHost
Atualize seu NavHost para usar os novos tipos genéricos na função composable e
dialog.
Antes:
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(...) }
composable("profile/{userId}") { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
ProfileScreen(userId)
}
}
Depois:
NavHost(navController, startDestination = Home) {
composable<Home> {
HomeScreen(...)
}
composable<Profile> { backStackEntry ->
// The library automatically handles argument extraction
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(profile.userId)
}
}
Etapa 3: implementar chamadas de navegação com segurança de tipo
Substitua as chamadas de navegação com interpolação de strings por instâncias de classe.
Antes:
navController.navigate("profile/user123")
Depois:
navController.navigate(Profile(userId = "user123"))
Etapa 4: acessar argumentos em ViewModels
Se você usa um ViewModel, agora é possível extrair o objeto de rota diretamente do
SavedStateHandle.
Implementação:
class ProfileViewModel(
savedStateHandle: SavedStateHandle
) : ViewModel() {
// Automatically parses arguments into the Profile class
private val profile = savedStateHandle.toRoute<Profile>()
val userId = profile.userId
}
Etapa 5: (avançado) processar tipos personalizados
Se você precisar transmitir classes de dados complexas (não apenas primitivas), defina
um NavType personalizado.
- Crie o tipo personalizado:
```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)
) { ... }
Práticas recomendadas e dicas
- Hierarquias seladas: para apps grandes, agrupe suas rotas usando uma interface ou classe selada para manter a estrutura de navegação organizada.
- Instâncias de objeto: para rotas sem parâmetros, sempre use
objectem vez declasspara evitar alocações desnecessárias. - Tipos anuláveis: a nova API é compatível com tipos anuláveis (por exemplo,
data class Search(val query: String?)) e fornece valores padrão automaticamente. - Teste: use
navController.currentBackStackEntry?.hasRoute<T>()para verificar o destino atual de maneira segura durante os testes de UI.