Otocz kod nawigacyjny

Gdy tworzysz wykres za pomocą DSL Kotlin, przechowywanie miejsc docelowych i zdarzeń nawigacji w jednym pliku może być trudne do utrzymania. Jest tak szczególnie w przypadku, gdy masz wiele niezależnych funkcji.

Wyodrębnij miejsca docelowe

Miejsca docelowe należy przenieść do funkcji rozszerzenia NavGraphBuilder. Powinny one znajdować się w pobliżu tras, które je definiują, i wyświetlanych ekranów. Przyjrzyjmy się np. temu kodowi na poziomie aplikacji, który tworzy miejsce docelowe z listą kontaktów:

// MyApp.kt

@Serializable
object Contacts

@Composable
fun MyApp() {
  ...
  NavHost(navController, startDestination = Contacts) {
    composable<Contacts> { ContactsScreen( /* ... */ ) }
  }
}

Przenieś kod związany z nawigacją do osobnego pliku:

// ContactsNavigation.kt

@Serializable
object Contacts

fun NavGraphBuilder.contactsDestination() {
    composable<Contacts> { ContactsScreen( /* ... */ ) }
}

// MyApp.kt

@Composable
fun MyApp() {
  ...
  NavHost(navController, startDestination = Contacts) {
     contactsDestination()
  }
}

Trasy i definicje miejsc docelowych są teraz oddzielne od głównej aplikacji i można je aktualizować niezależnie. Główna aplikacja zależy tylko od jednej funkcji rozszerzenia. W tym przypadku jest to NavGraphBuilder.contactsDestination().

Funkcja rozszerzenia NavGraphBuilder tworzy pomost między bezstanową funkcją kompozycyjną na poziomie ekranu a logiką właściwą dla nawigacji. Może ona też określać, skąd pochodzi stan i jak obsługujesz zdarzenia.

Przykład

Poniższy fragment kodu wprowadza nowe miejsce docelowe, w którym wyświetlane są szczegóły kontaktu, oraz aktualizuje miejsce docelowe istniejącej listy kontaktów, aby wyświetlić zdarzenie nawigacji w celu wyświetlenia szczegółów kontaktu.

Oto typowy zestaw ekranów, które internal mogą połączyć ze swoim modułem, dzięki czemu inne moduły nie mają do nich dostępu:

// 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) { ... }

Tworzenie miejsc docelowych

Poniższa funkcja rozszerzenia NavGraphBuilder tworzy miejsce docelowe, w którym wyświetla się funkcja kompozycyjna ConversationScreen. Łączy on teraz ekran z elementem ViewModel, który podaje stan interfejsu ekranu i obsługuje logikę biznesową związaną z ekranem.

Zdarzenia nawigacji, takie jak nawigacja do miejsca docelowego szczegółów kontaktu, są widoczne dla rozmówcy, a nie obsługiwane przez interfejs ViewModel.

// 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
    )
  }
}

W ten sam sposób możesz utworzyć miejsce docelowe, w którym będzie wyświetlany ContactDetailsScreen. W takim przypadku zamiast uzyskiwać stan interfejsu z modelu widoku danych, możesz go pobrać bezpośrednio z metody NavBackStackEntry.

// ContactsNavigation.kt

@Serializable
internal data class ContactDetails(val id: String)

fun NavGraphBuilder.contactDetailsScreen() {
  composable<ContactDetails> { navBackStackEntry ->
    ContactDetailsScreen(contact = navBackStackEntry.toRoute())
  }
}

Herbaty zdarzeń nawigacji

W taki sam sposób, w jaki uwzględniasz miejsca docelowe, możesz też ujmować zdarzenia nawigacji, aby uniknąć niepotrzebnego ujawniania typów tras. Aby to zrobić, utwórz funkcje rozszerzeń w NavController.

// ContactsNavigation.kt

fun NavController.navigateToContactDetails(id: String) {
  navigate(route = ContactDetails(id = id))
}

Połącz wszystko w jednym miejscu

Kod nawigacyjny do wyświetlania kontaktów jest teraz wyraźnie oddzielony od wykresu nawigacyjnego w aplikacji. Aplikacja musi:

  • Wywołaj funkcje rozszerzenia NavGraphBuilder, aby utworzyć miejsca docelowe
  • Połącz te miejsca docelowe, wywołując funkcje rozszerzenia NavController dla zdarzeń nawigacji
// MyApp.kt

@Composable
fun MyApp() {
  ...
  NavHost(navController, startDestination = Contacts) {
     contactsDestination(onNavigateToContactDetails = { contactId ->
        navController.navigateToContactDetails(id = contactId)
     })
     contactDetailsDestination()
  }
}

W skrócie

  • Otocz kod nawigacyjny powiązanego zestawu ekranów w oddzielnym pliku.
  • Udostępniaj miejsca docelowe, tworząc funkcje rozszerzeń w NavGraphBuilder
  • Udostępnij zdarzenia nawigacji, tworząc funkcje rozszerzenia w: NavController
  • Używaj internal, by chronić prywatność ekranów i typów tras