Komponent nawigacji obsługuje aplikacje Jetpack Compose. Możesz przechodzić między funkcjami kompozycyjnymi, korzystając z infrastruktury i funkcji komponentu Navigation.
Najnowszą bibliotekę nawigacji w wersji alfa, stworzoną specjalnie z myślą o Compose, znajdziesz w dokumentacji Navigation 3.
Konfiguracja
Aby obsługiwać Jetpack Compose, użyj w pliku build.gradle
modułu aplikacji tej zależności:
Groovy
dependencies { def nav_version = "2.9.5" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.5" 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 poświęconej 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. Zamiast tego podczas wykonywania działań nawigacyjnych należy przekazywać jako argumenty minimum 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 precyzyjne linki, które można zdefiniować w ramach funkcji composable()
. Jego parametr deepLinks
akceptuje 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 komponentem. 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żywać do tworzenia PendingIntent
z odpowiednim precyzyjnym linkiem z elementu kompozycyjnego:
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 dolnego paska nawigacyjnego i paska nawigacyjnego
NavigationSuiteScaffold
wyświetla odpowiedni interfejs nawigacji w zależności od WindowSizeClass
, w którym jest renderowana aplikacja. Na małych ekranach NavigationSuiteScaffold
wyświetla pasek nawigacyjny u dołu, a na większych ekranach – panel nawigacyjny.
Więcej informacji znajdziesz w artykule Tworzenie adaptacyjnej nawigacji.
Interoperacyjność
Jeśli chcesz używać komponentu Navigation z Compose, masz 2 możliwości:
- Zdefiniuj graf nawigacji za pomocą komponentu Navigation dla fragmentów.
- Zdefiniuj wykres nawigacji za pomocą
NavHost
w Compose, korzystając z miejsc docelowych Compose. 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 znajdzie się w Compose, kolejnym krokiem jest połączenie wszystkich ekranów za pomocą Navigation Compose i usunięcie wszystkich fragmentów.
Nawigacja z Compose za pomocą biblioteki Navigation dla fragmentów
Aby zmienić miejsca docelowe w kodzie Compose, udostępniasz zdarzenia, które mogą być przekazywane do dowolnej funkcji kompozycyjnej w hierarchii i przez nią wywoływane:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
W fragmencie łączysz Compose z 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 prostych funkcji jest jednak bardziej przydatne i łatwiejsze do testowania.
Testowanie
Oddziel kod nawigacji od miejsc docelowych, które można komponować, aby umożliwić testowanie każdego z nich osobno, 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 lambdę composable
umożliwia oddzielenie kodu Navigation od samego komponentu. Działa to w 2 kierunkach:
- Przekazuj do funkcji kompozycyjnej tylko przeanalizowane argumenty
- Przekazuj lambdy, które powinny być wywoływane przez komponent kompozycyjny w celu nawigacji, zamiast samego
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ć następujący podpis:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Dzięki temu ProfileScreen
komponent działa niezależnie od Navigation, co umożliwia jego niezależne testowanie. Funkcja lambda composable
zawierałaby minimalną logikę potrzebną do połączenia interfejsów API Navigation z komponentem kompozycyjnym:
@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ść NavHost
w aplikacji w funkcji kompozycyjnej, która przyjmuje 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 kompozycyjne w izolacji, zapoznaj się z samouczkiem dotyczącym testowania w Jetpack Compose.
Możesz też użyć navController
, aby sprawdzić asercje, porównując bieżącą trasę 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 nawigacji Jetpack 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, która dostosowuje się do różnych rozmiarów ekranu, orientacji i rodzajów urządzeń, przeczytaj artykuł Nawigacja w elastycznych interfejsach.
Aby dowiedzieć się więcej o bardziej zaawansowanej implementacji 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.
Próbki
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony.
- Material Design 2 w Compose
- Migracja z Jetpack Navigation do Navigation Compose
- Gdzie przenieść stan