Deferred Navigation — qué es, principios e implementación

Autor: IT Sectr Publicado: 2026-06-10 Tiempo de lectura: 9 min

Deferred Navigation es un patrón de navegación diferida donde la transición a la siguiente pantalla ocurre después de que se completa una operación asíncrona, en lugar de directamente en el momento de la acción del usuario. Según Android Developers (2024), la navegación diferida ayuda a evitar condiciones de carrera entre la navegación y la carga de datos, y simplifica el manejo de transiciones desde notificaciones push y Deeplinks. La diferencia clave es que la ruta se calcula después de que todos los datos necesarios estén disponibles.

Puntos Clave

  • Deferred Navigation es navegación diferida donde la transición ocurre después de operaciones asíncronas
  • La navegación directa maneja la transición al instante, la diferida espera los datos
  • Escenarios típicos: autenticación, Deeplink, notificaciones push, carga de configuración
  • En Android se implementa mediante Navigation Component con callbacks y StateFlow
  • En iOS se usa Combine o async/await con un coordinador de navegación

Qué es Deferred Navigation

Deferred Navigation es un patrón arquitectónico donde la decisión de navegación se pospone hasta que todos los datos necesarios estén disponibles. A diferencia de una transición directa donde el usuario presiona un botón y llega inmediatamente a una nueva pantalla, la navegación diferida separa el evento desencadenante y la transición real colocando una operación asíncrona entre ellos.

Arquitectónicamente, Deferred Navigation se basa en el cambio de estado: la pulsación de un botón inicia un proceso asíncrono, y una suscripción a su resultado desencadena la navegación. Esto es especialmente importante en aplicaciones con arquitectura MVVM o MVI, donde la ViewModel gestiona el estado y la Vista (Activity, Fragment, SwiftUI View) se suscribe a los cambios y reacciona con una transición. Este enfoque elimina la dependencia directa entre la UI y la lógica de navegación.

Según Google I/O 2023, la navegación diferida se recomienda para todos los escenarios donde la navegación depende del resultado de una solicitud de red, verificación de autenticación, carga de configuración o permisos. El patrón también es obligatorio al manejar Deeplinks, donde la aplicación debe primero iniciarse, cargar la pantalla raíz y solo entonces navegar a la ruta de destino.

Cuándo usar la navegación diferida

Deferred Navigation se utiliza en escenarios donde la navegación directa conduce a un estado de pantalla incorrecto o errores de carga. Examinemos cuatro casos principales donde la navegación diferida es necesaria.

Autorización y autenticación

Si un usuario toca contenido protegido, la aplicación debe primero verificar el token de acceso. La navegación directa a la pantalla de contenido resultará en una pantalla vacía o un error 401 si el token ha expirado. La navegación diferida verifica el token, y solo en caso de éxito — navega a la pantalla de destino. En caso de fallo — redirige a la pantalla de inicio de sesión.

Manejo de Deeplinks

Cuando una aplicación se abre mediante un enlace externo, primero debe cargar la pantalla raíz, restaurar el estado de navegación y solo entonces realizar la transición Deeplink. La navegación directa a la pantalla de destino sin contexto raíz provocará anomalías: una pila de navegación vacía o una pila atrás rota.

Notificaciones push con contenido

Al tocar una notificación push, la aplicación puede estar en uno de tres estados: cerrada, en segundo plano o activa. Deferred Navigation determina el estado de la aplicación, carga el contenido necesario y solo entonces muestra la pantalla de destino. iOS permite manejar este escenario a través de UNNotificationContentExtension.

Feature flag dinámico

Si la funcionalidad de una pantalla está controlada por un feature flag del servidor, la navegación diferida permite primero solicitar la configuración y solo entonces mostrar la pantalla. Si la función está deshabilitada — el usuario ve contenido alternativo o un marcador de posición en lugar de una pantalla vacía.

EscenarioNavegación directaDeferred Navigation
AutorizaciónPantalla vacía con token expiradoRedirección al inicio de sesión
DeeplinkPila atrás rotaPila de navegación correcta
PushCarga sin contextoDatos listos antes de la transición
Feature flagMostrar funcionalidad no disponibleMarcador de posición o alternativa

