Навигация в Android прошла путь от startActivity и транзакций фрагментов до одной Activity с Navigation Component и Navigation Compose. На собесе спрашивают back stack, передачу аргументов, типобезопасность, deep links и навигацию в многомодульном проекте.
Single Activity
Современный подход: одна Activity, экраны это composable-функции (или фрагменты), переходы делает NavController. Плюсы: общий back stack и анимации, проще передавать данные, ViewModel можно привязать к графу. Несколько Activity остаются для отдельных точек входа (например, экран звонка) и легаси.
Navigation Compose с типобезопасными маршрутами
С Navigation 2.8 маршруты описывают сериализуемыми классами вместо строк:
@Serializable object Home
@Serializable data class Article(val id: String)
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
NavHost(navController, startDestination = Home) {
composable<Home> {
HomeScreen(onOpen = { id -> navController.navigate(Article(id)) })
}
composable<Article> { entry ->
val route = entry.toRoute<Article>()
ArticleScreen(route.id)
}
}
}
Во ViewModel аргументы доступны через SavedStateHandle: savedStateHandle.toRoute<Article>(). Опечатка в имени аргумента теперь ошибка компиляции, а не падение.
Правило: передавать идентификаторы, а не объекты. Аргументы хранятся в Bundle back stack, большой объект замедлит навигацию и может не поместиться. Экран по id сам загрузит данные из репозитория.
В 2025 году Google представил Navigation 3 (androidx.navigation3): back stack это обычный список, которым владеет приложение, а NavDisplay его отображает. Основной объём кода на собесах пока про Navigation 2.
Back stack
NavController хранит стек NavBackStackEntry. Каждая запись это LifecycleOwner, ViewModelStoreOwner и SavedStateRegistryOwner: у экрана своя ViewModel, которая живёт, пока запись в стеке.
navController.navigate(Home) {
popUpTo<Login> { inclusive = true } // убрать вход из стека
launchSingleTop = true // не дублировать, если уже наверху
}
navController.popBackStack()
Нижняя навигация: при переключении вкладок используют popUpTo(graph.findStartDestination().id) { saveState = true }, launchSingleTop = true и restoreState = true, чтобы каждая вкладка сохраняла свой стек.
Вернуть результат на предыдущий экран: через previousBackStackEntry?.savedStateHandle?.set("key", value) или через общую ViewModel графа. Для однократных результатов этого хватает.
Вложенные графы
navigation<AuthGraph>(startDestination = Login) { composable<Login> {}; composable<Register> {} } группирует экраны в поток. ViewModel можно привязать к графу (navController.getBackStackEntry<AuthGraph>() как владелец), и все экраны потока получат одну.
Deep links
composable<Article>(
deepLinks = listOf(navDeepLink<Article>(basePath = "https://example.com/article"))
) { }
В манифесте Activity нужен intent-filter с VIEW, BROWSABLE и схемой. Для https-ссылок, которые открываются без диалога выбора, нужны App Links: android:autoVerify="true" и файл /.well-known/assetlinks.json на домене. При открытии по deep link Navigation строит синтетический back stack, чтобы «Назад» вело на родительский экран, а не закрывало приложение.
Системная кнопка «Назад»
С Android 13 появился predictive back: система показывает анимацию предпросмотра, а приложение должно сообщать заранее, обработает ли оно жест. Поэтому onBackPressed() устарел, используют OnBackPressedDispatcher с OnBackPressedCallback, в Compose BackHandler { } и PredictiveBackHandler. Navigation обрабатывает «Назад» сам.
Многомодульная навигация
Feature-модули не должны зависеть друг от друга. Обычный подход: каждая фича экспортирует функцию-расширение NavGraphBuilder.articleScreen(onBack: () -> Unit) и маршрут, а связывает графы модуль app. Переходы между фичами передаются колбэками или через API-модули с маршрутами.
Частые ошибки
- Передача больших объектов в аргументах навигации.
- Вызов
navigateпрямо в теле composable (вызывается при каждой рекомпозиции): только в колбэках или эффектах. NavControllerво ViewModel.- Двойной клик открывает экран дважды: нет
launchSingleTopили защиты от повторного нажатия. - Не продуманный стек после логина: «Назад» возвращает на экран входа.
Что спросят на собесе
- Как устроен back stack в Navigation и что такое
NavBackStackEntry? - Как передать аргументы и почему лучше передавать id?
- Как вернуть результат на предыдущий экран?
- Как работают deep links и App Links?
- Как организовать навигацию в многомодульном проекте?
- Как работает нижняя навигация с сохранением стека вкладок?