Die Navigationskomponente bietet Unterstützung für Jetpack. Anwendungen verfassen Sie können zwischen den zusammensetzbaren Funktionen während Sie gleichzeitig die Infrastruktur und Funktionen.
Einrichten
Verwende die folgende Abhängigkeit im
build.gradle
-Datei:
Cool
dependencies { def nav_version = "2.8.0" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.0" implementation("androidx.navigation:navigation-compose:$nav_version") }
Erste Schritte
Implementieren Sie bei der Implementierung der Navigation in einer App einen Navigationshost. Graph und Controller. Weitere Informationen finden Sie in der Übersicht über die Navigation.
NavController erstellen
Informationen zum Erstellen eines NavController
-Objekts in „Schreiben“ finden Sie unter „Schreiben“
unter Navigationscontroller erstellen.
NavHost erstellen
Informationen zum Erstellen einer NavHost
in „Schreiben“ finden Sie im Abschnitt „Schreiben“
Navigationsgrafik entwerfen.
Rufen Sie eine zusammensetzbare Funktion auf.
Informationen zum Navigieren zu einem Composable finden Sie unter Zu einem Ziel in der Architektur Dokumentation.
Mit Argumenten navigieren
Navigation Compose unterstützt auch die Übergabe von Argumenten zwischen zusammensetzbaren Ziele. Dazu müssen Sie Argumentplatzhalter zu Ihrem ähnlich wie Sie Argumente zu einem tiefen Link bei Verwendung des Basis- Navigationsbibliothek:
NavHost(startDestination = "profile/{userId}") {
...
composable("profile/{userId}") {...}
}
Standardmäßig werden alle Argumente als Strings geparst. Der arguments
-Parameter von
composable()
akzeptiert eine Liste mit NamedNavArgument
-Objekten. Sie können
erstellen Sie schnell mit der Methode navArgument()
eine NamedNavArgument
und
Geben Sie dann die exakte type
an:
NavHost(startDestination = "profile/{userId}") {
...
composable(
"profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType })
) {...}
}
Sie sollten die Argumente aus dem NavBackStackEntry
extrahieren, der
in der Lambda-Funktion der Funktion composable()
verfügbar.
composable("profile/{userId}") { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
Um das Argument an das Ziel zu übergeben, müssen Sie es der Route hinzufügen und anhängen
wenn du den navigate
-Aufruf tätigst:
navController.navigate("profile/user1234")
Eine Liste der unterstützten Typen finden Sie unter Daten übergeben zwischen Ziele.
Komplexe Daten beim Navigieren abrufen
Es wird dringend empfohlen, bei der Navigation keine komplexen Datenobjekte zu umgehen, sondern geben stattdessen die mindestens erforderlichen Informationen an, oder eine andere Art von ID als Argumente beim Ausführen von Navigationsaktionen verwenden:
// Pass only the user ID when navigating to a new destination as argument
navController.navigate("profile/user1234")
Komplexe Objekte sollten als Daten in einer einzigen verlässlichen Quelle gespeichert werden, z. B.
mit der Datenschicht. Sobald Sie Ihr Ziel erreicht haben, können Sie
die erforderlichen Informationen aus der Single Source of Truth laden, indem Sie die
ID übergeben. Zum Abrufen der Argumente in der ViewModel
, die für
mit dem SavedStateHandle
von ViewModel
:
class UserViewModel(
savedStateHandle: SavedStateHandle,
private val userInfoRepository: UserInfoRepository
) : ViewModel() {
private val userId: String = checkNotNull(savedStateHandle["userId"])
// Fetch the relevant user information from the data layer,
// ie. userInfoRepository, based on the passed userId argument
private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(userId)
// …
}
Dieser Ansatz verhindert Datenverluste bei Konfigurationsänderungen Inkonsistenzen, wenn das betreffende Objekt aktualisiert oder verändert wird
Eine ausführlichere Erläuterung, warum Sie es vermeiden sollten, komplexe Daten als sowie eine Liste der unterstützten Argumenttypen finden Sie unter Daten übergeben zwischen Ziele.
Optionale Argumente hinzufügen
Navigation Compose unterstützt auch optionale Navigationsargumente. Optional Argumente unterscheiden sich in zweierlei Hinsicht von erforderlichen Argumenten:
- Sie müssen mithilfe der Syntax für Suchparameter (
"?argName={argName}"
) eingeschlossen werden. - Für sie muss ein
defaultValue
festgelegt sein odernullable = true
(wodurch der Standardwert implizit aufnull
gesetzt wird)
Das bedeutet, dass alle optionalen Argumente explizit zum Parameter
composable()
-Funktion als Liste:
composable(
"profile?userId={userId}",
arguments = listOf(navArgument("userId") { defaultValue = "user1234" })
) { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
Selbst wenn kein Argument an das Ziel übergeben wird, gilt Folgendes: defaultValue
,
„user1234“.
Die Struktur der Verarbeitung der Argumente über die Routen bedeutet, dass Ihre sind zusammensetzbare Funktionen vollständig unabhängig von Navigation und überprüfbar.
Deeplinks
Navigation Compose unterstützt implizite Deeplinks, die als Teil von
auch die Funktion composable()
. Der Parameter deepLinks
akzeptiert eine Liste von
NavDeepLink
-Objekte, die schnell mit dem
Methode navDeepLink()
:
val uri = "https://www.example.com"
composable(
"profile?id={id}",
deepLinks = listOf(navDeepLink { uriPattern = "$uri/{id}" })
) { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("id"))
}
Über diese Deeplinks können Sie eine bestimmte URL, Aktion oder MIME-Typ
zusammensetzbar sind. Standardmäßig sind diese Deeplinks nicht für externe Apps sichtbar. Bis
Wenn Sie diese Deeplinks extern verfügbar machen möchten, müssen Sie die entsprechenden
<intent-filter>
-Elemente zur manifest.xml
-Datei deiner App hinzu. Um die Tiefenmessung
im vorherigen Beispiel verwenden, müssen Sie Folgendes im
<activity>
-Element des Manifests:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
Die Navigation führt automatisch Deeplinks zu dieser zusammensetzbaren Funktion aus, wenn der Deeplink von einer anderen App ausgelöst wird.
Dieselben Deeplinks können auch verwendet werden, um ein PendingIntent
mit der
Entsprechenden Deeplink aus einer zusammensetzbaren Funktion an:
val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://www.example.com/$id".toUri(),
context,
MyActivity::class.java
)
val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
Sie können diese deepLinkPendingIntent
dann wie jedes andere PendingIntent
verwenden, um
öffnen Sie Ihre App am Ziel des Deeplinks.
Verschachtelte Navigation
Informationen zum Erstellen verschachtelter Navigationsdiagramme finden Sie unter Verschachtelte Grafiken:
Integration in die Navigationsleiste unten
Wenn Sie NavController
auf einer höheren Ebene in Ihrer zusammensetzbaren Hierarchie definieren,
können Sie die Navigation mit anderen Komponenten wie z. B. der Navigation am unteren Rand verbinden.
Komponente. Sie können dann über die Symbole am unteren Rand
.
So verwenden Sie die Komponenten BottomNavigation
und BottomNavigationItem
:
Binden Sie die androidx.compose.material
-Abhängigkeit in Ihre Android-App ein.
Cool
dependencies { implementation "androidx.compose.material:material:1.7.1" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.7.1") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
So verknüpfen Sie die Elemente in einer Navigationsleiste am unteren Rand mit Routen in Ihrer Navigationsgrafik:
empfiehlt es sich, eine versiegelte Klasse zu definieren (wie hier gezeigt Screen
), die
enthält die Route und die String-Ressourcen-ID für die Ziele.
sealed class Screen(val route: String, @StringRes val resourceId: Int) {
object Profile : Screen("profile", R.string.profile)
object FriendsList : Screen("friendslist", R.string.friends_list)
}
Fügen Sie diese Elemente dann in eine Liste ein, die vom
BottomNavigationItem
:
val items = listOf(
Screen.Profile,
Screen.FriendsList,
)
Rufen Sie in der zusammensetzbaren Funktion BottomNavigation
den aktuellen NavBackStackEntry
ab.
mit der Funktion currentBackStackEntryAsState()
. Dieser Eintrag gibt Ihnen
Zugriff auf den aktuellen NavDestination
. Den jeweils ausgewählten Status
BottomNavigationItem
kann dann ermittelt werden, indem die Route des Artikels verglichen wird
mit der Route des aktuellen Ziels und seiner übergeordneten Ziele nach
wenn Sie eine verschachtelte Navigation
mit der Funktion
NavDestination
-Hierarchie.
Die Route des Elements wird auch verwendet, um das Lambda onClick
mit einem Aufruf von
navigate
, sodass durch Tippen auf das Element zu diesem Element navigiert wird. Durch die Verwendung von
Die Flags saveState
und restoreState
sowie der Status und Back-Stack
Das Element wird beim Wechsel zwischen der unteren Navigationsleiste korrekt gespeichert und wiederhergestellt.
Elemente.
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
items.forEach { screen ->
BottomNavigationItem(
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(stringResource(screen.resourceId)) },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.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 = Screen.Profile.route, Modifier.padding(innerPadding)) {
composable(Screen.Profile.route) { Profile(navController) }
composable(Screen.FriendsList.route) { FriendsList(navController) }
}
}
Hier nutzen Sie die NavController.currentBackStackEntryAsState()
Methode zum Winden des Zustands navController
aus der Funktion NavHost
und
mit der Komponente BottomNavigation
teilen. Das bedeutet, dass der
BottomNavigation
hat automatisch den aktuellsten Status.
Sicherheit in „Navigation Compose“ eingeben
Der Code auf dieser Seite ist nicht typsicher. Sie können die navigate()
mit nicht vorhandenen Routen oder falschen Argumenten. Sie können jedoch
Ihren Navigationscode so strukturieren,
dass er zur Laufzeit typsicher ist. Auf diese Weise können Sie
Vermeiden Sie Abstürze und achten Sie auf Folgendes:
- Die Argumente, die Sie beim Navigieren zu einem Ziel oder einer Navigationsgrafik angeben die richtigen Typen sind und alle erforderlichen Argumente vorhanden sind.
- Die Argumente, die Sie von
SavedStateHandle
abrufen, sind die richtigen Typen.
Weitere Informationen hierzu finden Sie unter Typsicherheit in Kotlin DSL und Navigation. Schreiben:
Interoperabilität
Wenn Sie die Komponente „Navigation“ zusammen mit „Schreiben“ verwenden möchten, haben Sie zwei Möglichkeiten:
- Definieren Sie ein Navigationsdiagramm mit der Navigationskomponente für Fragmente.
- Navigationsdiagramm mit
NavHost
in „Schreiben“ mithilfe von „Schreiben“ definieren Ziele. Dies ist nur möglich, wenn alle Bildschirme in der Navigation sind zusammensetzbare Funktionen.
Daher empfehlen wir für gemischte Apps vom Typ „Schreiben“ und „View“ die Fragmentbasierte Komponente „Navigation“ Fragmente erhalten dann ansichtsbasiert. Erstellungsbildschirme sowie Bildschirme, auf denen Sie die Funktionen „Ansicht“ und „Schreiben“ nutzen können. Einmal Der Inhalt des Fragments befindet sich in „Compose“. Im nächsten Schritt verbindest du alle diese Bildschirme zusammen mit „Navigation Compose“ ein und entfernen Sie alle Fragmente.
Von „Schreiben mit Navigation“ für Fragmente wechseln
Um Ziele innerhalb von "Compose-Code" zu ändern, stellen Sie Ereignisse bereit, die an alle zusammensetzbaren Funktionen in der Hierarchie übergeben und ausgelöst werden:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
In Ihrem Fragment stellen Sie die Brücke zwischen Compose und dem fragmentbasierten
Navigationskomponente, indem Sie nach NavController
suchen und zum
Ziel:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
Alternativ können Sie das NavController
-Element in der "Compose-Hierarchie" nach unten übergeben.
Einfache Funktionen verfügbar zu machen, ist jedoch viel besser wiederverwendbar und testbar.
Testen
Navigationscode von den zusammensetzbaren Zielen entkoppeln, um Tests zu ermöglichen
jede zusammensetzbare Funktion isoliert von der NavHost
zusammensetzbaren Funktion.
Das bedeutet, dass Sie die navController
nicht direkt an eine
zusammensetzbar und übergeben stattdessen Navigations-Callbacks als Parameter. Dadurch können Sie
all Ihre zusammensetzbaren Funktionen einzeln testbar sein, da für sie kein
Instanz von navController
in Tests.
Mit dem Grad der Indirektion, den die Lambda-Funktion „composable
“ erzeugt, kannst du
Ihren Navigationscode von der zusammensetzbaren Funktion selbst trennen. Das funktioniert in zwei
Wegbeschreibung:
- Nur geparste Argumente an die zusammensetzbare Funktion übergeben
- Die Lambdas übergeben, die von der zusammensetzbaren Funktion ausgelöst werden sollen,
statt mit dem
NavController
selbst.
Eine zusammensetzbare Funktion Profile
, die eine userId
als Eingabe annimmt und zulässt,
Nutzer, die zur Profilseite eines Freundes navigieren möchten, könnten folgende Signatur haben:
@Composable
fun Profile(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
So funktioniert die zusammensetzbare Funktion Profile
unabhängig von Navigation.
unabhängig voneinander getestet werden. Das Lambda von composable
würde
die minimale Logik kapseln, die erforderlich ist, um die Lücke zwischen
APIs und zusammensetzbare Funktionen:
composable(
"profile?userId={userId}",
arguments = listOf(navArgument("userId") { defaultValue = "user1234" })
) { backStackEntry ->
Profile(backStackEntry.arguments?.getString("userId")) { friendUserId ->
navController.navigate("profile?userId=$friendUserId")
}
}
Es wird empfohlen, Tests zu schreiben, die Ihre Anforderungen an die App-Navigation abdecken
durch Testen von NavHost
, Navigationsaktionen bestanden
zusammensetzbaren Funktionen
und Ihren eigenen zusammensetzbaren Funktionen.
NavHost
wird getestet
Füge den folgenden Navigationstest hinzu , um NavHost
zu testen
Abhängigkeit:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Sie können das Testsubjekt „NavHost
“ einrichten und eine
der navController
-Instanz hinzu. Hierzu wird die Navigation
Das Testartefakt stellt ein TestNavHostController
bereit. Ein UI-Test, der
überprüft das Startziel Ihrer App. NavHost
würde so aussehen:
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()
}
}
Navigationsaktionen testen
Sie haben mehrere Möglichkeiten, Ihre Navigationsimplementierung zu testen: auf die UI-Elemente klickt und dann entweder oder die erwartete mit der aktuellen Route vergleichen.
Um die Implementierung Ihrer konkreten App zu testen, klicken Sie auf den Link vorzugsweise UI. Um zu erfahren, wie Sie dies mit einzelnen zusammensetzbaren Funktionen testen können, isoliert arbeiten, sollten Sie sich die Codelab zum Testen in Jetpack Compose.
Du kannst deine Assertions auch mithilfe der navController
die aktuelle Stringroute mit der erwarteten
Route zu vergleichen, indem
currentBackStackEntry
von navController
:
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
val route = navController.currentBackStackEntry?.destination?.route
assertEquals(route, "profiles")
}
Weitere Informationen zu den Grundlagen von Compose-Tests finden Sie unter Layout von Compose testen und Tests in Jetpack Compose Codelab. Weitere Informationen zu erweiterten Tests von Navigationscode finden Sie in der Navigation testen
Weitere Informationen
Weitere Informationen zur Jetpack-Navigation finden Sie unter Erste Schritte mit der Navigation. Komponente oder das Jetpack-Paket Codelab „Navigation erstellen“.
Um zu erfahren, wie Sie die Navigation Ihrer App so gestalten, dass sie sich an verschiedene Bildschirme anpasst Größen, Ausrichtungen und Formfaktoren finden Sie unter Navigation für responsive UIs.
Weitere Informationen zur erweiterten Navigationsimplementierung von „Compose“ in einem modularen Anwendungen, einschließlich Konzepten wie verschachtelten Grafiken und Navigationsleiste am unteren Rand Integration suchen, sehen Sie sich die App Now in Android auf GitHub an.
Produktproben
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Material Design 2 in Compose
- Jetpack Navigation zu Navigation Compose migrieren
- Wo soll der Windenstatus hergestellt werden?