Deferred Navigation vs navegación directa

La navegación directa es un enfoque tradicional donde la transición se realiza inmediatamente en respuesta a un evento. El usuario presiona un botón y el enrutador de UI cambia la pantalla de inmediato. Este enfoque es simple y predecible, pero limitado en escenarios que requieren datos del servidor o verificación de condiciones.

Deferred Navigation añade una capa en forma de estado asíncrono. Un evento de usuario inicia una operación, y una suscripción al resultado controla la navegación. Esto aumenta la complejidad del código pero proporciona flexibilidad: el mismo desencadenante puede llevar a diferentes pantallas dependiendo de los datos cargados.

La elección entre los dos enfoques depende de los requisitos: si mostrar una pantalla no requiere datos asíncronos — use navegación directa. Si la pantalla depende del resultado de una solicitud, autorización o condiciones externas — la navegación diferida es necesaria. Un enfoque híbrido, donde algunas transiciones son directas y otras diferidas, es la práctica más común en aplicaciones industriales.

Implementación en Android: Navigation Component y ViewModel

Android Jetpack proporciona mecanismos para implementar Deferred Navigation a nivel de arquitectura. La idea principal es que la ViewModel gestiona el estado, mientras que la Activity o Fragment se suscribe a los cambios y desencadena la navegación a través de NavController.

Deferred Navigation con StateFlow

StateFlow en corrutinas de Kotlin es la herramienta ideal para la navegación diferida. La ViewModel actualiza un StateFlow con un evento de navegación, y la Activity lo observa y realiza la transición. Una vez procesado el evento, el StateFlow se limpia, evitando la navegación repetida.

kotlin
class MainViewModel : ViewModel() {
    private val _navigation = MutableSharedFlow<NavigationEvent>()
    val navigation: SharedFlow<NavigationEvent> = _navigation

    fun onDeepLinkReceived(link: String) {
        viewModelScope.launch {
            val data = resolveDeepLink(link)
            _navigation.emit(NavigationEvent.GoToScreen(data))
        }
    }
}

Deferred Navigation con NavController

En la Activity, una suscripción a la navegación activa NavController con la ruta desde la ViewModel. Para evitar la navegación repetida al rotar la pantalla, se utiliza un wrapper NavigationEventWrapper que procesa el evento solo una vez. Jetpack Navigation 2.7+ soporta Safe Args para el paso de argumentos con seguridad de tipos.

kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val vm: MainViewModel by viewModels()
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            vm.navigation.collect { event ->
                when (event) {
                    is NavigationEvent.GoToScreen ->
                        findNavController(R.id.nav_host)
                            .navigate(event.route)
                }
            }
        }
    }
}

Implementación en iOS: Coordinator Pattern y Combine

iOS no tiene un Navigation Component integrado similar a Android Jetpack, por lo que los desarrolladores implementan Deferred Navigation a través del Coordinator Pattern en combinación con Combine o async/await. El Coordinator gestiona la pila de pantallas y toma decisiones de navegación basadas en los datos cargados.

Coordinator con Combine

Coordinator es un objeto que gestiona la navegación entre ViewControllers. En combinación con Combine, la ViewModel publica eventos a través de PassthroughSubject, y el Coordinator se suscribe a ellos y realiza la transición. Este enfoque separa completamente la UI de la lógica de navegación y sigue las recomendaciones de Apple para la arquitectura de aplicaciones.

swift
final class AppCoordinator {
    private var cancellables = Set<AnyCancellable>()

    func start(viewModel: MainViewModel) {
        viewModel.$navigationDestination
            .compactMap { $0 }
            .sink { [weak self] destination in
                self?.navigateTo(destination)
            }
            .store(in: &cancellables)
    }
}

Deferred Navigation con async/await

Swift 5.5 introdujo la concurrencia estructurada, que permite implementar la navegación diferida mediante async/await sin Combine. La ViewModel proporciona una función async que devuelve una ruta después de cargar los datos. El Coordinator llama a esta función en un Task y realiza la transición basándose en la ruta recibida.

swift
class AuthViewModel: ObservableObject {
    func resolveDeeplink(_ url: URL) async -> AppRoute? {
        guard let token = await AuthService.shared.getValidToken() else { return .login }
        return await DeeplinkRouter.resolve(url, token: token)
    }
}

