Переход на Navigation 3

Навигация 3 – это фундаментальное изменение в том, как Jetpack Compose обрабатывает состояние навигации. Она предлагает значительные архитектурные преимущества по сравнению с Навигацией 2.

Узнайте об изменениях в архитектуре и шагах, необходимых для переноса приложения Wear Compose с Navigation 2 на Navigation 3.

Основные преимущества Navigation 3

  • Прямое управление стеком возврата. NavBackStack – это изменяемый список объектов NavKey, представляющий историю экранов, которые посетил пользователь. Вы управляете им так же, как и любым другим объектом Kotlin MutableList (add, removeLast, clear). Вы напрямую работаете со списком, чтобы выполнять действия навигации, например добавлять ключ для перехода вперед или удалять ключ для перехода назад.
  • Compose-First Design: обратный стек моделируется как стандартное наблюдаемое состояние. Изменение истории навигации работает так же, как обновление любого другого состояния Compose, автоматически вызывая повторную композицию для отображения текущего экрана.
  • Безопасность типов по умолчанию. Маршруты на основе строк полностью исключены. Для навигации используются сериализуемые объекты данных и классы данных.
  • Отдельные презентации (стратегии сцен). Слой перехода интерфейса (NavDisplay и SwipeDismissableSceneStrategy) полностью отделен от отслеживания состояния (NavBackStack), что упрощает интеграцию встроенных переходов навигации Wear OS.

Этапы переноса

1. Обновление зависимостей

Удалена старая зависимость androidx.wear.compose:compose-navigation и добавлены новые разделенные зависимости Navigation 3, а также поддержка сериализации Kotlin.

Удалить

implementation("androidx.wear.compose:compose-navigation:...")

Добавить:

implementation("androidx.navigation3:navigation3-runtime:...") // State logic
implementation("androidx.navigation3:navigation3-ui:...")      // Display logic
implementation("androidx.wear.compose:compose-navigation3:...") // Wear gestures
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:...") // Requires compiler plugin

2. Как обновить целевые страницы, чтобы реализовать NavKey

В Navigation 2 для маршрутизации могли использоваться строки или универсальные объекты. В Navigation 3 обязательно нужно реализовать интерфейс маркера NavKey и добавить аннотацию @Serializable к каждому объекту экрана.

Зачем это нужно? Чтобы стек возврата можно было сохранить и восстановить после завершения процесса, лежащий в основе navigation3-runtime использует kotlinx-serialization для сериализации состояния.

До (Navigation 2 – универсальные маршруты с безопасностью типов):

sealed class Nav2Screen {
    data object Landing : Nav2Screen()
    data object List : Nav2Screen()
}

После (Navigation 3 – NavKey + Serializable):

@Serializable
sealed interface MigrationScreen : NavKey {
    @Serializable
    data object Landing : MigrationScreen

    @Serializable
    data object List : MigrationScreen
}

3. Заменить логику маршрутизации (NavController на NavBackStack)

Замените NavController на NavBackStack, инициализированный с помощью rememberNavBackStack. Вам также нужно создать экземпляр SwipeDismissableSceneStrategy специально для Wear OS.

До (Навигация 2):

val navController = rememberSwipeDismissableNavController()

После (Навигация 3):

val backStack = rememberNavBackStack(MigrationScreen.Landing as NavKey)
val strategy = rememberSwipeDismissableSceneStrategy<NavKey>()

4. Замените NavHost на NavDisplay и entryProvider DSL.

Контейнер NavHost и его внутренний конструктор composable("route") { ... } DSL заменены на NavDisplay и entryProvider { entry<Key> { ... } } DSL.

До (Навигация 2):

SwipeDismissableNavHost(navController = navController, startDestination = "menu") {
    composable("menu") {
        GreetingScreen(
            onShowList = { navController.navigate("list") }
        )
    }
    composable("list") {
        ListScreen()
    }
}

После (Навигация 3):

NavDisplay(
    backStack = backStack,
    sceneStrategies = listOf(strategy),
    entryProvider = entryProvider {
        entry<MigrationScreen.Landing> {
            GreetingScreen(
                onShowList = { backStack.add(MigrationScreen.List) }
            )
        }
        entry<MigrationScreen.List> {
            ListScreen()
        }
    }
)