Komponent Nawigacja obsługuje aplikacje Jetpack Compose. Możesz poruszać się między komponentami, korzystając z ich infrastruktury i funkcji.
Najnowszą bibliotekę nawigacji w wersji alfa, która została stworzona specjalnie dla Compose, znajdziesz w dokumentacji Nawigacji 3.
Skonfiguruj
Aby obsługiwać Compose, użyj w pliku build.gradle
modułu aplikacji tej zależności:
Groovy
dependencies { def nav_version = "2.9.0" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.0" implementation("androidx.navigation:navigation-compose:$nav_version") }
Rozpocznij
Podczas implementowania nawigacji w aplikacji zastosuj hosta nawigacji, wykres i sterownik. Więcej informacji znajdziesz w artykule Nawigacja.
Tworzenie kontrolera nawigacji
Informacje o tworzeniu elementu NavController
w sekcji „Compose” (Utwórz) znajdziesz w sekcji Tworzenie kontrolera nawigacji.
Tworzenie elementu NavHost
Informacje o tworzeniu NavHost
w sekcji Redagowanie znajdziesz w artykule Projektowanie grafu nawigacyjnego.
Przejdź do komponentu.
Informacje o nawigowaniu do komponentu znajdziesz w sekcji Nawigowanie do miejsca docelowego w dokumentacji architektury.
Poruszanie się za pomocą argumentów
Informacje o przekazywaniu argumentów między miejscami docelowymi do modyfikowania znajdziesz w sekcji „Tworzenie” w artykule Projektowanie wykresu nawigacji.
Pobieranie złożonych danych podczas nawigacji
Zdecydowanie zalecamy, aby podczas nawigacji nie przekazywać skomplikowanych obiektów danych, lecz zamiast tego przekazywać jako argumenty podczas wykonywania działań nawigacyjnych minimalną ilość niezbędnych informacji, takich jak unikalny identyfikator lub inna forma identyfikatora:
// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))
Złożone obiekty powinny być przechowywane jako dane w jednym źródle prawdy, np. w warstwie danych. Gdy dotrzesz do miejsca docelowego, możesz pobrać wymagane informacje z jednego źródła informacji, używając przekazanego identyfikatora. Aby pobrać argumenty w elementach ViewModel
, które odpowiadają za dostęp do warstwy danych, użyj funkcji SavedStateHandle
elementu 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)
// …
}
Takie podejście pomaga zapobiegać utracie danych podczas zmian konfiguracji i wszelkim niespójnościom, gdy dany obiekt jest aktualizowany lub modyfikowany.
Pełniejsze wyjaśnienie, dlaczego nie należy przekazywać złożonych danych jako argumentów, oraz lista obsługiwanych typów argumentów znajdują się w artykule Przesyłanie danych między miejscami docelowymi.
Precyzyjne linki
Element Navigation Compose obsługuje precyzyjne linki, które można zdefiniować w ramach funkcji composable()
. Parametr deepLinks
przyjmuje listę obiektów NavDeepLink
, które można szybko utworzyć za pomocą metody 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)
}
Te precyzyjne linki umożliwiają powiązanie określonego adresu URL, działania lub typu mime z komponowalnym elementem. Domyślnie te precyzyjne linki nie są widoczne dla aplikacji zewnętrznych. Aby udostępnić te precyzyjne linki na zewnątrz, musisz dodać odpowiednie elementy <intent-filter>
do pliku manifest.xml
aplikacji. Aby włączyć precyzyjny link z poprzedniego przykładu, w elemencie <activity>
w pliku manifestu dodaj:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
Nawigacja automatycznie tworzy precyzyjny link do tej składowej, gdy precyzyjny link zostanie wywołany przez inną aplikację.
Tych samych precyzyjnych linków możesz też używać do tworzenia PendingIntent
z odpowiednim precyzyjnym linkiem z komponowalnego:
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)
}
Możesz potem użyć tego deepLinkPendingIntent
jak każdego innego PendingIntent
, aby otworzyć aplikację w miejscu docelowym precyzyjnego linku.
Nawigacja zagnieżdżona
Informacje o tworzeniu zagnieżdżonych diagramów nawigacji znajdziesz w artykule Zagnieżdżone diagramy.
Integracja z dolnym paskiem nawigacji
Definiując NavController
na wyższym poziomie hierarchii komponentów, możesz połączyć komponent Nawigacja z innymi komponentami, takimi jak komponent dolnej nawigacji. Dzięki temu możesz się poruszać, wybierając ikony na pasku na dole.
Aby używać komponentów BottomNavigation
i BottomNavigationItem
, dodaj do aplikacji na Androida zależność androidx.compose.material
.
Groovy
dependencies { implementation "androidx.compose.material:material:1.8.2" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.8.2") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Aby połączyć elementy na dolnym pasku nawigacji z trasami na diagramie nawigacji, zalecamy zdefiniowanie klasy, takiej jak TopLevelRoute
, która ma klasę trasy i ikonę.
data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector)
Następnie umieść te trasy na liście, z której może korzystać BottomNavigationItem
:
val topLevelRoutes = listOf(
TopLevelRoute("Profile", Profile, Icons.Profile),
TopLevelRoute("Friends", Friends, Icons.Friends)
)
W komponentach BottomNavigation
pobieraj bieżącą wartość NavBackStackEntry
za pomocą funkcji currentBackStackEntryAsState()
. Ta pozycja zapewnia dostęp do bieżącej wersji NavDestination
. Wybrany stan każdego elementu BottomNavigationItem
można następnie określić, porównując trasę elementu z trasą bieżącego miejsca docelowego i jego nadrzędnych miejsc docelowych, aby obsłużyć przypadki, gdy korzystasz z nawigacji zagnieżdżonej, używając hierarchii NavDestination
.
Trasa elementu jest też używana do łączenia funkcji lambda onClick
z wywołaniem funkcji navigate
, dzięki czemu kliknięcie elementu powoduje przejście do tego elementu. Dzięki flagom saveState
i restoreState
stan oraz stos elementów tego elementu są prawidłowo zapisywane i przywracane podczas przełączania się między elementami nawigacji u dołu ekranu.
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
topLevelRoutes.forEach { topLevelRoute ->
BottomNavigationItem(
icon = { Icon(topLevelRoute.icon, contentDescription = topLevelRoute.name) },
label = { Text(topLevelRoute.name) },
selected = currentDestination?.hierarchy?.any { it.hasRoute(topLevelRoute.route::class) } == true,
onClick = {
navController.navigate(topLevelRoute.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
}
) { innerPadding ->
NavHost(navController, startDestination = Profile, Modifier.padding(innerPadding)) {
composable<Profile> { ProfileScreen(...) }
composable<Friends> { FriendsScreen(...) }
}
}
Tutaj korzystasz z metody NavController.currentBackStackEntryAsState()
, aby wyodrębnić stan navController
z funkcji NavHost
i udostępnić go komponentowi BottomNavigation
. Oznacza to, że BottomNavigation
automatycznie ma najbardziej aktualny stan.
Interoperacyjność
Jeśli chcesz użyć komponentu Nawigacja w komponencie Compose, masz 2 możliwości:
- Zdefiniuj graf nawigacji za pomocą komponentu Nawigacja dla fragmentów.
- Zdefiniuj graf nawigacji za pomocą elementu
NavHost
w sekcji Compose (Tworzenie) przy użyciu miejsc docelowych Compose. Jest to możliwe tylko wtedy, gdy wszystkie ekrany w diagramie nawigacji są składanymi.
Dlatego w przypadku aplikacji mieszanych, które łączą komponenty Compose i View, zalecamy używanie komponentu nawigacji opartej na fragmentach. Fragmenty będą zawierać ekrany oparte na widokach, ekrany Compose oraz ekrany, które korzystają zarówno z widoków, jak i Compose. Gdy zawartość każdego fragmentu znajdzie się w Compose, należy połączyć wszystkie te ekrany z Compose nawigacji i usunąć wszystkie fragmenty.
Poruszanie się w Compose za pomocą nawigacji fragmentów
Aby zmienić miejsca docelowe w kodzie usługi Compose, udostępnij zdarzenia, które mogą być przekazywane do dowolnego komponentu w hierarchii i mogą być przez niego wywoływane:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
W fragmentie łączysz komponent Nawigacja oparty na fragmencie z komponentem Compose. Aby to zrobić, znajdź element NavController
i przejdź do miejsca docelowego:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
Możesz też przekazać NavController
w hierarchii Compose.
Jednak udostępnianie prostych funkcji jest znacznie bardziej przydatne i łatwe do przetestowania.
Testowanie
Odłącz kod nawigacji od miejsc docelowych w komponowanych treściach, aby umożliwić testowanie poszczególnych komponentów osobno, niezależnie od komponentu NavHost
.
Oznacza to, że nie należy przekazywać navController
bezpośrednio do żadnej funkcji kompozytowej, a zamiast tego należy przekazywać wywołania zwrotne nawigacji jako parametry. Dzięki temu wszystkie komponenty można testować osobno, ponieważ nie wymagają one instancji navController
w testach.
Poziom pośrednictwa zapewniany przez funkcję lambda composable
pozwala na rozdzielenie kodu nawigacji od samego komponentu. Funkcja działa w 2 kierunkach:
- Przekazywanie do kompozytowalnej funkcji tylko przeanalizowanych argumentów
- Przekazywanie funkcji lambda, które powinny być wywoływane przez komponent, do nawigacji, a nie do
NavController
.
Na przykład kompozyt ProfileScreen
, który przyjmuje jako dane wejściowe userId
i pozwala użytkownikom przejść na stronę profilu znajomego, może mieć podpis:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Dzięki temu komponent ProfileScreen
działa niezależnie od nawigacji, co pozwala na jego niezależne testowanie. Funkcja lambda composable
ujęłaby minimalną logikę potrzebną do połączenia interfejsów API nawigacji z komponowalnym:
@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))
}
}
Zalecamy napisanie testów, które obejmują wymagania dotyczące nawigacji w aplikacji. W tym celu przetestuj NavHost
, czyli czynności nawigacyjne przekazywane do komponentów, a także poszczególne komponenty ekranu.
Testowanie NavHost
Aby rozpocząć testowanie NavHost
, dodaj tę zależność testowania nawigacji:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Zawiń funkcję NavHost
aplikacji w komponent, który akceptuje jako parametr funkcję NavHostController
.
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
Teraz możesz przetestować AppNavHost
i całą logikę nawigacji zdefiniowaną w NavHost
, przekazując instancję artefaktu testowania nawigacji TestNavHostController
. Test UI, który sprawdza miejsce docelowe na początku NavHost
, wygląda tak:
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()
}
}
Testowanie działań związanych z nawigacją
Implementację nawigacji możesz testować na kilka sposobów, klikając elementy interfejsu, a następnie weryfikując wyświetlone miejsce docelowe lub porównując oczekiwaną trasę z obecną trasą.
Ponieważ chcesz przetestować implementację konkretnej aplikacji, kliknięcia w interfejsie są preferowane. Aby dowiedzieć się, jak testować te funkcje razem z poszczególnymi funkcjami składanymi, zapoznaj się z tym testem w Jetpack Compose w Codelab.
Możesz też użyć navController
, aby sprawdzić swoje twierdzenia, porównując bieżącą trasę z oczekiwaną, korzystając z funkcji navController
:currentBackStackEntry
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
Więcej wskazówek dotyczących podstaw testowania w Compose znajdziesz w artykule Testowanie układu Compose i w codelab Testowanie w Jetpack Compose. Więcej informacji o zaawansowanym testowaniu kodu nawigacji znajdziesz w przewodniku Testowanie nawigacji.
Więcej informacji
Więcej informacji o komponencie Nawigacja w Jetpacku znajdziesz w artykule Pierwsze kroki z komponentem Nawigacja lub w laboratorium kodu Nawigacja w Jetpack Compose.
Aby dowiedzieć się, jak zaprojektować nawigację w aplikacji, aby dostosowywała się do różnych rozmiarów, orientacji i formatów ekranu, zapoznaj się z artykułem Nawigacja w elastycznym interfejsie użytkownika.
Aby dowiedzieć się więcej o bardziej zaawansowanej implementacji nawigacji w Compose w ramach aplikacji modułowej, w tym o takich koncepcjach jak zagnieżdżone wykresy i integracja paska nawigacji u dołu ekranu, zapoznaj się z aplikacją Now in Android na GitHubie.
Próbki
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy obsługa JavaScript jest wyłączona
- Material Design 2 w Compose
- Migracja Jetpack Navigation do Nawigacji w Compose
- Gdzie umieścić stan