Incapsula il tuo codice di navigazione

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