Router (router) — wzorzec architektoniczny centralizujący logikę nawigacji między ekranami aplikacji. Router określa, dokąd i jak przejść po wystąpieniu zdarzenia. W iOS Router abstrahuje UINavigationController i segue, w Android — FragmentManager i NavController z Jetpack Navigation. Router może działać jako samodzielny wzorzec lub jako komponent Coordinator (Router-per-Coordinator) i VIPER (Router-per-module). Więcej — w Android Navigation Component Guide.
Najważniejsze
Router — wzorzec hermetyzujący logikę przejścia między ekranami. Główna idea: ViewController (lub Presenter/ViewModel) nie wywołuje bezpośrednio navigationController.pushViewController, lecz informuje Router o zamiarze przejścia. Router decyduje, który ViewController utworzyć i jak go wyświetlić. Router może być prosty (zbiór metod nawigacji) lub złożony (z routingiem według schematów URL, deep linków i powiadomień push).
Trzy podejścia do Router — Router jako usługa (globalny Navigator-singleton), Router jako komponent modułu (podejście VIPER), Router jako protokół (abstrakcja dla Coordinator). Globalny Router — prostota (jedna instancja na aplikację) — popularny w małych projektach. Modułowy Router — każda funkcja ma własny Router — standard VIPER i Clean Architecture. Protokołowy Router — używany w Coordinator: RouterProtocol z metodami push/present/pop/dismiss.
| Podejście | Struktura | Kiedy używać |
|---|---|---|
| Globalny Navigator | Singleton z metodami przejść | Małe projekty, prototypy |
| Modułowy Router | Router wewnątrz każdego modułu | VIPER, Clean Architecture |
| Protokołowy Router | Protocol + implementacja dla Coordinator | MVVM-C, Coordinator pattern |
Historia wzorca — Router pojawił się w frameworkach webowych (URL Router — Rails Routes, Express.js) i został zaadaptowany do programowania mobilnego. W iOS Router stał się popularny wraz z rozwojem VIPER (2015-2017). W Android Router — część Jetpack Navigation (2018), standardowego rozwiązania Google do nawigacji. Obecnie Router — nieodłączna część każdej modułowej architektury aplikacji mobilnych.
iOS Router — protokół z metodami dla różnych typów nawigacji. Podstawowa implementacja opakowuje UINavigationController. Router może zarządzać prezentacjami modalnymi (present/dismiss), push/pop w stosie nawigacyjnym, showDetail dla Split View Controller oraz pełnoekranowymi prezentacjami (iOS 13+). Router nie tworzy ViewController — otrzymuje gotową instancję i wyświetla ją. Tworzeniem ViewController zajmuje się fabryka (Assembly, DI-kontener) lub Coordinator.
// RouterProtocol dla 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)
}
// Implementacja Router przez 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 — tworzenie modułu z Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router in VIPER — każdy moduł VIPER ma własny Router, który wie, które ekrany otwierać z bieżącego modułu. Router tworzy ViewController następnego modułu przez fabrykę i przekazuje sterowanie. Router w VIPER — jedyny komponent zawierający import UIKit (oprócz View). Ułatwia to testowanie Presenter i Interactor: nie zależą one od UIKit.
Jetpack Navigation Component — standardowy router dla Android, zalecany przez Google od 2018 roku. Nawigacja jest definiowana w grafie XML (nav_graph.xml): węzły (fragments, activities, destinations) i krawędzie (actions). NavController zarządza przejściami: wywołuje FragmentManager.replace/add i zarządza back stackiem. Navigation Component obsługuje deep links, argumenty, animacje przejść i Safe Args do typowo bezpiecznego przesyłania danych.
// nav_graph.xml — graf nawigacji
<!-- @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 — nawigacja przez 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 — typowo bezpieczne argumenty
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — integracja z 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 w Android Navigation — Navigation Component obsługuje jawne deep links (PendingIntent + NavDeepLinkBuilder) i niejawne (przez intent-filter w manifeście). Deep link może prowadzić do dowolnego węzła nav_graph. Obsługa powiadomień push: Notification tworzy PendingIntent z NavDeepLinkBuilder, który odtwarza back stack i otwiera odpowiedni ekran. Navigation Component automatycznie obsługuje przyciski Up/Back i zwraca użytkownika do poprzedniego ekranu.
URL Routing — routing oparty na schematach URL, deep linkach i universal linkach. Aplikacja rejestruje schemat (myapp://profile/42) lub universal link (https://example.com/profile/42). Router parsuje URL, wyodrębnia parametry (userId=42) i otwiera odpowiedni ekran. URL Routing umożliwia otwieranie aplikacji z zewnątrz: przez link ze strony, z emaila, powiadomienia push, kodu QR. iOS używa NSUserActivity + universal link, Android — intent-filter + deep link.
// URL Router — parsowanie deep link i nawigacja
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 — mapowanie URL na trasy
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 w praktyce — aplikacja musi obsługiwać deep link w trzech stanach: nieuruchomiona (uruchomienie z deep link), w tle (przywrócenie z tła), aktywna (onNewIntent/SceneDelegate). URL Router powinien poprawnie odtwarzać back stack: przy deep link do ekranu profilu użytkownik powinien móc nacisnąć Back i wrócić do poprzedniego ekranu. iOS i Android inaczej obsługują back stack — Router musi uwzględniać różnice platformowe.
Router vs Coordinator — Router odpowiada na pytanie „jak wyświetlić ekran?” (push/present), Coordinator — „jaki ekran wyświetlić?” (jaki flow uruchomić). Router — narzędzie nawigacji, Coordinator — organizator flow. Router jest używany przez Coordinator. W VIPER Router pełni obie funkcje: decyduje, dokąd przejść i jak wyświetlić. W MVVM-C Router — osobny protokół używany przez Coordinator. Wzorce nie wykluczają się, lecz uzupełniają.
| Charakterystyka | Router | Coordinator | Navigator |
|---|---|---|---|
| Cel | Mechanika nawigacji | Flow nawigacji | Globalny dostęp |
| Poziom | UI-framework (push/present) | Logika biznesowa (flow) | Infrastruktura |
| Tworzenie ekranów | Otrzymuje gotowy VC | Tworzy VC przez fabrykę | Tworzy VC |
| Cykl życia | Jeden na nawigację | childCoordinators | Singleton |
Router vs Navigator — Navigator — przestarzała nazwa Router w środowisku iOS. We wczesnych implementacjach Navigator był klasą Singleton z metodami przejść. Navigator — to globalny Router-singleton. Navigator jest prostszy, ale tworzy ukryte zależności. Router na protokołach — bardziej testowalny i modułowy. Navigator nadaje się do projektów bez ścisłej architektury, Router — do Clean Architecture i VIPER.
Często zadawane pytania
Router — abstrakcja nad UINavigationController (push/present/pop). Coordinator — warstwa nad Router, zarządzająca flow (jaki ekran wyświetlić jako następny). Router odpowiada za mechanikę, Coordinator — za logikę nawigacji. Coordinator używa Router do wykonywania przejść. W VIPER Router łączy obie funkcje, w MVVM-C są rozdzielone.
Nie. Dla aplikacji z 3-5 ekranami bezpośrednie wywołania navigationController.pushViewController — normalne rozwiązanie. Router staje się przydatny przy 10+ ekranach, deep linkach, powiadomieniach push i architekturze modułowej. Router upraszcza testowanie nawigacji i umożliwia centralne przetwarzanie przejść z dowolnego źródła (przycisk, deep link, push).
URL Router — rozszerzenie Router, parsujące deep linki i universal linki. URL Router określa dopasowanie między URL a ekranem aplikacji. Na przykład route myapp://profile/42 → ProfileViewController(userId: 42). URL Router centralizuje obsługę deep linków z różnych źródeł: powiadomienia push, kody QR, linki email, linki uniwersalne.
Tak. Router może działać samodzielnie — jako globalny Navigator lub jako komponent modułu VIPER. Router bez Coordinator — to po prostu hermetyzacja UINavigationController za protokołem. Coordinator bez Router — trudniejszy (ViewController sam wywołuje push/present), ale możliwy. Najlepsza praktyka — Router + Coordinator razem.
Funkcjonalnie — tak, oba centralizują nawigację. Implementacja różna: iOS Router — protokół + klasa wokół UINavigationController; Android — Jetpack Navigation Component z grafem XML i NavController. Android Router jest wbudowany w framework (Navigation Component), iOS Router — implementacja zewnętrzna. Android Router obsługuje Safe Args, iOS Router — nie (potrzebne generics lub Codable).
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również