Die Navigationskomponente unterstützt Jetpack Compose-Anwendungen. Sie können zwischen zusammensetzbaren Funktionen wechseln und gleichzeitig die Infrastruktur und Funktionen der Navigationskomponente nutzen.
Einrichten
Verwenden Sie die folgende Abhängigkeit in der Datei build.gradle
Ihres App-Moduls, um Compose zu unterstützen:
Groovig
dependencies { def nav_version = "2.7.7" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" implementation("androidx.navigation:navigation-compose:$nav_version") }
Erste Schritte
Bei der Implementierung der Navigation in einer App sollten Sie einen Navigationshost, eine Grafik und einen Controller implementieren. Weitere Informationen finden Sie in der Übersicht zur Navigation.
NavController erstellen
Informationen zum Erstellen eines NavController
in Compose finden Sie unter Navigations-Controller erstellen im Abschnitt „Compose“.
NavHost erstellen
Informationen zum Erstellen einer NavHost
in Compose finden Sie im Abschnitt „Compose“ unter Navigationsgrafik entwerfen.
Zu einer zusammensetzbaren Funktion wechseln
Informationen zum Aufrufen einer zusammensetzbaren Funktion finden Sie in der Architekturdokumentation unter Ziel aufrufen.
Mit Argumenten navigieren
Navigation Compose unterstützt auch die Übergabe von Argumenten zwischen zusammensetzbaren Zielen. Dazu müssen Sie Ihrer Route Argumentplatzhalter hinzufügen, ähnlich wie beim Hinzufügen von Argumenten zu einem Deeplink, wenn Sie die Basisnavigationsbibliothek verwenden:
NavHost(startDestination = "profile/{userId}") {
...
composable("profile/{userId}") {...}
}
Standardmäßig werden alle Argumente als Strings geparst. Der Parameter arguments
von composable()
akzeptiert eine Liste von NamedNavArgument
-Objekten. Sie können mit der Methode navArgument()
schnell eine NamedNavArgument
erstellen und dann die genaue type
angeben:
NavHost(startDestination = "profile/{userId}") {
...
composable(
"profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType })
) {...}
}
Sie sollten die Argumente aus dem NavBackStackEntry
extrahieren, das in der Lambda-Funktion der composable()
-Funktion verfügbar ist.
composable("profile/{userId}") { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
Um das Argument an das Ziel zu übergeben, müssen Sie es an die Route anhängen, wenn Sie den navigate
-Aufruf ausführen:
navController.navigate("profile/user1234")
Eine Liste der unterstützten Typen finden Sie unter Daten zwischen Zielen übergeben.
Komplexe Daten beim Navigieren abrufen
Es wird dringend empfohlen, beim Navigieren keine komplexen Datenobjekte zu umgehen, sondern die unbedingt notwendigen Informationen wie eine eindeutige Kennung oder eine andere Art von ID als Argumente für Navigationsaktionen zu übergeben:
// 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 Datenquelle gespeichert werden, z. B. in der Datenschicht. Wenn Sie nach der Navigation an Ihrem Ziel gelandet sind, können Sie mithilfe der übergebenen ID die erforderlichen Informationen aus der Single Source of Truth laden. Wenn Sie die Argumente in der ViewModel
abrufen möchten, die für den Zugriff auf die Datenschicht verantwortlich sind, verwenden Sie den 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)
// …
}
So lassen sich Datenverluste während Konfigurationsänderungen und Inkonsistenzen beim Aktualisieren oder Modifizieren des betreffenden Objekts vermeiden.
Eine ausführlichere Erläuterung, warum Sie es vermeiden sollten, komplexe Daten als Argumente zu übergeben, sowie eine Liste der unterstützten Argumenttypen finden Sie unter Daten zwischen Zielen übergeben.
Optionale Argumente hinzufügen
Navigation Compose unterstützt auch optionale Navigationsargumente. Optionale Argumente unterscheiden sich in zweierlei Hinsicht von erforderlichen Argumenten:
- Sie müssen mithilfe der Abfrageparametersyntax (
"?argName={argName}"
) eingefügt werden. - Für sie muss ein
defaultValue
odernullable = true
festgelegt sein (wodurch der Standardwert implizit aufnull
festgelegt wird)
Das bedeutet, dass alle optionalen Argumente der Funktion composable()
explizit als Liste hinzugefügt werden müssen:
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, wird stattdessen der defaultValue
, „user1234“, verwendet.
Die Struktur der Verarbeitung der Argumente über die Routen bedeutet, dass Ihre zusammensetzbaren Funktionen völlig unabhängig von der Navigation bleiben und daher wesentlich besser testbar sind.
Deeplinks
Navigation Compose unterstützt implizite Deeplinks, die auch als Teil der composable()
-Funktion definiert werden können. Der Parameter deepLinks
akzeptiert eine Liste von NavDeepLink
-Objekten, die schnell mit der Methode navDeepLink()
erstellt werden können:
val uri = "https://www.example.com"
composable(
"profile?id={id}",
deepLinks = listOf(navDeepLink { uriPattern = "$uri/{id}" })
) { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("id"))
}
Mit diesen Deeplinks können Sie eine URL, eine Aktion oder einen MIME-Typ mit einer zusammensetzbaren Funktion verknüpfen. Standardmäßig sind diese Deeplinks nicht für externe Apps sichtbar. Wenn du diese Deeplinks extern verfügbar machen möchtest, musst du der Datei manifest.xml
deiner App die entsprechenden <intent-filter>
-Elemente hinzufügen. Um den Deeplink im vorherigen Beispiel zu aktivieren, müssen Sie Folgendes in das <activity>
-Element des Manifests einfügen:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
Die Navigation automatisch Deeplinks zu dieser zusammensetzbaren Funktion, wenn der Deeplink von einer anderen App ausgelöst wird.
Dieselben Deeplinks können auch verwendet werden, um ein PendingIntent
mit dem entsprechenden Deeplink aus einer zusammensetzbaren Funktion zu erstellen:
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 dieses deepLinkPendingIntent
dann wie jede andere PendingIntent
verwenden, um Ihre App am Ziel des Deeplinks zu öffnen.
Verschachtelte Navigation
Informationen zum Erstellen verschachtelter Navigationsgrafiken finden Sie unter Verschachtelte Grafiken.
Einbindung in die untere Navigationsleiste
Wenn Sie NavController
auf einer höheren Ebene in Ihrer zusammensetzbaren Hierarchie definieren, können Sie „Navigation“ mit anderen Komponenten wie der unteren Navigationskomponente verbinden. Dazu wählen Sie einfach die Symbole in der unteren Leiste aus.
Fügen Sie die Abhängigkeit androidx.compose.material
in Ihre Android-App ein, um die Komponenten BottomNavigation
und BottomNavigationItem
zu verwenden.
Groovig
dependencies { implementation "androidx.compose.material:material:1.6.8" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.14" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.6.8") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.14" } kotlinOptions { jvmTarget = "1.8" } }
Wenn Sie die Elemente in einer Navigationsleiste unten mit Routen in Ihrem Navigationsdiagramm verknüpfen möchten, sollten Sie eine versiegelte Klasse wie Screen
definieren, die die Route und die String-Ressourcen-ID für die Ziele enthält.
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
verwendet werden kann:
val items = listOf(
Screen.Profile,
Screen.FriendsList,
)
Rufen Sie in der zusammensetzbaren Funktion BottomNavigation
den aktuellen NavBackStackEntry
mit der Funktion currentBackStackEntryAsState()
ab. Mit diesem Eintrag erhalten Sie Zugriff auf den aktuellen NavDestination
. Der ausgewählte Status jedes BottomNavigationItem
kann dann ermittelt werden, indem die Route des Elements mit der Route des aktuellen Ziels und seiner übergeordneten Ziele verglichen wird, um Fälle zu verarbeiten, in denen Sie die verschachtelte Navigation mit der NavDestination
-Hierarchie verwenden.
Die Route des Elements wird auch verwendet, um das Lambda onClick
mit einem Aufruf von navigate
zu verbinden, sodass durch Tippen auf das Element dieses Element aufgerufen wird. Mit den Flags saveState
und restoreState
werden Status und Back Stack dieses Elements korrekt gespeichert und wiederhergestellt, wenn Sie zwischen den unteren Navigationselementen wechseln.
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 Methode NavController.currentBackStackEntryAsState()
, um den navController
-Status aus der NavHost
-Funktion zu ziehen und mit der BottomNavigation
-Komponente zu teilen. Das bedeutet, dass BottomNavigation
automatisch den aktuellen Status hat.
Sicherheit in Navigation Compose eingeben
Der Code auf dieser Seite ist nicht typsicher. Sie können die Funktion navigate()
mit nicht vorhandenen Routen oder falschen Argumenten aufrufen. Sie können Ihren Navigationscode jedoch so strukturieren, dass er zur Laufzeit typsicher ist. So können Sie Abstürze vermeiden und Folgendes sicherstellen:
- Die Argumente, die Sie beim Aufrufen eines Ziels oder einer Navigationsgrafik angeben, sind die richtigen Typen und alle erforderlichen Argumente müssen vorhanden sein.
- Die Argumente, die Sie von
SavedStateHandle
abrufen, sind die richtigen Typen.
Weitere Informationen dazu finden Sie unter Type Safety in Kotlin DSL and Navigation Composer.
Interoperabilität
Wenn Sie die Komponente „Navigation“ zusammen mit der Funktion „Schreiben“ verwenden möchten, haben Sie zwei Möglichkeiten:
- Definieren Sie mit der Komponente „Navigation“ für Fragmente ein Navigationsdiagramm.
- Definieren Sie in der Funktion „Compose“ mithilfe von „Compose“-Zielen ein Navigationsdiagramm mit einem
NavHost
. Dies ist nur möglich, wenn alle Bildschirme im Navigationsdiagramm zusammensetzbar sind.
Daher wird für gemischte Anwendungen zum Schreiben und für Ansichten empfohlen, die fragmentbasierte Navigationskomponente zu verwenden. Die Fragmente enthalten dann ansichtsbasierte Bildschirme, Bildschirmen zum Verfassen und Bildschirme, die sowohl Ansichten als auch „Schreiben“ verwenden. Sobald sich die Inhalte jedes Fragments in der Erstellung befinden, verknüpfen Sie im nächsten Schritt alle diese Bildschirme mit der Funktion „Navigationskomposition“ und entfernen alle Fragmente.
Fragmente über „Mit Navigation verfassen“ aufrufen
Um Ziele innerhalb von Compose-Code zu ändern, stellen Sie Ereignisse bereit, die an jede zusammensetzbare Funktion in der Hierarchie übergeben und durch diese ausgelöst werden können:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
In Ihrem Fragment erstellen Sie die Brücke zwischen Compose und der fragmentbasierten Navigationskomponente. Dazu suchen Sie nach NavController
und rufen zum Ziel auf:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
Alternativ können Sie die NavController
in der Erstellungshierarchie nach unten übergeben.
Die Bereitstellung einfacher Funktionen ist jedoch viel besser wiederverwendbar und testbar.
Testen
Entkoppeln Sie den Navigationscode von Ihren zusammensetzbaren Zielen, damit jede zusammensetzbare Funktion isoliert von der zusammensetzbaren Funktion NavHost
getestet werden kann.
Das bedeutet, dass Sie navController
nicht direkt an eine zusammensetzbare Funktion übergeben sollten, sondern Navigations-Callbacks als Parameter. Dadurch können alle zusammensetzbaren Funktionen einzeln getestet werden, da sie in Tests keine Instanz von navController
benötigen.
Mithilfe der von der Lambda-Funktion composable
gegebenen Indirektionsebene können Sie Ihren Navigationscode von der zusammensetzbaren Funktion selbst trennen. Dies funktioniert
in zwei Richtungen:
- Übergeben Sie nur geparste Argumente in Ihre zusammensetzbare Funktion.
- Übergeben Sie Lambdas, die von der zusammensetzbaren Funktion ausgelöst werden sollen, und nicht von
NavController
selbst.
Eine zusammensetzbare Funktion Profile
, die eine userId
als Eingabe annimmt und Nutzern ermöglicht, zur Profilseite eines Freundes zu navigieren, könnte beispielsweise die folgende Signatur haben:
@Composable
fun Profile(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Auf diese Weise funktioniert die zusammensetzbare Funktion Profile
unabhängig von Navigation und kann unabhängig getestet werden. Das Lambda composable
würde die Mindestlogik kapseln, die erforderlich ist, um die Lücke zwischen den Navigations-APIs und Ihrer zusammensetzbaren Funktion zu schließen:
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. Testen Sie dazu NavHost
, die an Ihre zusammensetzbaren Funktionen sowie die einzelnen zusammensetzbaren Funktionen, die an die Navigation übergeben werden.
NavHost
wird getestet
Fügen Sie die folgende Abhängigkeit von Navigationstests hinzu , um mit dem Testen von NavHost
zu beginnen:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Sie können Ihr NavHost
-Testobjekt einrichten und eine Instanz der navController
-Instanz an dieses übergeben. Dazu bietet das Navigationstestartefakt ein TestNavHostController
. Ein UI-Test, mit dem das Startziel Ihrer App und NavHost
bestätigt wird, sieht so aus:
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 können die Navigationsimplementierung auf verschiedene Arten testen. Klicken Sie dazu auf die UI-Elemente und prüfen Sie dann entweder das angezeigte Ziel oder die erwartete Route mit der aktuellen Route.
Wenn du die Implementierung deiner konkreten App testen möchtest, sind Klicks auf die Benutzeroberfläche vorzugsweise zu empfehlen. Im Codelab Testen in Jetpack Compose erfahren Sie, wie Sie dies mit einzelnen zusammensetzbaren Funktionen isoliert testen.
Sie können auch den navController
verwenden, um Ihre Assertions zu prüfen. Dazu vergleichen Sie die aktuelle Stringroute mit der erwarteten Route. Dazu verwenden Sie den 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 Compose-Layout testen und im Codelab zum Testen in Jetpack Compose. Weitere Informationen zum erweiterten Testen des Navigationscodes finden Sie im Leitfaden Testnavigation.
Weitere Informationen
Weitere Informationen zu Jetpack Navigation finden Sie unter Erste Schritte mit der Navigationskomponente oder im Codelab zu Jetpack Compose-Navigation.
Informationen dazu, wie Sie die Navigation Ihrer App so gestalten, dass sie sich an verschiedene Bildschirmgrößen, Ausrichtungen und Formfaktoren anpasst, finden Sie unter Navigation für responsive UI.
Weitere Informationen zu einer erweiterten Implementierung der Compose-Navigation in einer modularen App, einschließlich Konzepten wie verschachtelter Grafiken und der Einbindung der unteren Navigationsleiste, finden Sie in der App Now in Android auf GitHub.
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 Winde erreicht werden?