Руководство: Переход к типобезопасной навигации в Compose и Navigation 2

В этом руководстве описывается процесс замены строковых маршрутов сериализуемыми типами Kotlin для обеспечения безопасности на этапе компиляции и устранения сбоев во время выполнения, вызванных опечатками или некорректными типами аргументов.

Предварительные требования

Перед началом миграции убедитесь, что ваш проект соответствует следующим требованиям:

  1. Версия навигации : Обновите Jetpack Navigation до версии 2.8.0 или выше.
  2. Плагин сериализации Kotlin :
  3. Добавьте плагин в файл 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" }
  • Добавьте зависимости в главный файл build.gradle.kts и в файл build.gradle.kts на уровне модуля.

Шаг 1: Определите пункты назначения

Замените ваши постоянные строковые маршруты на объекты и классы с @Serializable .

  • Для экранов без аргументов : используйте data object
  • Для экранов с аргументами : используйте data class

До (на основе строк):

const val ROUTE_HOME = "home"
const val ROUTE_PROFILE = "profile/{userId}"

После (безопасного ввода):

import kotlinx.serialization.Serializable

@Serializable
object Home

@Serializable
data class Profile(val userId: String)

Шаг 2: Обновите конфигурацию NavHost.

Обновите NavHost , чтобы использовать новые универсальные типы в функциях composable и dialog .

До:

NavHost(navController, startDestination = "home") {
    composable("home") { HomeScreen(...) }
    composable("profile/{userId}") { backStackEntry ->
        val userId = backStackEntry.arguments?.getString("userId")
        ProfileScreen(userId)
    }
}

После:

NavHost(navController, startDestination = Home) {
    composable<Home> {
        HomeScreen(...)
    }
    composable<Profile> { backStackEntry ->
        // The library automatically handles argument extraction
        val profile: Profile = backStackEntry.toRoute()
        ProfileScreen(profile.userId)
    }
}

Шаг 3: Реализация типобезопасных вызовов навигации

Замените вызовы навигации с интерполяцией строк экземплярами классов.

До:

navController.navigate("profile/user123")

После:

navController.navigate(Profile(userId = "user123"))

Шаг 4: Доступ к аргументам в ViewModel.

Если вы используете ViewModel , теперь вы можете извлечь объект маршрута непосредственно из SavedStateHandle .

Выполнение:

class ProfileViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    // Automatically parses arguments into the Profile class
    private val profile = savedStateHandle.toRoute<Profile>()
    val userId = profile.userId
}

Шаг 5: (Расширенные) Обработка пользовательских типов

Если вам необходимо передавать сложные классы данных (а не только примитивы), необходимо определить пользовательский NavType ).

  1. Создайте пользовательский тип : ```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)
) { ... }

Передовые методы и советы

  • Иерархии с закрытым интерфейсом : Для больших приложений группируйте маршруты, используя закрытый интерфейс или класс, чтобы сохранить упорядоченную структуру навигации.
  • Экземпляры объектов : Для маршрутов без параметров всегда используйте object вместо class , чтобы избежать ненужного выделения памяти.
  • Типы, допускающие значение null : Новый API поддерживает типы, допускающие значение null (например, data class Search(val query: String?) ) и автоматически предоставляет значения по умолчанию.
  • Тестирование : Используйте navController.currentBackStackEntry?.hasRoute<T>() для проверки текущего пункта назначения типобезопасным способом во время тестирования пользовательского интерфейса.