// In Coordinator:
Task {
    if let route = await viewModel.resolveDeeplink(url) {
        navigateTo(route)
    }
}

Errores comunes y mejores prácticas

Deferred Navigation simplifica el manejo de escenarios asíncronos pero requiere un enfoque disciplinado en la gestión del estado. Examinemos los principales errores que cometen los desarrolladores al implementar la navegación diferida.

Error: navegación antes de completar la inicialización

El error más común es intentar realizar navegación diferida antes de que la pantalla raíz esté completamente inicializada y el NavController o Coordinator esté listo para la transición. En Android esto lleva a IllegalStateException, en iOS — a un estado de UI indefinido. La solución es asegurarse de que el ciclo de vida del componente esté en estado STARTED o RESUMED antes de desencadenar la navegación.

Error: navegación repetida al regresar a una pantalla

Si el StateFlow o Subject no limpia el evento después del procesamiento, al regresar a la pantalla anterior el usuario puede ser redirigido automáticamente a la misma pantalla nuevamente. Use SharedFlow con replay=0 en Android o CurrentValueSubject con nil después del procesamiento en iOS, para que el evento de navegación se active solo una vez.

Mejor práctica: un solo NavController o Coordinator

Use un componente central para toda la navegación en la aplicación. Cuando cada Activity, Fragment o ViewController tiene su propio controlador de navegación, la navegación diferida entre diferentes partes de la aplicación se vuelve caótica. Un único Coordinator simplifica la depuración y las pruebas de escenarios de navegación.

Mejor práctica: pruebas de escenarios de navegación diferida

Deferred Navigation es más difícil de probar que la navegación directa porque las operaciones asíncronas introducen un factor de tiempo. Use TestDispatcher en Android (kotlinx-coroutines-test) y XCTestExpectation en iOS para simular la carga de datos y verificar que la navegación sigue la ruta esperada. Simule servicios de autorización y deeplinks para pruebas aisladas de cada escenario.

Preguntas Frecuentes

¿En qué se diferencia Deferred Navigation de Deep Link?

Deferred Navigation es un patrón de transición diferida que se puede aplicar en cualquier escenario asíncrono. Deep Link es un desencadenante para la navegación diferida, pero no el único. La autorización y los feature flags también usan navegación diferida.

¿Se pueden combinar la navegación diferida y la directa?

, la mayoría de las aplicaciones usan un enfoque híbrido. Una pantalla de lista de productos (sin dependencias asíncronas) puede usar navegación directa, mientras que una pantalla de detalles con carga de datos usa diferida. La separación se determina por la arquitectura de cada pantalla específica.

¿Cómo evitar condiciones de carrera con navegación diferida?

Use SharedFlow sin replay en Android y combineLatest sin almacenamiento en búfer en iOS. Cancele suscripciones anteriores ante un nuevo desencadenante. Esto garantiza que solo se procese el último evento de navegación.

¿Jetpack Compose admite navegación diferida?

, en Compose la navegación diferida se implementa mediante suscripción al StateFlow de la ViewModel y llamada a NavController.navigate en LaunchedEffect. Google recomienda usar Navigation Compose con un modelo basado en eventos para escenarios diferidos.

¿Cómo manejar la navegación diferida cuando la aplicación está minimizada?

Retrase la navegación hasta que la aplicación vuelva a estar activa. En Android use Lifecycle.State.STARTED para filtrar eventos. En iOS verifique UIApplication.State en bloques Combine o async/await.

Resumen

  • Deferred Navigation es un patrón donde la transición ocurre después de completar una operación asíncrona
  • Escenarios principales: autenticación, Deeplink, notificaciones push, feature flags
  • StateFlow/SharedFlow en Android y Combine/async-await en iOS son herramientas de implementación
  • Deferred Navigation a diferencia de la directa no causa condiciones de carrera durante la carga asíncrona
  • Navigation Component en Android y Coordinator Pattern en iOS son las arquitecturas base
  • Un único NavController/Coordinator es obligatorio para una navegación consistente
  • Pruebas de navegación diferida requieren TestDispatcher y mocks para servicios asíncronos

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también