Компонент «Навигация» обеспечивает поддержку приложений Jetpack Compose. Вы можете перемещаться между составными элементами, используя преимущества инфраструктуры и функций компонента «Навигация».
Для ознакомления с последней предварительной версией библиотеки навигации, разработанной специально для Compose, см. документацию Navigation 3 .
Настраивать
Для поддержки Compose добавьте следующую зависимость в файл build.gradle вашего модуля приложения:
классный
dependencies { def nav_version = "2.9.7" implementation "androidx.navigation:navigation-compose:$nav_version" }
Котлин
dependencies { val nav_version = "2.9.7" implementation("androidx.navigation:navigation-compose:$nav_version") }
Начать
При реализации навигации в приложении необходимо реализовать хост навигации, граф и контроллер. Дополнительную информацию см. в разделе «Обзор навигации» .
Создайте NavController
Информацию о том, как создать NavController в Compose, см. в разделе Compose руководства по созданию контроллера навигации .
Создать NavHost
Информацию о том, как создать NavHost в Compose, см. в разделе Compose руководства «Создание графа навигации» .
Перейдите к составному элементу
Для получения информации о переходе к компоненту Composable см. раздел «Переход к месту назначения» в документации по архитектуре.
Навигация с использованием аргументов
Информацию о передаче аргументов между составными элементами интерфейса см. в разделе «Композиция» руководства «Создание графа навигации» .
Получение сложных данных при навигации
Настоятельно рекомендуется не передавать сложные объекты данных при навигации, а вместо этого передавать в качестве аргументов при выполнении навигационных действий минимально необходимую информацию, такую как уникальный идентификатор или другой вид идентификации:
// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))
Сложные объекты следует хранить в виде данных в едином источнике достоверной информации, например, в слое данных. После навигации по сайту вы можете загрузить необходимую информацию из этого единого источника, используя переданный ID. Чтобы получить аргументы в вашей ViewModel , отвечающей за доступ к слою данных, используйте SavedStateHandle вашей ViewModel :
class UserViewModel(
savedStateHandle: SavedStateHandle,
private val userInfoRepository: UserInfoRepository
) : ViewModel() {
private val profile = savedStateHandle.toRoute<Profile>()
// Fetch the relevant user information from the data layer,
// ie. userInfoRepository, based on the passed userId argument
private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(profile.id)
// …
}
Такой подход помогает предотвратить потерю данных во время изменений конфигурации, а также любые несоответствия при обновлении или изменении соответствующего объекта.
Более подробное объяснение того, почему следует избегать передачи сложных данных в качестве аргументов, а также список поддерживаемых типов аргументов см. в разделе «Передача данных между целевыми объектами» .
Прямые ссылки
Функция Navigation Compose поддерживает прямые ссылки, которые также можно определить как часть функции composable() . Параметр deepLinks принимает список объектов NavDeepLink , которые можно создать с помощью метода navDeepLink() :
@Serializable data class Profile(val id: String)
val uri = "https://www.example.com"
composable<Profile>(
deepLinks = listOf(
navDeepLink<Profile>(basePath = "$uri/profile")
)
) { backStackEntry ->
ProfileScreen(id = backStackEntry.toRoute<Profile>().id)
}
Эти глубокие ссылки позволяют связать определенный URL-адрес, действие или MIME-тип с создаваемым объектом. По умолчанию эти глубокие ссылки не доступны внешним приложениям. Чтобы сделать эти глубокие ссылки доступными извне, необходимо добавить соответствующие элементы <intent-filter> в файл manifest.xml вашего приложения. Чтобы включить глубокую ссылку в приведенном выше примере, следует добавить следующее внутри элемента <activity> манифеста:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
При активации этой составной ссылки другим приложением навигация автоматически создает прямые ссылки на этот компонент.
Эти же глубокие ссылки можно использовать для создания PendingIntent с соответствующей глубокой ссылкой из составного объекта:
val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://www.example.com/profile/$id".toUri(),
context,
MyActivity::class.java
)
val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
Затем вы можете использовать этот deepLinkPendingIntent как и любой другой PendingIntent , чтобы открыть ваше приложение по ссылке, указанной в deepLink.
Вложенная навигация
Информацию о создании вложенных навигационных графов см. в разделе «Вложенные графы» .
Создайте адаптивную панель навигации и навигационную панель.
Компонент NavigationSuiteScaffold отображает соответствующий интерфейс навигации верхнего уровня для вашего приложения в зависимости от текущего значения WindowSizeClass . Для компактных экранов компонент отображает нижнюю панель навигации; для средних и расширенных экранов — боковую панель навигации.
NavigationSuiteScaffold отвечает за основную навигацию; однако адаптивные макеты часто включают в себя другие специализированные компоненты. Для канонических макетов типа «список-подробности» и «вспомогательная панель», которые распространены в адаптивных проектах, используйте ListDetailPaneScaffold и SupportingPaneScaffold соответственно. Для получения дополнительной информации см. раздел «Создание адаптивных макетов» .
Взаимодействие
Если вы хотите использовать компонент «Навигация» с компонентом «Составить», у вас есть два варианта:
- Определите граф навигации с помощью компонента «Навигация» для фрагментов.
- Определите граф навигации с помощью
NavHostв Compose, используя назначения Compose. Это возможно только в том случае, если все экраны в графе навигации являются составными элементами.
Поэтому для приложений, использующих как Compose, так и Views, рекомендуется использовать компонент навигации на основе фрагментов. Фрагменты будут содержать экраны, созданные с помощью Views, экраны Compose и экраны, использующие как Views, так и Compose. После того, как содержимое каждого фрагмента будет добавлено в Compose, следующим шагом будет объединение всех этих экранов с помощью Navigation Compose и удаление всех фрагментов.
Навигация из меню «Составить» с помощью функции навигации для фрагментов.
Для изменения мест назначения внутри кода Compose необходимо предоставить доступ к событиям, которые могут передаваться и запускаться любым компонентом в иерархии:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
В вашем фрагменте вы создаете связь между Compose и компонентом навигации на основе фрагмента, находя NavController и переходя к месту назначения:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
В качестве альтернативы вы можете передавать NavController вниз по иерархии Compose. Однако предоставление доступа к функциям значительно повышает их повторное использование и удобство тестирования.
Тестирование
Отделите код навигации от ваших составных элементов, чтобы иметь возможность тестировать каждый составной элемент изолированно, отдельно от составного элемента NavHost .
Это означает, что вам не следует передавать navController напрямую в какой-либо компонуемый объект , а вместо этого передавать коллбэки навигации в качестве параметров. Это позволяет тестировать каждый из ваших компонуемых объектов по отдельности, поскольку в тестах им не требуется экземпляр navController .
Уровень косвенности, обеспечиваемый composable лямбда-функцией, позволяет отделить код навигации от самой компонуемой функции. Это работает в двух направлениях:
- Передайте в ваш составной объект только разобранные аргументы.
- Передайте лямбда-функции, которые должны запускаться компонуемым объектом для навигации, а не самим
NavController.
Например, компонент ProfileScreen , принимающий в качестве входных данных идентификатор userId и позволяющий пользователям переходить на страницу профиля друга, может иметь следующую сигнатуру:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Таким образом, компонент ProfileScreen работает независимо от Navigation, что позволяет тестировать его отдельно. Лямбда- composable будет инкапсулировать минимальную логику, необходимую для устранения разрыва между API Navigation и вашим компонентом:
@Serializable data class Profile(id: String)
composable<Profile> { backStackEntry ->
val profile = backStackEntry.toRoute<Profile>()
ProfileScreen(userId = profile.id) { friendUserId ->
navController.navigate(route = Profile(id = friendUserId))
}
}
Рекомендуется писать тесты, охватывающие требования к навигации вашего приложения, тестируя NavHost , действия навигации, передаваемые вашим составным объектам, а также отдельные составные объекты экранов.
Тестирование NavHost
Для начала тестирования вашего NavHost добавьте следующую зависимость navigation-testing:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Оберните NavHost вашего приложения в составной объект, который принимает NavHostController в качестве параметра.
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
Теперь вы можете протестировать AppNavHost и всю логику навигации, определенную внутри NavHost , передав экземпляр артефакта для тестирования навигации TestNavHostController . Тест пользовательского интерфейса, проверяющий начальную точку назначения вашего приложения и NavHost , будет выглядеть следующим образом:
class NavigationTest {
@get:Rule
val composeTestRule = createComposeRule()
lateinit var navController: TestNavHostController
@Before
fun setupAppNavHost() {
composeTestRule.setContent {
navController = TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(ComposeNavigator())
AppNavHost(navController = navController)
}
}
// Unit test
@Test
fun appNavHost_verifyStartDestination() {
composeTestRule
.onNodeWithContentDescription("Start Screen")
.assertIsDisplayed()
}
}
Тестирование действий навигации
Проверить реализацию навигации можно несколькими способами: кликая по элементам пользовательского интерфейса и затем либо проверяя отображаемый пункт назначения, либо сравнивая ожидаемый маршрут с текущим.
Поскольку вы хотите протестировать реализацию вашего конкретного приложения, предпочтительнее использовать клики по элементам пользовательского интерфейса. Чтобы узнать, как тестировать это вместе с отдельными составными функциями в отрыве от контекста, обязательно ознакомьтесь с практической работой по тестированию в Jetpack Compose .
Вы также можете использовать navController для проверки утверждений, сравнивая текущий маршрут с ожидаемым, используя currentBackStackEntry из navController :
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
Дополнительные сведения об основах тестирования Compose см. в разделах «Тестирование макета Compose» и «Практическое руководство по тестированию в Jetpack Compose» . Чтобы узнать больше о расширенном тестировании кода навигации, посетите руководство «Тестирование навигации» .
Узнать больше
Чтобы узнать больше о Jetpack Navigation, см. раздел «Начало работы с компонентом Navigation» или пройдите практический урок по Jetpack Compose Navigation .
Чтобы узнать, как разработать навигацию вашего приложения таким образом, чтобы она адаптировалась к различным размерам экрана, ориентации и форм-факторам, см. раздел «Навигация для адаптивных пользовательских интерфейсов» .
Чтобы узнать о более продвинутой реализации навигации Compose в модульном приложении, включая такие концепции, как вложенные графы и интеграция нижней панели навигации, ознакомьтесь с приложением Now in Android на GitHub.
Образцы
Рекомендуем вам
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Material Design 2 в Compose
- Перенести навигацию Jetpack в Navigation Compose.
- Где поднять штат