Quando utilizzi Kotlin DSL per creare il grafico, mantenere le destinazioni e gli eventi di navigazione in un unico file può essere difficile da gestire. Questo è particolarmente vero se hai più funzionalità indipendenti.
Estrai destinazioni
Dovresti spostare le destinazioni nelle funzioni di estensione
NavGraphBuilder
. Dovrebbero trovarsi vicino ai percorsi che li definiscono e agli
schermi che mostrano. Ad esempio, considera il seguente codice a livello di app
che crea una destinazione che mostra un elenco di contatti:
// MyApp.kt
@Serializable
object Contacts
@Composable
fun MyApp() {
...
NavHost(navController, startDestination = Contacts) {
composable<Contacts> { ContactsScreen( /* ... */ ) }
}
}
Devi spostare il codice specifico per la navigazione in un file separato:
// ContactsNavigation.kt
@Serializable
object Contacts
fun NavGraphBuilder.contactsDestination() {
composable<Contacts> { ContactsScreen( /* ... */ ) }
}
// MyApp.kt
@Composable
fun MyApp() {
...
NavHost(navController, startDestination = Contacts) {
contactsDestination()
}
}
Le route e le definizioni della destinazione sono ora separate dall'app principale e
puoi aggiornarle in modo indipendente. L'app principale dipende solo da una singola
funzione di estensione. In questo caso, è
NavGraphBuilder.contactsDestination()
.
La funzione di estensione NavGraphBuilder
costituisce il ponte tra una funzione componibile a livello di schermo stateless e la logica specifica per la navigazione. Questo livello può anche definire l'origine dello stato e il modo in cui gestisci gli eventi.
Esempio
Lo snippet seguente introduce una nuova destinazione per visualizzare i dettagli di un contatto e aggiorna la destinazione dell'elenco contatti esistente per esporre un evento di navigazione e visualizzare i dettagli del contatto.
Di seguito è riportato un tipico insieme di schermate che può essere internal
nel proprio modulo, in modo che altri moduli non possano accedervi:
// 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) { ... }
Crea destinazioni
La seguente funzione dell'estensione NavGraphBuilder
crea una destinazione
che mostra l'elemento componibile ConversationScreen
. Inoltre, ora connette lo schermo a un ViewModel
che fornisce lo stato dell'interfaccia utente della schermata e gestisce la logica di business relativa allo schermo.
Gli eventi di navigazione, ad esempio l'accesso alla destinazione dei dettagli del contatto, vengono esposti al chiamante anziché essere gestiti da 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
)
}
}
Puoi utilizzare lo stesso approccio per creare una destinazione che mostri ContactDetailsScreen
. In questo caso, anziché ottenere lo stato dell'UI da un modello di visualizzazione, puoi ottenerlo direttamente da NavBackStackEntry
.
// ContactsNavigation.kt
@Serializable
internal data class ContactDetails(val id: String)
fun NavGraphBuilder.contactDetailsScreen() {
composable<ContactDetails> { navBackStackEntry ->
ContactDetailsScreen(contact = navBackStackEntry.toRoute())
}
}
Incapsulare gli eventi di navigazione
Nello stesso modo in cui incapsula le destinazioni, puoi incapsulare gli eventi di navigazione per evitare di esporre inutilmente i tipi di percorso. Per farlo, crea funzioni di estensione in NavController
.
// ContactsNavigation.kt
fun NavController.navigateToContactDetails(id: String) {
navigate(route = ContactDetails(id = id))
}
Unisci le forze
Il codice di navigazione per la visualizzazione dei contatti è ora chiaramente separato dal grafico di navigazione dell'app. L'app deve:
- Chiama le funzioni dell'estensione
NavGraphBuilder
per creare destinazioni - Collega queste destinazioni chiamando le funzioni dell'estensione di
NavController
per gli eventi di navigazione
// MyApp.kt
@Composable
fun MyApp() {
...
NavHost(navController, startDestination = Contacts) {
contactsDestination(onNavigateToContactDetails = { contactId ->
navController.navigateToContactDetails(id = contactId)
})
contactDetailsDestination()
}
}
In sintesi
- Incapsula il codice di navigazione per un insieme di schermate correlate inserendolo in un file separato
- Esponi le destinazioni creando funzioni di estensione su
NavGraphBuilder
- Esponi gli eventi di navigazione creando funzioni di estensione su
NavController
- Usa
internal
per mantenere privati le schermate e i tipi di percorso