Router (routeur) — un pattern architectural qui centralise la logique de navigation entre les écrans de l'application. Le Router détermine où et comment naviguer lors d'un événement. Sous iOS, le Router abstrait UINavigationController et les segues ; sous Android — FragmentManager et NavController de Jetpack Navigation. Le Router peut fonctionner comme un pattern indépendant ou comme un composant de Coordinator (Router-per-Coordinator) et VIPER (Router-per-module). Plus de détails — dans le Android Navigation Component Guide.
Points clés
Router — un pattern qui encapsule la logique de transition entre les écrans. L'idée principale : le ViewController (ou Presenter/ViewModel) n'appelle pas directement navigationController.pushViewController, mais notifie le Router de son intention de naviguer. Le Router décide quel ViewController créer et comment l'afficher. Le Router peut être simple (un ensemble de méthodes de navigation) ou complexe (avec routage par schémas d'URL, deep links et notifications push).
Trois approches du Router — Router comme service (Navigator singleton global), Router comme composant de module (approche VIPER), Router comme protocole (abstraction pour Coordinator). Router global — simplicité (une instance par application) — populaire dans les petits projets. Router modulaire — chaque fonctionnalité a son propre Router — standard VIPER et Clean Architecture. Router par protocole — utilisé dans Coordinator : RouterProtocol avec les méthodes push/present/pop/dismiss.
| Approche | Structure | Quand l'utiliser |
|---|---|---|
| Navigator global | Singleton avec méthodes de navigation | Petits projets, prototypes |
| Router modulaire | Router dans chaque module | VIPER, Clean Architecture |
| Router par protocole | Protocole + implémentation pour Coordinator | MVVM-C, Coordinator pattern |
Histoire du pattern — le Router est apparu dans les frameworks web (URL Router — Rails Routes, Express.js) et a été adapté pour le développement mobile. Sous iOS, le Router est devenu populaire avec l'essor de VIPER (2015-2017). Sous Android, le Router fait partie de Jetpack Navigation (2018), la solution standard de Google pour la navigation. Aujourd'hui, le Router fait partie intégrante de toute architecture modulaire d'applications mobiles.
iOS Router — un protocole avec des méthodes pour différents types de navigation. L'implémentation de base englobe UINavigationController. Le Router peut gérer les présentations modales (present/dismiss), les push/pop dans la pile de navigation, showDetail pour Split View Controller et les présentations plein écran (iOS 13+). Le Router ne crée pas de ViewControllers — il reçoit une instance prête et l'affiche. La création des ViewControllers est gérée par une fabrique (Assembly, conteneur DI) ou un Coordinator.
// RouterProtocol pour 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)
}
// Implémentation de Router via 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 — création d'un module avec Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router dans VIPER — chaque module VIPER a son propre Router, qui sait quels écrans ouvrir à partir du module actuel. Le Router crée le ViewController du module suivant via une fabrique et passe le contrôle. Le Router dans VIPER est le seul composant qui contient import UIKit (sauf View). Cela simplifie les tests du Presenter et de l'Interactor : ils ne dépendent pas d'UIKit.
Jetpack Navigation Component — le routeur standard pour Android, recommandé par Google depuis 2018. La navigation est définie dans un graphe XML (nav_graph.xml) : nœuds (fragments, activities, destinations) et arêtes (actions). NavController gère les transitions : appelle FragmentManager.replace/add et gère la pile arrière. Navigation Component prend en charge les deep links, les arguments, les animations de transition et Safe Args pour le passage de données type-safe.
// nav_graph.xml — graphe de navigation
<!-- @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 — navigation via 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 — arguments type-safe
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — intégration avec 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 dans Android Navigation — Navigation Component prend en charge les deep links explicites (PendingIntent + NavDeepLinkBuilder) et implicites (via intent-filter dans le manifeste). Un deep link peut mener à n'importe quel nœud du nav_graph. Gestion des notifications push : la Notification crée un PendingIntent avec NavDeepLinkBuilder, qui restaure la pile arrière et ouvre l'écran souhaité. Navigation Component gère automatiquement les boutons Up/Back et ramène l'utilisateur à l'écran précédent.
URL Routing — routage basé sur les schémas d'URL, les deep links et les universal links. L'application enregistre un schéma (myapp://profile/42) ou un universal link (https://example.com/profile/42). Le Router analyse l'URL, extrait les paramètres (userId=42) et ouvre l'écran correspondant. L'URL Routing permet d'ouvrir l'application de l'extérieur : via un lien depuis un site web, un e-mail, une notification push, un code QR. iOS utilise NSUserActivity + universal link, Android — intent-filter + deep link.
// URL Router — analyse de deep link et navigation
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 — mappage des URLs avec les routes
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
}
}
}
}
Routage deep link en pratique — l'application doit gérer les deep links dans trois états : non lancée (lancement avec deep link), en arrière-plan (restauration depuis l'arrière-plan), active (onNewIntent/SceneDelegate). Le URL Router doit restaurer correctement la pile arrière : lors d'un deep link vers un écran de profil, l'utilisateur doit pouvoir appuyer sur Retour et revenir à l'écran précédent. iOS et Android gèrent les piles arrière différemment — le Router doit tenir compte des spécificités de la plateforme.
Router vs Coordinator — le Router répond à la question « comment afficher un écran ? » (push/present), le Coordinator — « quel écran afficher ? » (quel flux lancer). Le Router est un outil de navigation, le Coordinator est un organisateur de flux. Le Coordinator utilise le Router. Dans VIPER, le Router remplit les deux fonctions : décider où aller et comment l'afficher. Dans MVVM-C, le Router est un protocole séparé utilisé par le Coordinator. Les patterns ne s'excluent pas mais se complètent.
| Caractéristique | Router | Coordinator | Navigator |
|---|---|---|---|
| Focus | Mécanique de navigation | Flux de navigation | Accès global |
| Niveau | Framework UI (push/present) | Logique métier (flux) | Infrastructure |
| Création d'écrans | Reçoit VC prêt | Crée VC via fabrique | Crée VC |
| Cycle de vie | Un par navigation | childCoordinators | Singleton |
Router vs Navigator — Navigator est un nom plus ancien pour Router dans la communauté iOS. Dans les premières implémentations, Navigator était une classe Singleton avec des méthodes de navigation. Navigator est un Router singleton global. Navigator est plus simple mais crée des dépendances cachées. Le Router basé sur les protocoles est plus testable et modulaire. Navigator convient aux projets sans architecture stricte, Router — pour Clean Architecture et VIPER.
Questions fréquentes
Router est une abstraction sur UINavigationController (push/present/pop). Coordinator est une couche au-dessus du Router qui gère le flux (quel écran afficher ensuite). Le Router s'occupe de la mécanique, le Coordinator de la logique de navigation. Le Coordinator utilise le Router pour effectuer les transitions. Dans VIPER, le Router combine les deux fonctions ; dans MVVM-C, elles sont séparées.
Non. Pour les applications avec 3 à 5 écrans, les appels directs à navigationController.pushViewController sont une solution parfaitement acceptable. Le Router devient utile avec 10+ écrans, des deep links, des notifications push et une architecture modulaire. Le Router simplifie les tests de navigation et permet un traitement centralisé des transitions depuis n'importe quelle source (bouton, deep link, notification push).
URL Router est une extension du Router qui analyse les deep links et les universal links. URL Router détermine la correspondance entre les URLs et les écrans de l'application. Par exemple, route myapp://profile/42 → ProfileViewController(userId: 42). URL Router centralise le traitement des deep links provenant de différentes sources : notifications push, codes QR, liens e-mail, liens universels.
Oui. Le Router peut fonctionner indépendamment — comme un Navigator global ou comme un composant de module VIPER. Router sans Coordinator consiste simplement à encapsuler UINavigationController derrière un protocole. Coordinator sans Router est plus complexe (le ViewController appelle push/present directement), mais possible. La meilleure pratique est Router + Coordinator ensemble.
Fonctionnellement — oui, tous deux centralisent la navigation. L'implémentation est différente : iOS Router — protocole + classe autour de UINavigationController ; Android — Jetpack Navigation Component avec graphe XML et NavController. Android Router est intégré au framework (Navigation Component), iOS Router est une implémentation tierce. Android Router prend en charge Safe Args, iOS Router non (nécessite des generics ou Codable).
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi