Router (router) — model arhitectural care centralizează logica de navigare între ecranele aplicației. Router determină unde și cum să se navigheze la apariția unui eveniment. În iOS, Router abstractizează UINavigationController și segue, în Android — FragmentManager și NavController din Jetpack Navigation. Router poate funcționa ca model independent sau ca component Coordinator (Router-per-Coordinator) și VIPER (Router-per-module). Mai multe — în Android Navigation Component Guide.
Esențial
Router — model care încapsulează logica de tranziție între ecrane. Ideea principală: ViewController (sau Presenter/ViewModel) nu apelează direct navigationController.pushViewController, ci informează Router-ul despre intenția de navigare. Router decide ce ViewController să creeze și cum să-l afișeze. Router poate fi simplu (un set de metode de navigare) sau complex (cu rutare după scheme URL, deep link-uri și notificări push).
Trei abordări ale Router — Router ca serviciu (Navigator-singleton global), Router ca component de modul (abordarea VIPER), Router ca protocol (abstractizare pentru Coordinator). Router global — simplitate (o instanță per aplicație) — popular în proiecte mici. Router modular — fiecare caracteristică are propriul Router — standardul VIPER și Clean Architecture. Router bazat pe protocol — folosit în Coordinator: RouterProtocol cu metode push/present/pop/dismiss.
| Abordare | Structură | Când să folosești |
|---|---|---|
| Navigator global | Singleton cu metode de tranziție | Proiecte mici, prototipuri |
| Router modular | Router în fiecare modul | VIPER, Clean Architecture |
| Router pe protocol | Protocol + implementare pentru Coordinator | MVVM-C, Coordinator pattern |
Istoria modelului — Router a apărut în framework-uri web (URL Router — Rails Routes, Express.js) și a fost adaptat pentru dezvoltarea mobilă. În iOS, Router a devenit popular odată cu creșterea VIPER (2015-2017). În Android, Router — parte din Jetpack Navigation (2018), soluția standard Google pentru navigare. Acum Router — parte integrantă a oricărei arhitecturi modulare a aplicațiilor mobile.
iOS Router — protocol cu metode pentru diferite tipuri de navigare. Implementarea de bază înfășoară UINavigationController. Router poate gestiona prezentări modale (present/dismiss), push/pop în stiva de navigare, showDetail pentru Split View Controller și prezentări pe ecran complet (iOS 13+). Router nu creează ViewController — primește o instanță gata făcută și o afișează. Crearea ViewController-ului este realizată de fabrică (Assembly, container DI) sau Coordinator.
// RouterProtocol pentru 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)
}
// Implementare Router prin 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 — crearea modulului cu Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router în VIPER — fiecare modul VIPER are propriul Router care știe ce ecrane să deschidă din modulul curent. Router creează ViewController-ul următorului modul prin fabrică și transferă controlul. Router în VIPER — singurul component care conține import UIKit (în afară de View). Aceasta simplifică testarea Presenter și Interactor: nu depind de UIKit.
Jetpack Navigation Component — routerul standard pentru Android, recomandat de Google din 2018. Navigarea este definită într-un graf XML (nav_graph.xml): noduri (fragments, activities, destinations) și muchii (actions). NavController gestionează tranzițiile: apelează FragmentManager.replace/add și gestionează stiva de navigare (back stack). Navigation Component suportă deep link-uri, argumente, animații de tranziție și Safe Args pentru transmiterea tip-securizată a datelor.
// nav_graph.xml — graful de navigare
<!-- @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 — navigare prin 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 — argumente tip-securizate
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — integrare cu 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-uri în Android Navigation — Navigation Component suportă deep link-uri explicite (PendingIntent + NavDeepLinkBuilder) și implicite (prin intent-filter în manifest). Deep link poate duce la orice nod din nav_graph. Procesarea notificărilor push: Notification creează un PendingIntent cu NavDeepLinkBuilder care restaurează stiva de navigare și deschide ecranul dorit. Navigation Component gestionează automat butoanele Up/Back și readuce utilizatorul la ecranul anterior.
URL Routing — rutare bazată pe scheme URL, deep link-uri și universal link-uri. Aplicația înregistrează o schemă (myapp://profile/42) sau un universal link (https://example.com/profile/42). Router parsează URL-ul, extrage parametrii (userId=42) și deschide ecranul corespunzător. URL Routing permite deschiderea aplicației din exterior: printr-un link de pe site, din email, notificare push, cod QR. iOS folosește NSUserActivity + universal link, Android — intent-filter + deep link.
// URL Router — parsare deep link și navigare
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 — mapare URL pe rute
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
}
}
}
}
Deep link routing în practică — aplicația trebuie să proceseze deep link-ul în trei stări: ne Lansată (pornire cu deep link), în fundal (restaurare din fundal), activă (onNewIntent/SceneDelegate). URL Router trebuie să restaureze corect stiva de navigare: la deep link către ecranul de profil, utilizatorul trebuie să poată apăsa Back și să revină la ecranul anterior. iOS și Android gestionează stiva de navigare diferit — Router trebuie să țină cont de particularitățile platformei.
Router vs Coordinator — Router răspunde la întrebarea "cum să afișăm ecranul?" (push/present), Coordinator — "ce ecran să afișăm?" (ce flow să pornim). Router — instrument de navigare, Coordinator — organizator de flow. Router este folosit de Coordinator. În VIPER, Router îndeplinește ambele funcții: decide încotro să navigheze și cum să afișeze. În MVVM-C, Router — protocol separat folosit de Coordinator. Modelele nu se exclud, ci se completează reciproc.
| Caracteristică | Router | Coordinator | Navigator |
|---|---|---|---|
| Focus | Mecanica navigării | Flow-ul navigării | Acces global |
| Nivel | UI-framework (push/present) | Logică de business (flow) | Infrastructură |
| Crearea ecranelor | Primește VC gata făcut | Creează VC prin fabrică | Creează VC |
| Ciclu de viață | Unul per navigare | childCoordinators | Singleton |
Router vs Navigator — Navigator — denumirea învechită a Router în comunitatea iOS. În implementările timpurii, Navigator era o clasă Singleton cu metode de tranziție. Navigator — un Router-singleton global. Navigator este mai simplu, dar creează dependențe ascunse. Router pe protocoale — mai testabil și mai modular. Navigator este potrivit pentru proiecte fără o arhitectură strictă, Router — pentru Clean Architecture și VIPER.
Întrebări frecvente
Router — abstractizare peste UINavigationController (push/present/pop). Coordinator — strat peste Router care gestionează flow-ul (ce ecran să afișeze în continuare). Router răspunde de mecanică, Coordinator — de logica navigării. Coordinator folosește Router pentru a efectua tranzițiile. În VIPER, Router îmbină ambele funcții, în MVVM-C — sunt separate.
Nu. Pentru aplicații cu 3-5 ecrane, apelurile directe navigationController.pushViewController sunt o soluție normală. Router devine util la 10+ ecrane, deep link-uri, notificări push și arhitectură modulară. Router simplifică testarea navigării și permite procesarea centralizată a tranzițiilor din orice sursă (buton, deep link, push).
URL Router — extensie a Router care parsează deep link-uri și universal link-uri. URL Router determină corespondența dintre URL și ecranul aplicației. De exemplu, route myapp://profile/42 → ProfileViewController(userId: 42). URL Router centralizează procesarea deep link-urilor din diferite surse: notificări push, coduri QR, link-uri email, link-uri universale.
Da. Router poate funcționa independent — ca Navigator global sau ca component al modulului VIPER. Router fără Coordinator — doar încapsularea UINavigationController în spatele unui protocol. Coordinator fără Router — mai dificil (ViewController însuși apelează push/present), dar posibil. Cea mai bună practică — Router + Coordinator împreună.
Funcțional — da, ambele centralizează navigarea. Implementarea diferă: iOS Router — protocol + clasă în jurul UINavigationController; Android — Jetpack Navigation Component cu graf XML și NavController. Android Router este încorporat în framework (Navigation Component), iOS Router — implementare externă. Android Router suportă Safe Args, iOS Router — nu (necesită generics sau Codable).
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și