Cómo encapsular tu código de navegación

Cuando uses el DSL de Kotlin para construir tu gráfico, mantener los destinos y los eventos de navegación en un solo archivo puede ser difícil de mantener. Este es especialmente si tienes varios atributos independientes.

Extrae destinos

Debes mover tus destinos a la extensión NavGraphBuilder. funciones. Deben vivir cerca de las rutas que los definen, y pantallas que muestran. Por ejemplo, considera el siguiente código a nivel de la aplicación que crea un destino que muestra una lista de contactos:

// MyApp.kt

@Serializable
object Contacts

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

Debes mover el código específico de navegación a un archivo separado:

// ContactsNavigation.kt

@Serializable
object Contacts

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

// MyApp.kt

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

Las definiciones de rutas y destinos ahora están separadas de la app principal y puedes actualizarlos de forma independiente. La app principal solo depende de un función de extensión. En este caso, es NavGraphBuilder.contactsDestination()

La función de extensión NavGraphBuilder forma el puente entre una red sin estado función de componibilidad a nivel de la pantalla y lógica específica de Navigation. Esta capa puede y definir de dónde proviene el estado y cómo manejar los eventos.

Ejemplo

El siguiente fragmento de código presenta un nuevo destino para mostrar la dirección de correo electrónico de un contacto y actualiza el destino de la lista de contactos existente para exponer una evento de navegación para mostrar los detalles del contacto.

Este es un conjunto típico de pantallas que pueden ser internal en su propio módulo. que otros módulos no puedan acceder a ellos:

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

Cómo crear destinos

La siguiente función de extensión NavGraphBuilder crea un destino. que muestra el elemento ContactsScreen componible. Además, ahora conecta la pantalla con un ViewModel que proporciona el estado de la IU de la pantalla y controla la la lógica empresarial relacionada con la pantalla.

Los eventos de navegación, como la navegación al destino de los detalles de contacto, se que están expuestos al llamador, en lugar de que ViewModel los controle.

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

Puedes usar el mismo enfoque para crear un destino que muestre las ContactDetailsScreen En este caso, en lugar de obtener el estado de la IU desde un de vista, puedes obtenerlo directamente de NavBackStackEntry.

// ContactsNavigation.kt

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

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

Cómo encapsular eventos de navegación

Así como encapsulas los destinos, puedes encapsular de navegación para evitar exponer los tipos de rutas innecesariamente. Hazlo antes del Crear funciones de extensión en NavController

// ContactsNavigation.kt

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

Reúnelo en un solo lugar

El código de navegación para mostrar contactos ahora está claramente separado del gráfico de navegación de tu app. La app debe hacer lo siguiente:

  • Llama a funciones de extensión de NavGraphBuilder para crear destinos
  • Para conectar esos destinos, llama a las funciones de extensión de NavController para eventos de navegación
// MyApp.kt

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

Resumen

  • Ubica tu código de navegación para un conjunto relacionado de pantallas. en un archivo separado
  • Crea funciones de extensión en NavGraphBuilder para exponer destinos
  • Crea funciones de extensión en NavController para exponer los eventos de navegación.
  • Usa internal para mantener la privacidad de las pantallas y los tipos de rutas