Komponent nawigacji obsługuje aplikacje Jetpack Compose. Możesz poruszać się między funkcjami kompozycyjnymi, korzystając z infrastruktury i funkcji komponentu Navigation.
Najnowszą bibliotekę nawigacji w wersji przedpremierowej, która została stworzona specjalnie dla Compose, znajdziesz w dokumentacji Navigation 3.
Konfiguracja
Aby obsługiwać Compose, użyj tej zależności w pliku build.gradle modułu aplikacji:
Groovy
dependencies { def nav_version = "2.9.8" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.8" implementation("androidx.navigation:navigation-compose:$nav_version") }
Rozpocznij
Podczas wdrażania nawigacji w aplikacji zaimplementuj hosta, wykres i kontroler nawigacji. Więcej informacji znajdziesz w omówieniu nawigacji.
Tworzenie NavController
Informacje o tworzeniu NavController w Compose znajdziesz w sekcji Compose w artykule Tworzenie kontrolera nawigacji.
Tworzenie elementu NavHost
Informacje o tym, jak utworzyć NavHost w funkcji Compose, znajdziesz w sekcji Compose w artykule Projektowanie wykresu nawigacji.
Przejdź do funkcji kompozycyjnej
Informacje o przechodzeniu do funkcji Composable znajdziesz w sekcji Przechodzenie do miejsca docelowego w dokumentacji architektury.
Nawigacja za pomocą argumentów
Informacje o przekazywaniu argumentów między miejscami docelowymi w funkcjach kompozycyjnych znajdziesz w sekcji dotyczącej Compose w artykule Projektowanie wykresu nawigacji.
Pobieranie złożonych danych podczas nawigacji
Podczas nawigacji zdecydowanie nie zaleca się przekazywania złożonych obiektów danych, ale zamiast tego przekazywanie jako argumentów podczas wykonywania działań nawigacyjnych minimalnej ilości 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 informacji, np. w warstwie danych. Po dotarciu do miejsca docelowego możesz za pomocą przekazanego identyfikatora wczytać wymagane informacje z jednego źródła danych. Aby pobrać argumenty w ViewModel, który odpowiada za dostęp do warstwy danych, użyj SavedStateHandle w 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 wprowadzania zmian w konfiguracji i wszelkim niespójnościom podczas aktualizowania lub modyfikowania danego obiektu.
Bardziej szczegółowe wyjaśnienie, dlaczego należy unikać przekazywania złożonych danych jako argumentów, oraz listę obsługiwanych typów argumentów znajdziesz w artykule Przekazywanie danych między miejscami docelowymi.
Precyzyjne linki
Navigation Compose obsługuje też precyzyjne linki, które można zdefiniować w ramach funkcji composable(). Jego parametr deepLinks akceptuje listę obiektów NavDeepLink, które można 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 elementem kompozycyjnym. Domyślnie te precyzyjne linki nie są udostępniane aplikacjom zewnętrznym. 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 w przykładzie powyżej, dodaj w elemencie <activity> pliku manifestu ten kod:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
Gdy precyzyjny link zostanie wywołany przez inną aplikację, nawigacja automatycznie przekieruje do tego komponentu.
Tych samych precyzyjnych linków można też użyć do utworzenia PendingIntent z odpowiednim precyzyjnym linkiem z kompozycji:
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 użyć tego deepLinkPendingIntent tak jak każdego innego PendingIntent, aby otworzyć aplikację w miejscu docelowym precyzyjnego linku.
Zagnieżdżona nawigacja
Informacje o tworzeniu zagnieżdżonych wykresów nawigacji znajdziesz w artykule Zagnieżdżone wykresy.
Tworzenie adaptacyjnego paska nawigacyjnego i kolumny nawigacji
Element NavigationSuiteScaffold wyświetla odpowiedni interfejs nawigacji najwyższego poziomu dla aplikacji na podstawie bieżącego WindowSizeClass. W przypadku małych ekranów komponent Scaffold wyświetla dolny pasek nawigacyjny, a w przypadku średnich i dużych ekranów – kolumnę nawigacji.
NavigationSuiteScaffold obsługuje nawigację podstawową, ale układy adaptacyjne często obejmują inne specjalistyczne komponenty. W przypadku kanonicznych układów typu szczegółowa lista i panel pomocniczy, które są powszechne w projektach adaptacyjnych, używaj odpowiednio ListDetailPaneScaffold i SupportingPaneScaffold.
Więcej informacji znajdziesz w artykule Tworzenie układów adaptacyjnych.
Interoperacyjność
Jeśli chcesz używać komponentu Navigation z Compose, masz 2 możliwości:
- Zdefiniuj wykres nawigacji za pomocą komponentu Navigation dla fragmentów.
- Zdefiniuj wykres nawigacji za pomocą
NavHostw Compose, korzystając z Compose Destinations. Jest to możliwe tylko wtedy, gdy wszystkie ekrany w grafie nawigacji są funkcjami kompozycyjnymi.
Dlatego w przypadku aplikacji, które korzystają zarówno z Compose, jak i z widoków, zalecamy używanie komponentu nawigacji opartej na fragmentach. Fragmenty będą wtedy zawierać ekrany oparte na widokach, ekrany Compose i ekrany, które korzystają zarówno z widoków, jak i z Compose. Gdy zawartość każdego fragmentu zostanie przeniesiona do Compose, kolejnym krokiem jest powiązanie wszystkich tych ekranów za pomocą Navigation Compose i usunięcie wszystkich fragmentów.
Nawigowanie z Compose za pomocą Navigation dla fragmentów
Aby zmienić miejsca docelowe w kodzie Compose, udostępniasz zdarzenia, które mogą być przekazywane do dowolnego komponentu w hierarchii i przez niego wywoływane:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
W fragmencie tworzysz pomost między Compose a komponentem Navigation opartym na fragmentach, wyszukując NavController i przechodząc do miejsca docelowego:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
Możesz też przekazać NavController w dół hierarchii Compose.
Udostępnianie funkcji jest jednak bardziej uniwersalne i łatwiejsze do testowania.
Testowanie
Oddziel kod nawigacji od miejsc docelowych funkcji kompozycyjnych, aby umożliwić testowanie każdej funkcji kompozycyjnej w izolacji, niezależnie od funkcji NavHost.
Oznacza to, że nie należy przekazywać navController bezpośrednio do żadnego komponentu, tylko przekazywać wywołania zwrotne nawigacji jako parametry. Dzięki temu wszystkie funkcje kompozycyjne można testować indywidualnie, ponieważ nie wymagają one w testach instancji navController.
Poziom pośredniości zapewniany przez funkcję lambda composable umożliwia oddzielenie kodu Navigation od samego komponentu kompozycyjnego. Działa to w 2 kierunkach:
- Przekazuj do funkcji kompozycyjnej tylko przeanalizowane argumenty
- Przekazuj lambdy, które powinny być wywoływane przez funkcję kompozycyjną, aby nawigować, a nie samą funkcję
NavController.
Na przykład funkcja kompozycyjna ProfileScreen, która przyjmuje jako dane wejściowe userId i umożliwia użytkownikom przejście na stronę profilu znajomego, może mieć taką sygnaturę:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Dzięki temu komponent ProfileScreen działa niezależnie od Navigation, co umożliwia jego niezależne testowanie. Funkcja composable lambda zawierałaby minimalną logikę potrzebną do połączenia interfejsów Navigation API z funkcją kompozycyjną:
@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 pisanie testów, które obejmują wymagania dotyczące nawigacji w aplikacji, poprzez testowanie NavHost, działań nawigacyjnych przekazywanych do funkcji kompozycyjnych, a także poszczególnych funkcji kompozycyjnych ekranu.
Testowanie NavHost
Aby rozpocząć testowanie NavHost , dodaj tę zależność testowania nawigacji:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Umieść element NavHost aplikacji w funkcji kompozycyjnej, która przyjmuje element NavHostController jako parametr.
@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 interfejsu, który weryfikuje miejsce docelowe uruchomienia aplikacji i NavHost, może 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 przetestować na kilka sposobów, klikając elementy interfejsu, a następnie weryfikując wyświetlane miejsce docelowe lub porównując oczekiwaną trasę z bieżącą.
Ponieważ chcesz przetestować implementację konkretnej aplikacji, preferowane są kliknięcia interfejsu. Aby dowiedzieć się, jak testować poszczególne funkcje typu „composable” w izolacji, zapoznaj się z ćwiczeniem Testowanie w Jetpack Compose.
Możesz też użyć navController, aby sprawdzić asercje, porównując bieżącą ścieżkę z oczekiwaną za pomocą navController:currentBackStackEntry
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
Więcej informacji o podstawach testowania w Compose znajdziesz w artykule Testowanie układu Compose i w samouczku Testowanie w Jetpack Compose. Więcej informacji o zaawansowanym testowaniu kodu nawigacji znajdziesz w przewodniku Testowanie nawigacji.
Więcej informacji
Więcej informacji o Jetpack Navigation znajdziesz w artykule Pierwsze kroki z komponentem Navigation lub w samouczku dotyczącym nawigacji w Jetpack Compose.
Aby dowiedzieć się, jak zaprojektować nawigację w aplikacji, aby dostosowywała się do różnych rozmiarów ekranu, orientacji i formatów, przeczytaj artykuł Nawigacja w elastycznych interfejsach.
Aby dowiedzieć się więcej o bardziej zaawansowanym wdrożeniu nawigacji w Compose w aplikacji modułowej, w tym o koncepcjach takich jak zagnieżdżone wykresy i integracja paska nawigacyjnego u dołu, zapoznaj się z aplikacją Now in Android na GitHubie.
Przykłady
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Material Design 2 w Compose
- Migracja z Jetpack Navigation do Navigation Compose
- Gdzie przenieść stan