Router (instradatore) — un pattern architetturale che centralizza la logica di navigazione tra gli schermi dell'applicazione. Il Router determina dove e come navigare al verificarsi di un evento. In iOS, il Router astrae UINavigationController e segue; in Android — FragmentManager e NavController di Jetpack Navigation. Il Router può funzionare come pattern indipendente o come componente di Coordinator (Router-per-Coordinator) e VIPER (Router-per-module). Maggiori dettagli — nella Android Navigation Component Guide.
Punti chiave
Router — un pattern che incapsula la logica di transizione tra schermi. L'idea principale: ViewController (o Presenter/ViewModel) non chiama direttamente navigationController.pushViewController, ma notifica al Router l'intenzione di navigare. Il Router decide quale ViewController creare e come visualizzarlo. Il Router può essere semplice (un insieme di metodi di navigazione) o complesso (con routing tramite schemi URL, deep link e notifiche push).
Tre approcci al Router — Router come servizio (Navigator singleton globale), Router come componente di modulo (approccio VIPER), Router come protocollo (astrazione per Coordinator). Router globale — semplicità (un'istanza per applicazione) — popolare nei progetti piccoli. Router modulare — ogni funzionalità ha il proprio Router — standard VIPER e Clean Architecture. Router a protocollo — usato in Coordinator: RouterProtocol con metodi push/present/pop/dismiss.
| Approccio | Struttura | Quando usarlo |
|---|---|---|
| Navigator globale | Singleton con metodi di navigazione | Progetti piccoli, prototipi |
| Router modulare | Router dentro ogni modulo | VIPER, Clean Architecture |
| Router a protocollo | Protocollo + implementazione per Coordinator | MVVM-C, Coordinator pattern |
Storia del pattern — il Router è apparso nei framework web (URL Router — Rails Routes, Express.js) ed è stato adattato per lo sviluppo mobile. In iOS, il Router è diventato popolare con l'ascesa di VIPER (2015-2017). In Android, il Router fa parte di Jetpack Navigation (2018), la soluzione standard di Google per la navigazione. Oggi, il Router è parte integrante di qualsiasi architettura modulare di applicazioni mobili.
iOS Router — un protocollo con metodi per vari tipi di navigazione. L'implementazione di base avvolge UINavigationController. Il Router può gestire presentazioni modali (present/dismiss), push/pop nello stack di navigazione, showDetail per Split View Controller e presentazioni a schermo intero (iOS 13+). Il Router non crea ViewController — riceve un'istanza pronta e la visualizza. La creazione di ViewController è gestita da una factory (Assembly, contenitore DI) o Coordinator.
// RouterProtocol per 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)
}
// Implementazione di Router tramite 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 — creazione di un modulo con Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router in VIPER — ogni modulo VIPER ha il proprio Router, che sa quali schermi aprire dal modulo corrente. Il Router crea il ViewController del modulo successivo tramite una factory e passa il controllo. Il Router in VIPER è l'unico componente che contiene import UIKit (tranne View). Questo semplifica i test di Presenter e Interactor: non dipendono da UIKit.
Jetpack Navigation Component — il router standard per Android, raccomandato da Google dal 2018. La navigazione è definita in un grafo XML (nav_graph.xml): nodi (fragments, activities, destinations) e archi (actions). NavController gestisce le transizioni: chiama FragmentManager.replace/add e gestisce lo stack indietro. Navigation Component supporta deep link, argomenti, animazioni di transizione e Safe Args per il passaggio type-safe dei dati.
// nav_graph.xml — grafo di navigazione
<!-- @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 — navigazione tramite 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 — argomenti type-safe
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — integrazione 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 link in Android Navigation — Navigation Component supporta deep link espliciti (PendingIntent + NavDeepLinkBuilder) e impliciti (tramite intent-filter nel manifesto). Un deep link può portare a qualsiasi nodo del nav_graph. Gestione delle notifiche push: la Notification crea un PendingIntent con NavDeepLinkBuilder, che ripristina lo stack indietro e apre lo schermo desiderato. Navigation Component gestisce automaticamente i pulsanti Up/Back e riporta l'utente allo schermo precedente.
URL Routing — routing basato su schemi URL, deep link e universal link. L'app registra uno schema (myapp://profile/42) o un universal link (https://example.com/profile/42). Il Router analizza l'URL, estrae i parametri (userId=42) e apre lo schermo corrispondente. L'URL Routing permette di aprire l'app dall'esterno: tramite un link da un sito web, email, notifica push, codice QR. iOS usa NSUserActivity + universal link, Android — intent-filter + deep link.
// URL Router — parsing deep link e navigazione
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 — mappatura degli URL con le route
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
}
}
}
}
Routing deep link nella pratica — l'app deve gestire i deep link in tre stati: non avviata (avvio con deep link), in background (ripristino da background), attiva (onNewIntent/SceneDelegate). L'URL Router deve ripristinare correttamente lo stack indietro: quando si fa un deep link a uno schermo profilo, l'utente deve poter premere Indietro e tornare allo schermo precedente. iOS e Android gestiscono gli stack indietro in modo diverso — il Router deve tenere conto delle peculiarità della piattaforma.
Router vs Coordinator — il Router risponde alla domanda "come mostrare uno schermo?" (push/present), il Coordinator — "quale schermo mostrare?" (quale flusso avviare). Il Router è uno strumento di navigazione, il Coordinator è un organizzatore di flussi. Il Coordinator usa il Router. In VIPER, il Router svolge entrambe le funzioni: decide dove andare e come mostrare. In MVVM-C, il Router è un protocollo separato usato dal Coordinator. I pattern non si escludono ma si complementano.
| Caratteristica | Router | Coordinator | Navigator |
|---|---|---|---|
| Focus | Meccanica di navigazione | Flusso di navigazione | Accesso globale |
| Livello | Framework UI (push/present) | Logica di business (flusso) | Infrastruttura |
| Creazione schermi | Riceve VC pronto | Crea VC tramite factory | Crea VC |
| Ciclo di vita | Uno per navigazione | childCoordinators | Singleton |
Router vs Navigator — Navigator è un nome più vecchio per Router nella comunità iOS. Nelle prime implementazioni, Navigator era una classe Singleton con metodi di navigazione. Navigator è un Router singleton globale. Navigator è più semplice ma crea dipendenze nascoste. Il Router basato su protocolli è più testabile e modulare. Navigator è adatto a progetti senza architettura rigorosa, Router — per Clean Architecture e VIPER.
Domande frequenti
Router è un'astrazione su UINavigationController (push/present/pop). Coordinator è un livello sopra il Router che gestisce il flusso (quale schermo mostrare successivamente). Il Router si occupa della meccanica, il Coordinator della logica di navigazione. Il Coordinator usa il Router per eseguire le transizioni. In VIPER, il Router combina entrambe le funzioni; in MVVM-C, sono separate.
No. Per applicazioni con 3-5 schermi, le chiamate dirette a navigationController.pushViewController sono una soluzione perfettamente valida. Il Router diventa utile con 10+ schermi, deep link, notifiche push e architettura modulare. Il Router semplifica i test di navigazione e permette la gestione centralizzata delle transizioni da qualsiasi fonte (pulsante, deep link, notifica push).
URL Router è un'estensione del Router che analizza deep link e universal link. URL Router determina la corrispondenza tra URL e schermi dell'app. Ad esempio, rotta myapp://profile/42 → ProfileViewController(userId: 42). URL Router centralizza la gestione dei deep link da diverse fonti: notifiche push, codici QR, link email, link universali.
Sì. Il Router può funzionare indipendentemente — come Navigator globale o come componente di modulo VIPER. Router senza Coordinator è semplicemente incapsulare UINavigationController dietro un protocollo. Coordinator senza Router è più complesso (ViewController chiama push/present direttamente), ma possibile. La best practice è Router + Coordinator insieme.
Funzionalmente — sì, entrambi centralizzano la navigazione. L'implementazione è diversa: iOS Router — protocollo + classe attorno a UINavigationController; Android — Jetpack Navigation Component con grafo XML e NavController. Android Router è integrato nel framework (Navigation Component), iOS Router è un'implementazione di terze parti. Android Router supporta Safe Args, iOS Router no (necessita di generics o Codable).
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche