Навигация между экранами
Navigation Compose управляет переходами между экранами в single-activity приложении: NavController хранит стек экранов, NavHost описывает граф, а с версии 2.8 маршруты можно задавать типобезопасно.
Суть: вместо множества Activity современное приложение использует один граф навигации, где экраны — это composable, а переходы и аргументы описываются декларативно и типобезопасно.
Мы уже знаем, что современная архитектура — single-activity. Но приложению нужно несколько экранов: список, детали, профиль. За переходы между ними отвечает Navigation Compose. Два ключевых объекта: NavController управляет стеком экранов и выполняет переходы, а NavHost описывает граф — какие экраны существуют и какой стартовый.
import androidx.navigation.compose.*
import androidx.navigation.NavController
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "list") {
composable("list") {
ListScreen(onItemClick = { id ->
navController.navigate("details/$id")
})
}
composable("details/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id")
DetailsScreen(id = id)
}
}
}Типобезопасная навигация
Строковые маршруты вроде "details/$id" легко сломать опечаткой. Начиная с Navigation Compose 2.8, появилась типобезопасная навигация: маршруты задаются сериализуемыми классами, а аргументы — их полями. Компилятор проверяет правильность переходов, и ошибка всплывает на этапе сборки, а не в рантайме.
import kotlinx.serialization.Serializable
@Serializable object ListRoute
@Serializable data class DetailsRoute(val id: Int)
// переход теперь типобезопасный:
// navController.navigate(DetailsRoute(id = 42))
// аргумент достаётся как объект, без ручного парсинга строкиКак работает под капотом
NavController хранит back stack — стек записей о посещённых экранах. Переход navigate кладёт запись наверх, а кнопка «назад» снимает её. NavHost при каждом переходе показывает composable, соответствующий верхней записи стека. В типобезопасном варианте маршрут — это сериализуемый объект: библиотека кодирует его аргументы и восстанавливает на целевом экране, поэтому ручной разбор строк не нужен. Чтобы не накапливать экраны в стеке (например, после логина), используют popUpTo, очищая часть стека при переходе.
Back stack навигации navigate(Details(42)) +------------------+ | DetailsRoute(42) | <-- верх стека (виден) +------------------+ | ListRoute | +------------------+ кнопка "назад" --> снимает верхнюю запись
Частые ошибки
Создавать NavController вручную без remember. Используйте rememberNavController, иначе контроллер пересоздастся при рекомпозиции.
Передавать тяжёлые объекты как аргументы. В навигацию передают идентификаторы, а сами данные грузят на экране из репозитория.
Забывать про popUpTo. Без очистки стека после логина пользователь кнопкой «назад» вернётся на экран входа.
Best practices
- Используйте
rememberNavControllerи одинNavHostна приложение. - Предпочитайте типобезопасную навигацию (2.8+) строковым маршрутам.
- Передавайте между экранами идентификаторы, а не целые объекты.
- Управляйте стеком через
popUpToтам, где не нужен возврат.
Поведение back stack удобно смоделировать на Python как обычный стек. Запустите врезку.
# Аналог навигационного back stack
stack = ['ListRoute']
def navigate(route):
stack.append(route)
print('navigate ->', stack)
def back():
if len(stack) > 1:
stack.pop()
print('back ->', stack)
navigate('DetailsRoute(42)')
navigate('ProfileRoute')
back()
back()Попробуй сам ▶ — добавьте функцию, очищающую стек до начального экрана (аналог popUpTo после логина). В Android это управляет тем, куда приведёт кнопка «назад».
Закрепим главное
Закрепите модель «один граф вместо множества Activity». В современном приложении экраны — это composable-узлы одного графа навигации, а переходы между ними — операции над стеком в NavController. Это упрощает и состояние, и жизненный цикл: вся навигация живёт внутри одной Activity, под единым деревом Compose. Мысленно представляйте навигацию как стек карточек, который вы наращиваете при переходах и снимаете кнопкой «назад».
Второй ориентир — передавайте ссылки, а не данные. Через маршрут отправляйте идентификатор сущности, а её содержимое экран загрузит сам из репозитория. Так вы не дублируете данные, не упираетесь в ограничения на размер аргументов и сохраняете единый источник истины. Типобезопасные маршруты 2.8 делают этот контракт ещё строже: аргументы описаны полями класса, и компилятор не даст перейти на экран, забыв передать нужный идентификатор.
Итог: Navigation Compose реализует переходы в single-activity приложении через NavController и NavHost, а типобезопасные маршруты делают навигацию надёжной. На этом курс замыкается: у вас есть полный набор — от синтаксиса Kotlin до архитектуры экрана с данными, сетью и навигацией.