Router (enrutador) — un patrón arquitectónico que centraliza la lógica de navegación entre pantallas de la aplicación. Router determina hacia dónde y cómo navegar cuando ocurre un evento. En iOS, Router abstrae UINavigationController y segue; en Android — FragmentManager y NavController de Jetpack Navigation. Router puede funcionar como patrón independiente o como componente de Coordinator (Router-per-Coordinator) y VIPER (Router-per-module). Más detalles — en Android Navigation Component Guide.
Puntos clave
Router — un patrón que encapsula la lógica de transición entre pantallas. La idea principal: ViewController (o Presenter/ViewModel) no llama directamente a navigationController.pushViewController, sino que notifica al Router su intención de navegar. El Router decide qué ViewController crear y cómo mostrarlo. Router puede ser simple (un conjunto de métodos de navegación) o complejo (con enrutamiento por esquemas URL, deep links y notificaciones push).
Tres enfoques de Router — Router como servicio (Navigator singleton global), Router como componente de módulo (enfoque VIPER), Router como protocolo (abstracción para Coordinator). Router global — simplicidad (una instancia por aplicación) — popular en proyectos pequeños. Router modular — cada feature tiene su propio Router — estándar VIPER y Clean Architecture. Router de protocolo — usado en Coordinator: RouterProtocol con métodos push/present/pop/dismiss.
| Enfoque | Estructura | Cuándo usarlo |
|---|---|---|
| Navigator global | Singleton con métodos de navegación | Proyectos pequeños, prototipos |
| Router modular | Router dentro de cada módulo | VIPER, Clean Architecture |
| Router por protocolo | Protocolo + implementación para Coordinator | MVVM-C, Coordinator pattern |
Historia del patrón — Router apareció en frameworks web (URL Router — Rails Routes, Express.js) y fue adaptado para el desarrollo móvil. En iOS, Router se popularizó con el auge de VIPER (2015-2017). En Android, Router es parte de Jetpack Navigation (2018), la solución estándar de Google para navegación. Hoy, Router es una parte integral de cualquier arquitectura modular de aplicaciones móviles.
iOS Router — un protocolo con métodos para varios tipos de navegación. La implementación básica envuelve UINavigationController. Router puede manejar presentaciones modales (present/dismiss), push/pop en el stack de navegación, showDetail para Split View Controller y presentaciones a pantalla completa (iOS 13+). Router no crea ViewControllers — recibe una instancia lista y la muestra. La creación de ViewControllers la maneja una fábrica (Assembly, contenedor DI) o Coordinator.
// RouterProtocol para iOS
protocol RouterProtocol {
func push(_ viewController: UIViewController, animated: Bool)
func pop(animated: Bool)
func popToRoot(animated: Bool)
func present(_ viewController: UIViewController, animated: Bool)
func dismiss(animated: Bool)
func setViewControllers(_: [UIViewController], animated: Bool)
}
// Implementación de Router mediante UINavigationController
class NavigationRouter: RouterProtocol {
private let navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func push(_ vc: UIViewController, animated: Bool) {
if navigationController.presentedViewController {
navigationController.dismiss(animated: false)
}
navigationController.pushViewController(vc, animated: animated)
}
func pop(animated: Bool) {
navigationController.popViewController(animated: animated)
}
func popToRoot(animated: Bool) {
navigationController.popToRootViewController(animated: animated)
}
func present(_ vc: UIViewController, animated: Bool) {
navigationController.present(vc, animated: animated)
}
func dismiss(animated: Bool) {
navigationController.dismiss(animated: animated)
}
func setViewControllers(_ vcs: [UIViewController], animated: Bool) {
navigationController.setViewControllers(vcs, animated: animated)
}
}
// Assembly — creación de un módulo con Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router en VIPER — cada módulo VIPER tiene su propio Router, que sabe qué pantallas abrir desde el módulo actual. El Router crea el ViewController del siguiente módulo a través de una fábrica y pasa el control. Router en VIPER es el único componente que contiene import UIKit (excepto View). Esto simplifica las pruebas de Presenter e Interactor: no dependen de UIKit.
Jetpack Navigation Component — el router estándar para Android, recomendado por Google desde 2018. La navegación se define en un grafo XML (nav_graph.xml): nodos (fragments, activities, destinations) y aristas (actions). NavController gestiona las transiciones: llama a FragmentManager.replace/add y gestiona la pila de retroceso. Navigation Component admite deep links, argumentos, animaciones de transición y Safe Args para paso de datos type-safe.
// nav_graph.xml — grafo de navegación
<!-- @xml/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/loginFragment">
<fragment android:id="@+id/loginFragment"
android:name=".ui.login.LoginFragment">
<action android:id="@+id/to_home"
app:destination="@id/homeFragment" />
</fragment>
<fragment android:id="@+id/homeFragment"
android:name=".ui.home.HomeFragment">
<argument android:name="userId"
android:defaultValue="0"
app:argType="integer" />
<deepLink app:uri="myapp://home/{userId}" />
</fragment>
</navigation>
// Kotlin — navegación mediante NavController
class LoginFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.loginButton.setOnClickListener {
val action = LoginFragmentDirections.toHome(userId = 42)
findNavController().navigate(action)
}
}
}
// Safe Args — argumentos type-safe
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — integración con Toolbar, BottomNav, Drawer
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navHost = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHost.navController
NavigationUI.setupActionBarWithNavController(this, navController)
}
}
Deep links en Android Navigation — Navigation Component admite deep links explícitos (PendingIntent + NavDeepLinkBuilder) e implícitos (mediante intent-filter en el manifiesto). Un deep link puede conducir a cualquier nodo del nav_graph. Manejo de notificaciones push: Notification crea un PendingIntent con NavDeepLinkBuilder, que restaura la pila de retroceso y abre la pantalla deseada. Navigation Component maneja automáticamente los botones Up/Back y devuelve al usuario a la pantalla anterior.
URL Routing — enrutamiento basado en esquemas URL, deep links y universal links. La aplicación registra un esquema (myapp://profile/42) o universal link (https://example.com/profile/42). El Router analiza la URL, extrae parámetros (userId=42) y abre la pantalla correspondiente. URL Routing permite abrir la aplicación desde fuera: mediante un enlace desde un sitio web, correo electrónico, notificación push, código QR. iOS usa NSUserActivity + universal link, Android — intent-filter + deep link.
// URL Router — análisis de deep link y navegación
protocol URLRoute {
var pattern: String { get }
func navigate(parameters: [String: String], router: RouterProtocol)
}
struct ProfileRoute: URLRoute {
let pattern = "myapp://profile/{userId}"
func navigate(parameters: [String: String], router: RouterProtocol) {
guard let userId = parameters["userId"] else { return }
let profileVC = ProfileViewController(userId: userId)
router.push(profileVC, animated: true)
}
}
// URL Router — mapeo de URLs con rutas
class URLRouter {
private var routes: [URLRoute] = []
func handle(_ url: URL, router: RouterProtocol) {
for route in routes {
if let params = matchPattern(route.pattern, url: url) {
route.navigate(parameters: params, router: router)
return
}
}
}
}
Enrutamiento deep link en la práctica — la aplicación debe manejar deep links en tres estados: no iniciada (inicio con deep link), en segundo plano (restauración desde background), activa (onNewIntent/SceneDelegate). El URL Router debe restaurar correctamente la pila de retroceso: al hacer deep link a una pantalla de perfil, el usuario debe poder presionar Atrás y volver a la pantalla anterior. iOS y Android manejan las pilas de retroceso de manera diferente — el Router debe tener en cuenta las particularidades de cada plataforma.
Router vs Coordinator — Router responde a la pregunta "¿cómo mostrar una pantalla?" (push/present), Coordinator — "¿qué pantalla mostrar?" (qué flujo iniciar). Router es una herramienta de navegación, Coordinator es un organizador de flujos. Coordinator utiliza Router. En VIPER, Router realiza ambas funciones: decide hacia dónde ir y cómo mostrarlo. En MVVM-C, Router es un protocolo separado usado por Coordinator. Los patrones no se excluyen, sino que se complementan.
| Característica | Router | Coordinator | Navigator |
|---|---|---|---|
| Enfoque | Mecánica de navegación | Flujo de navegación | Acceso global |
| Nivel | Framework UI (push/present) | Lógica de negocio (flujo) | Infraestructura |
| Creación de pantallas | Recibe VC listo | Crea VC mediante fábrica | Crea VC |
| Ciclo de vida | Uno por navegación | childCoordinators | Singleton |
Router vs Navigator — Navigator es un nombre antiguo para Router en la comunidad iOS. En implementaciones tempranas, Navigator era una clase Singleton con métodos de navegación. Navigator es un Router singleton global. Navigator es más simple pero crea dependencias ocultas. El Router basado en protocolos es más testeable y modular. Navigator es adecuado para proyectos sin arquitectura estricta, Router — para Clean Architecture y VIPER.
Preguntas frecuentes
Router es una abstracción sobre UINavigationController (push/present/pop). Coordinator es una capa sobre Router que gestiona el flujo (qué pantalla mostrar a continuación). Router se encarga de la mecánica, Coordinator de la lógica de navegación. Coordinator utiliza Router para realizar las transiciones. En VIPER, Router combina ambas funciones; en MVVM-C, están separadas.
No. Para aplicaciones con 3-5 pantallas, las llamadas directas a navigationController.pushViewController son una solución perfectamente válida. Router se vuelve útil con 10+ pantallas, deep links, notificaciones push y arquitectura modular. Router simplifica las pruebas de navegación y permite manejar de forma centralizada las transiciones desde cualquier fuente (botón, deep link, notificación push).
URL Router es una extensión de Router que analiza deep links y universal links. URL Router determina la correspondencia entre URLs y pantallas de la aplicación. Por ejemplo, ruta myapp://profile/42 → ProfileViewController(userId: 42). URL Router centraliza el manejo de deep links de diferentes fuentes: notificaciones push, códigos QR, enlaces de correo electrónico, enlaces universales.
Sí. Router puede funcionar de forma independiente — como Navigator global o como componente de un módulo VIPER. Router sin Coordinator es simplemente encapsular UINavigationController detrás de un protocolo. Coordinator sin Router es más complejo (ViewController llama a push/present directamente), pero es posible. La mejor práctica es Router + Coordinator juntos.
Funcionalmente — sí, ambos centralizan la navegación. La implementación es diferente: iOS Router — protocolo + clase alrededor de UINavigationController; Android — Jetpack Navigation Component con grafo XML y NavController. Android Router está integrado en el framework (Navigation Component), iOS Router es una implementación de terceros. Android Router admite Safe Args, iOS Router no (necesita generics o Codable).
Resumen
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.
Lea también