Navigation mit der Funktion „Schreiben“

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.

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.

Informationen zum Navigieren zu einem Composable finden Sie unter Zu einem Ziel in der Architektur Dokumentation.

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 oder nullable = true (wodurch der Standardwert implizit auf null 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.

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.

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