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( 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