Wenn Sie Kotlin-DSL zum Erstellen Ihrer Grafik verwenden, kann es schwierig sein, Ziele und Navigationsereignisse in einer einzigen Datei zu verwalten. Dies gilt insbesondere, wenn Sie mehrere unabhängige Features haben.
Ziele extrahieren
Sie sollten Ihre Ziele in die Funktionen der NavGraphBuilder
-Erweiterung verschieben. Sie sollten sich in der Nähe der Routen und der angezeigten Bildschirme befinden. Betrachten Sie beispielsweise den folgenden Code auf App-Ebene, mit dem ein Ziel mit einer Liste von Kontakten erstellt wird:
// MyApp.kt
@Serializable
object Contacts
@Composable
fun MyApp() {
...
NavHost(navController, startDestination = Contacts) {
composable<Contacts> { ContactsScreen( /* ... */ ) }
}
}
Sie sollten den navigationsspezifischen Code in eine separate Datei verschieben:
// ContactsNavigation.kt
@Serializable
object Contacts
fun NavGraphBuilder.contactsDestination() {
composable<Contacts> { ContactsScreen( /* ... */ ) }
}
// MyApp.kt
@Composable
fun MyApp() {
...
NavHost(navController, startDestination = Contacts) {
contactsDestination()
}
}
Die Routen- und Zieldefinitionen sind jetzt von der Hauptanwendung getrennt und können unabhängig voneinander aktualisiert werden. Die Hauptanwendung ist nur von einer einzigen Erweiterungsfunktion abhängig. In diesem Fall ist das NavGraphBuilder.contactsDestination()
.
Die Erweiterungsfunktion NavGraphBuilder
bildet die Brücke zwischen einer zustandslosen zusammensetzbaren Funktion auf Bildschirmebene und einer navigationsspezifischen Logik. Diese Ebene kann auch definieren, woher der Status stammt und wie Ereignisse verarbeitet werden.
Beispiel
Mit dem folgenden Snippet wird ein neues Ziel eingeführt, um die Details eines Kontakts anzuzeigen. Das vorhandene Ziel der Kontaktliste wird aktualisiert, um ein Navigationsereignis verfügbar zu machen, um die Details des Kontakts anzuzeigen.
Hier sehen Sie einen typischen Satz von Bildschirmen, für die ihr eigenes Modul mit internal
verknüpft werden kann, damit andere Module nicht darauf zugreifen können:
// ContactScreens.kt
// Displays a list of contacts
@Composable
internal fun ContactsScreen(
uiState: ContactsUiState,
onNavigateToContactDetails: (contactId: String) -> Unit
) { ... }
// Displays the details for an individual contact
@Composable
internal fun ContactDetailsScreen(contact: ContactDetails) { ... }
Ziele erstellen
Die folgende NavGraphBuilder
-Erweiterungsfunktion erstellt ein Ziel, das die zusammensetzbare Funktion ConversationScreen
anzeigt. Außerdem wird der Bildschirm jetzt mit einem ViewModel
verbunden, das den Bildschirm-UI-Status bereitstellt und die bildschirmbezogene Geschäftslogik verarbeitet.
Navigationsereignisse, wie das Aufrufen des Ziels für die Kontaktdaten, werden dem Aufrufer angezeigt und nicht von ViewModel
verarbeitet.
// ContactsNavigation.kt
@Serializable
object Contacts
// Adds contacts destination to `this` NavGraphBuilder
fun NavGraphBuilder.contactsDestination(
// Navigation events are exposed to the caller to be handled at a higher level
onNavigateToContactDetails: (contactId: String) -> Unit
) {
composable<Contacts> {
// The ViewModel as a screen level state holder produces the screen
// UI state and handles business logic for the ConversationScreen
val viewModel: ContactsViewModel = hiltViewModel()
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
ContactsScreen(
uiState,
onNavigateToContactDetails
)
}
}
Auf dieselbe Weise können Sie ein Ziel erstellen, das ContactDetailsScreen
anzeigt. In diesem Fall können Sie den UI-Status direkt aus dem NavBackStackEntry
abrufen, anstatt den UI-Status von einem Ansichtsmodell abzurufen.
// ContactsNavigation.kt
@Serializable
internal data class ContactDetails(val id: String)
fun NavGraphBuilder.contactDetailsScreen() {
composable<ContactDetails> { navBackStackEntry ->
ContactDetailsScreen(contact = navBackStackEntry.toRoute())
}
}
Navigationsereignisse zusammenfassen
Auf die gleiche Weise wie Ziele können Sie Navigationsereignisse kapseln, um Routentypen nicht unnötig offenzulegen. Dazu erstellst du Erweiterungsfunktionen in NavController
.
// ContactsNavigation.kt
fun NavController.navigateToContactDetails(id: String) {
navigate(route = ContactDetails(id = id))
}
Zusammenführung
Der Navigationscode für die Anzeige von Kontakten ist jetzt sauber vom Navigationsdiagramm der App getrennt. Die App muss:
- Funktionen der
NavGraphBuilder
-Erweiterung aufrufen, um Ziele zu erstellen - Verbinden Sie diese Ziele durch Aufrufen von
NavController
-Erweiterungsfunktionen für Navigationsereignisse
// MyApp.kt
@Composable
fun MyApp() {
...
NavHost(navController, startDestination = Contacts) {
contactsDestination(onNavigateToContactDetails = { contactId ->
navController.navigateToContactDetails(id = contactId)
})
contactDetailsDestination()
}
}
Zusammenfassung
- Navigationscode für eine Reihe von Bildschirmen kapseln, indem Sie ihn in einer separaten Datei platzieren
- Ziele durch Erstellen von Erweiterungsfunktionen in
NavGraphBuilder
angeben - Navigationsereignisse durch Erstellen von Erweiterungsfunktionen in
NavController
verfügbar machen - Mit
internal
Bildschirme und Routentypen privat halten