Урок 6 из 8 · 20 мин · обновлено
Несколько экранов и навигация
Как в Compose-приложении переходить между экранами: NavController, NavHost, маршруты, передача данных на новый экран и кнопка «Назад».
Экраны и переходы
В нашем приложении будет два экрана: список покупок и подробности товара, где можно поменять количество. Переходы между экранами делают библиотекой Navigation Compose. Подключите её в build.gradle.kts, как в уроке 2, и нажмите Sync Now.
Главные понятия:
- NavController — управляет переходами и помнит историю экранов;
- NavHost — описание всех экранов: какому маршруту какой экран соответствует;
- маршрут — «адрес» экрана, как адрес страницы на сайте.
Маршруты
В современной версии Navigation Compose маршруты описывают классами — так компилятор проверяет, что вы передаёте правильные параметры:
@Serializable
object ListRoute
@Serializable
data class DetailsRoute(val productId: Int)
ListRoute— экран списка, параметров у него нет;DetailsRoute— экран подробностей, ему нужен id товара;@Serializableразрешает библиотеке превращать маршрут в адрес и обратно. Для этого в проект подключают плагин сериализации Kotlin.
NavHost
Обычно навигацию описывают в одной функции, которую вызывают из MainActivity:
@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 и модификатор у строки:
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
// содержимое строки — как раньше
}
В ShoppingScreen передайте onClick = { onOpenProduct(product.id) }, для этого добавьте в Product поле id. Экран подробностей:
@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, чтобы нажатие срабатывало на всей строке вместе с отступами.
Мини-тест: проверьте себя
Закрепите урок: несколько вопросов, разбор ответов — в конце.
-
Какой объект переключает экраны в Navigation Compose?
NavController знает историю переходов и выполняет navigate и navigateUp. NavHost описывает, какие экраны вообще есть.
-
Что лучше передать на экран подробностей товара?
Маленький параметр надёжнее: данные по id всегда актуальные, и навигация не зависит от размера объекта.
-
Что произойдёт при нажатии системной кнопки «Назад» на экране подробностей?
NavController хранит стек экранов. «Назад» снимает верхний экран со стека и показывает предыдущий.