Navigation mit der Funktion „Schreiben“

Die Navigationskomponente unterstützt Jetpack Compose-Anwendungen. Sie können zwischen den Komponenten wechseln und dabei 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:

dependencies {
    def nav_version = "2.8.8"

    implementation "androidx.navigation:navigation-compose:$nav_version"
}
dependencies {
    val nav_version = "2.8.8"

    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 Navigation.

Informationen zum Erstellen eines NavController in Compose finden Sie im Abschnitt „Compose“ des Artikels Navigationscontroller erstellen.

NavHost erstellen

Informationen zum Erstellen eines NavHost in „Compose“ finden Sie im Abschnitt „Compose“ des Artikels Navigationsdiagramm entwerfen.

Informationen zum Aufrufen eines Composeable finden Sie in der Architekturdokumentation unter Ziel aufrufen.

Informationen zum Übergeben von Argumenten zwischen zusammensetzbaren Zielen finden Sie im Abschnitt zum Erstellen von Navigationsgrafiken entwerfen.

Komplexe Daten bei der Navigation abrufen

Es wird dringend empfohlen, bei der Navigation keine komplexen Datenobjekte zu übergeben, sondern die mindestens erforderlichen Informationen wie eine eindeutige Kennung oder eine andere Art von ID als Argumente bei Navigationsaktionen weiterzugeben:

// 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 Datenebene. Sobald Sie nach der Navigation am Ziel angekommen sind, können Sie die erforderlichen Informationen mithilfe der übergebenen ID aus der Single Source of Truth 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.

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 mit der Methode navDeepLink() schnell 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)
}

Über diese Deeplinks können Sie eine bestimmte URL, Aktion oder einen MIME-Typ mit einer zusammensetzbaren Funktion 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 diesen deepLinkPendingIntent dann wie jeden anderen PendingIntent verwenden, um die App am Deeplink-Ziel zu öffnen.

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. So können Sie durch Auswahl der Symbole in der unteren Leiste navigieren.

Wenn Sie die Komponenten BottomNavigation und BottomNavigationItem verwenden möchten, fügen Sie Ihrer Android-Anwendung die Abhängigkeit androidx.compose.material hinzu.

dependencies {
    implementation "androidx.compose.material:material:1.7.5"
}

android {
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}
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)
)

Rufen Sie in der zusammensetzbaren Funktion BottomNavigation den aktuellen NavBackStackEntry mit der Funktion currentBackStackEntryAsState() 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.

Über den Pfad des Artikels wird auch die onClick-Lambda-Funktion mit einem Aufruf von navigate verbunden, damit durch Tippen auf das Element zu diesem Element gewechselt wird. Mit den Flags saveState und restoreState werden der Status und Back-Stack dieses Elements beim Wechseln zwischen den unteren Navigationselementen korrekt gespeichert und wiederhergestellt.

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 NavController.currentBackStackEntryAsState()-Methode, um den navController-Status aus der NavHost-Funktion zu extrahieren und für die BottomNavigation-Komponente freizugeben. 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 ein Navigationsdiagramm mit der Navigationskomponente für Fragmente.
  • Definieren Sie einen Navigationsgraphen mit einem NavHost in Compose mithilfe von Compose-Zielen. Das ist nur möglich, wenn alle Bildschirme im Navigationsgraphen Composables 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.

Wenn Sie Ziele im Compose-Code ändern möchten, müssen Sie Ereignisse freigeben, die an alle Compose-Elemente in der Hierarchie übergeben und von ihnen 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 das NavController-Element in der "Compose-Hierarchie" nach unten übergeben. Einfache Funktionen sind jedoch viel wiederverwendbarer und testbarer.

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. Dies 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.

Ein ProfileScreen-Komposit, das eine userId als Eingabe nimmt und Nutzern ermöglicht, zur Profilseite eines Freundes zu wechseln, könnte beispielsweise die Signatur haben:

@Composable
fun ProfileScreen(
    userId: String,
    navigateToFriendProfile: (friendUserId: String) -> Unit
) {
 
}

So funktioniert das ProfileScreen-Element unabhängig von der Navigation und kann unabhängig getestet werden. Das composable-Lambda würde die minimale Logik umfassen, die erforderlich ist, um die Lücke zwischen den Navigations-APIs und Ihrem Composeable 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"
  // ...
}

Hülle die NavHost deiner App in ein Composeable ein, das einen 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 im Codelab Testing in Jetpack Compose. Weitere Informationen zum erweiterten Testen von Navigationscode finden Sie im Leitfaden Navigation testen.

Weitere Informationen

Weitere Informationen zu Jetpack Navigation finden Sie unter Erste Schritte mit der Navigationskomponente oder im Codelab „Jetpack Compose Navigation“.

Informationen dazu, wie du die App-Navigation so gestaltest, dass sie sich an verschiedene Bildschirmgrößen, Ausrichtungen und Formfaktoren anpasst, findest du unter Navigation für responsive UIs.

Weitere Informationen über eine erweiterte Compose-Navigationsimplementierung in einer modularen App, einschließlich Konzepten wie verschachtelten Grafiken und der Einbindung von unteren Navigationsleisten, finden Sie in der App Now in Android auf GitHub.

Produktproben

Keine Ergebnisse gefunden.

Derzeit liegen keine Empfehlungen vor.

Versuchen Sie, sich bei Ihrem Google-Konto .