Урок 6 из 8 · 20 мин · обновлено

Несколько экранов и навигация

Как в Compose-приложении переходить между экранами: NavController, NavHost, маршруты, передача данных на новый экран и кнопка «Назад».

Экраны и переходы

В нашем приложении будет два экрана: список покупок и подробности товара, где можно поменять количество. Переходы между экранами делают библиотекой Navigation Compose. Подключите её в build.gradle.kts, как в уроке 2, и нажмите Sync Now.

Главные понятия:

  • NavController — управляет переходами и помнит историю экранов;
  • NavHost — описание всех экранов: какому маршруту какой экран соответствует;
  • маршрут — «адрес» экрана, как адрес страницы на сайте.

Маршруты

В современной версии Navigation Compose маршруты описывают классами — так компилятор проверяет, что вы передаёте правильные параметры:

Kotlin
@Serializable
object ListRoute

@Serializable
data class DetailsRoute(val productId: Int)
  • ListRoute — экран списка, параметров у него нет;
  • DetailsRoute — экран подробностей, ему нужен id товара;
  • @Serializable разрешает библиотеке превращать маршрут в адрес и обратно. Для этого в проект подключают плагин сериализации Kotlin.

NavHost

Обычно навигацию описывают в одной функции, которую вызывают из MainActivity:

Kotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = ListRoute) {
        composable<ListRoute> {
            ShoppingScreen(
                onOpenProduct = { id ->
                    navController.navigate(DetailsRoute(id))
                },
            )
        }
        composable<DetailsRoute> { entry ->
            val route: DetailsRoute = entry.toRoute()
            DetailsScreen(
                productId = route.productId,
                onBack = { navController.navigateUp() },
            )
        }
    }
}
  • startDestination — экран, с которого начинается приложение;
  • composable<ListRoute> { … } — что показывать по этому маршруту;
  • navController.navigate(DetailsRoute(id)) — перейти на экран подробностей нужного товара;
  • entry.toRoute() достаёт параметры маршрута, здесь productId;
  • navigateUp() возвращает на предыдущий экран. Системная кнопка «Назад» работает так же, без дополнительного кода.

Обратите внимание: экраны не знают про navController. Список получает функцию onOpenProduct и просто вызывает её при нажатии на товар. Так экраны можно показывать в Preview и переиспользовать.

Что передавать на экран

На экран передают минимум данных — обычно id. Экран подробностей сам возьмёт товар по id из ViewModel или базы данных. Если передавать объект целиком, легко показать устаревшие данные: например, количество уже поменяли на другом экране.

Стек экранов работает как стопка карточек: navigate кладёт новую карточку сверху, «Назад» снимает верхнюю. Если снять последнюю, приложение закроется.

Попробуйте сами. Сделайте строку товара в списке нажимаемой: по нажатию открывается экран подробностей. На нём заголовок с названием товара и кнопка «Назад».

Решение

В ProductRow добавьте параметр onClick: () -> Unit и модификатор у строки:

Kotlin
Row(
    modifier = Modifier
        .fillMaxWidth()
        .clickable(onClick = onClick)
        .padding(16.dp),
    verticalAlignment = Alignment.CenterVertically,
) {
    // содержимое строки — как раньше
}

В ShoppingScreen передайте onClick = { onOpenProduct(product.id) }, для этого добавьте в Product поле id. Экран подробностей:

Kotlin
@Composable
fun DetailsScreen(productId: Int, onBack: () -> Unit) {
    val viewModel: ShoppingViewModel = viewModel()
    val products by viewModel.products.collectAsStateWithLifecycle()
    val product = products.find { it.id == productId }

    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = product?.name ?: "Товар не найден",
            style = MaterialTheme.typography.headlineMedium,
        )
        Button(onClick = onBack) {
            Text("Назад")
        }
    }
}

find вернёт null, если товара с таким id нет. Например, его удалили на другом устройстве. Оператор ?: из урока 3 подставит запасной текст.

clickable стоит до padding, чтобы нажатие срабатывало на всей строке вместе с отступами.

Мини-тест: проверьте себя

Закрепите урок: несколько вопросов, разбор ответов — в конце.

  1. Какой объект переключает экраны в Navigation Compose?

  2. Что лучше передать на экран подробностей товара?

  3. Что произойдёт при нажатии системной кнопки «Назад» на экране подробностей?