Navigationscode einbauen

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