Die Navigationskomponente unterstützt Jetpack Compose-Anwendungen. Sie können zwischen den zusammensetzbaren Funktionen wechseln und gleichzeitig die Infrastruktur und Funktionen der Navigationskomponente nutzen.
Einrichten
Wenn Sie Compose unterstützen möchten, verwenden Sie die folgende Abhängigkeit in der build.gradle
-Datei Ihres App-Moduls:
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 Navigation in einer App einen Navigationshost, ein Navigationsdiagramm und einen Navigationscontroller. Weitere Informationen finden Sie in der Übersicht über die Navigation.
NavController erstellen
Informationen zum Erstellen eines NavController
in „Compose“ finden Sie im Abschnitt „Compose“ von Navigationscontroller erstellen.
NavHost erstellen
Informationen zum Erstellen einer NavHost
in Compose finden Sie im Abschnitt „Compose“ des Artikels Navigationsgraph entwerfen.
Rufen Sie eine zusammensetzbare Funktion auf
Informationen zum Aufrufen einer zusammensetzbaren Funktion finden Sie in der Architekturdokumentation unter Zu einem Ziel navigieren.
Mit Argumenten navigieren
Informationen zum Übergeben von Argumenten zwischen zusammensetzbaren Zielen finden Sie im Abschnitt „Zusammenstellen“ des Artikels Navigationsgraph entwerfen.
Komplexe Daten bei der Navigation abrufen
Wir empfehlen dringend, bei der Navigation keine komplexen Datenobjekte zu übergeben, sondern stattdessen die minimal erforderlichen 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(id = "user1234"))
Komplexe Objekte sollten als Daten in einer einzigen Datenquelle gespeichert werden, z. B. in der Datenschicht. Sobald Sie das Ziel erreicht haben, können Sie die erforderlichen Informationen mithilfe der übergebenen ID aus der einzigen Quelle der Wahrheit laden. Um die Argumente in der ViewModel
abzurufen, die für den Zugriff auf die Datenschicht verantwortlich ist, verwenden Sie den SavedStateHandle
des 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)
// …
}
Dadurch werden Datenverluste während Konfigurationsänderungen und Inkonsistenzen beim Aktualisieren oder Ändern des betreffenden Objekts verhindert.
Eine ausführlichere Erklärung dazu, warum Sie komplexe Daten nicht als Argumente übergeben sollten, sowie eine Liste der unterstützten Argumenttypen finden Sie unter Daten zwischen Zielen übergeben.
Deeplinks
Navigation Compose unterstützt Deeplinks, die auch als Teil der Funktion composable()
definiert werden können. Der Parameter deepLinks
akzeptiert eine Liste von NavDeepLink
-Objekten, die schnell mit der Methode navDeepLink()
erstellt werden können:
@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)
}
Mit diesen Deeplinks kannst du eine bestimmte URL, Aktion oder einen bestimmten MIME-Typ mit einem Composeable verknüpfen. Standardmäßig sind diese Deeplinks für externe Apps nicht sichtbar. Wenn Sie diese Deeplinks extern verfügbar machen möchten, müssen Sie der Datei manifest.xml
Ihrer 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>
Bei der Navigation wird automatisch ein Deeplink zu diesem Composeable erstellt, wenn der Deeplink von einer anderen App ausgelöst wird.
Mit diesen Deeplinks können Sie auch eine PendingIntent
mit dem entsprechenden Deeplink aus einem Composeable erstellen:
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)
}
Sie können diese deepLinkPendingIntent
dann wie jede andere deepLinkPendingIntent
verwenden, um Ihre App am Deeplink-Ziel zu öffnen.PendingIntent
Verschachtelte Navigation
Informationen zum Erstellen verschachtelter Navigationsgraphen finden Sie unter Verschachtelte Diagramme.
Einbindung in die untere Navigationsleiste
Wenn Sie die NavController
auf einer höheren Ebene in Ihrer hierarchischen Zusammenstellung definieren, können Sie die Navigation mit anderen Komponenten wie der Navigationsleiste unten verknüpfen. Wählen Sie dazu die Symbole in der unteren Leiste aus.
Wenn Sie die Komponenten BottomNavigation
und BottomNavigationItem
verwenden möchten, fügen Sie Ihrer Android-Anwendung die Abhängigkeit androidx.compose.material
hinzu.
Groovy
dependencies { implementation "androidx.compose.material:material:1.7.5" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.7.5") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Wenn Sie die Elemente in einer unteren Navigationsleiste mit Routen in Ihrem Navigationsgraphen verknüpfen möchten, sollten Sie eine Klasse wie TopLevelRoute
definieren, die eine Routenklasse und ein Symbol hat.
data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector)
Fügen Sie diese Routen dann in eine Liste ein, die von BottomNavigationItem
verwendet werden kann:
val topLevelRoutes = listOf(
TopLevelRoute("Profile", Profile, Icons.Profile),
TopLevelRoute("Friends", Friends, Icons.Friends)
)
Rufe in deinem BottomNavigation
-Composeable mit der Funktion currentBackStackEntryAsState()
den aktuellen NavBackStackEntry
ab. Über diesen 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. So lassen sich Fälle bearbeiten, wenn 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 zu diesem Element navigiert wird. Mit den Flags saveState
und restoreState
werden der Status und der Backstack dieses Elements korrekt gespeichert und wiederhergestellt, wenn Sie zwischen den Navigationselementen unten wechseln.
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(...) }
}
}
Hier nutzen Sie die Methode NavController.currentBackStackEntryAsState()
, um den Zustand navController
aus der Funktion NavHost
herauszuziehen und mit der Komponente BottomNavigation
zu teilen. Das bedeutet, dass BottomNavigation
automatisch den aktuellsten Status hat.
Interoperabilität
Wenn Sie die Navigationskomponente mit Compose verwenden möchten, haben Sie zwei Möglichkeiten:
- Definieren Sie mit der Navigationskomponente ein Navigationsdiagramm für Fragmente.
- Definieren Sie einen Navigationsgraphen mit einem
NavHost
in Compose mithilfe von Compose-Zielen. Dies ist nur möglich, wenn alle Bildschirme im Navigationsdiagramm zusammensetzbar sind.
Daher wird für gemischte Apps vom Typ „Schreiben“ und „Views“ empfohlen, die Komponente „Fragmentbasierte Navigation“ zu verwenden. Fragmente enthalten dann ansichtenbasierte Bildschirme, Compose-Bildschirme und Bildschirme, auf denen sowohl Ansichten als auch Compose verwendet werden. Sobald sich der Inhalt jedes Fragments in Compose befindet, besteht der nächste Schritt darin, alle diese Bildschirme mit Navigation Compose zu verknüpfen und alle Fragmente zu entfernen.
Zwischen Compose und Navigation für Fragmente wechseln
Um Ziele innerhalb des Compose-Codes zu ändern, stellen Sie Ereignisse bereit, die an jede zusammensetzbare Funktion in der Hierarchie übergeben und ausgelöst werden können:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
In Ihrem Fragment stellen Sie eine Verbindung zwischen Compose und der fragmentbasierten Navigationskomponente her, indem Sie die NavController
suchen und zum Ziel navigieren:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
Alternativ können Sie NavController
in der Compose-Hierarchie weitergeben.
Einfache Funktionen verfügbar zu machen, ist jedoch viel besser wiederverwendbar und testbar.
Testen
Entkoppeln Sie den Navigationscode von Ihren Composeable-Zielen, damit jedes Composeable unabhängig vom NavHost
-Composeable getestet werden kann.
Das bedeutet, dass du die navController
nicht direkt in ein composable übergeben solltest, sondern Navigations-Callbacks als Parameter. So können alle Ihre Composeables einzeln getestet werden, da in Tests keine Instanz von navController
erforderlich ist.
Durch die Indirektion, die das composable
-Lambda bietet, können Sie Ihren Navigationscode vom eigentlichen Composeable trennen. Das funktioniert in zwei Richtungen:
- Übergeben Sie nur geparste Argumente an Ihre Composeable-Funktion.
- Übergeben Sie Lambdas, die vom Composeable ausgelöst werden sollen, um zur Navigation zu gelangen, und nicht vom
NavController
selbst.
Eine zusammensetzbare Funktion ProfileScreen
, die eine userId
als Eingabe übernimmt und Nutzern ermöglicht, die Profilseite eines Freundes aufzurufen, könnte beispielsweise folgende Signatur haben:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Auf diese Weise arbeitet die zusammensetzbare Funktion ProfileScreen
unabhängig von Navigation und kann unabhängig getestet werden. Die Lambda-Funktion composable
würde die minimale Logik kapseln, die erforderlich ist, um die Lücke zwischen den Navigation APIs und der zusammensetzbaren Funktion zu schließen:
@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))
}
}
Wir empfehlen, Tests zu schreiben, die die Anforderungen an die Navigation in Ihrer App abdecken. Dazu sollten Sie die NavHost
, die Navigationsaktionen, die an Ihre Composeables übergeben werden, sowie die einzelnen Bildschirm-Composeables testen.
NavHost
testen
Wenn Sie mit dem Testen Ihrer NavHost
beginnen möchten , fügen Sie die folgende Abhängigkeit für Navigationstests hinzu:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Schließen Sie das NavHost
Ihrer Anwendung in eine zusammensetzbare Funktion ein, die NavHostController
als Parameter akzeptiert.
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
Jetzt können Sie AppNavHost
und die gesamte in NavHost
definierte Navigationslogik testen, indem Sie eine Instanz des Navigationstest-Artefakts TestNavHostController
übergeben. Ein UI-Test, der das Startziel Ihrer App und NavHost
prüft, 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 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 vergleichen Sie die voraussichtliche Route mit der aktuellen Route.
Da Sie die Implementierung Ihrer App testen möchten, sind Klicks auf die Benutzeroberfläche vorzuziehen. Informationen dazu, wie Sie dies zusammen mit einzelnen kombinierbaren Funktionen separat testen, finden Sie im Codelab Testing in Jetpack Compose.
Sie können auch navController
verwenden, um Ihre Assertions zu prüfen. Dazu vergleichen Sie die aktuelle Route mithilfe des currentBackStackEntry
von navController
mit der erwarteten Route:
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
Weitere Informationen zu den Grundlagen von Compose-Tests finden Sie unter Compose-Layout testen und Testen in Jetpack Compose. Weitere Informationen zu erweiterten Tests von Navigationscode finden Sie im Leitfaden Navigation testen.
Weitere Informationen
Weitere Informationen zu Jetpack Navigation finden Sie unter Erste Schritte mit der Komponente „Navigation“ oder im Codelab zu Jetpack Compose Navigation.
Weitere Informationen dazu, wie Sie die Navigation Ihrer App so gestalten, dass sie sich an unterschiedliche Bildschirmgrößen, -ausrichtungen und Formfaktoren anpasst, finden Sie unter Navigation für responsive Benutzeroberflächen.
Eine erweiterte Implementierung der Compose-Navigation in einer modularen App, einschließlich Konzepten wie verschachtelten Diagrammen und der Integration der unteren Navigationsleiste, finden Sie in der Now in Android App auf GitHub.
Produktproben
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Material Design 2 in der compose-Ansicht
- Jetpack Navigation zu Navigation Compose migrieren
- Wo hoist-state verwenden