Router (маршрутизатор) — архитектурный паттерн, централизующий логику навигации между экранами приложения. Router определяет, куда и как перейти при наступлении события. В iOS Router абстрагирует UINavigationController и segue, в Android — FragmentManager и NavController от Jetpack Navigation. Router может работать как самостоятельный паттерн или как компонент Coordinator (Router-per-Coordinator) и VIPER (Router-per-module). Подробнее — в Android Navigation Component Guide.
Главное
Router — паттерн, инкапсулирующий логику перехода между экранами. Основная идея: ViewController (или Presenter/ViewModel) не вызывает navigationController.pushViewController напрямую, а сообщает Router-у о намерении перейти. Router решает, какой ViewController создать и как его отобразить. Router может быть простым (набор методов для навигации) или сложным (с маршрутизацией по URL-схемам, deep link'ам и push-уведомлениям).
Три подхода к Router — Router как сервис (глобальный Navigator-синглтон), Router как компонент модуля (VIPER-подход), Router как протокол (абстракция для Coordinator). Глобальный Router — простота (один экземпляр на приложение) — популярен в небольших проектах. Модульный Router — каждая фича имеет свой Router — стандарт VIPER и Clean Architecture. Протокольный Router — используется в Coordinator: RouterProtocol с методами push/present/pop/dismiss.
| Подход | Структура | Когда использовать |
|---|---|---|
| Глобальный Navigator | Singleton с методами переходов | Малые проекты, прототипы |
| Модульный Router | Router внутри каждого модуля | VIPER, Clean Architecture |
| Протокольный Router | Protocol + реализация для Coordinator | MVVM-C, Coordinator pattern |
История паттерна — Router появился в веб-фреймворках (URL Router — Rails Routes, Express.js) и был адаптирован для мобильной разработки. В iOS Router стал популярен с ростом VIPER (2015-2017). В Android Router — часть Jetpack Navigation (2018), стандартного решения Google для навигации. Сейчас Router — неотъемлемая часть любой модульной архитектуры мобильных приложений.
iOS Router — протокол с методами для различных типов навигации. Базовая реализация оборачивает UINavigationController. Router может управлять модальными презентациями (present/dismiss), push/pop в navigation stack, showDetail для Split View Controller и полноэкранные presentation (iOS 13+). Router не создаёт ViewController — он получает готовый экземпляр и отображает его. Созданием ViewController занимается фабрика (Assembly, DI-контейнер) или Coordinator.
// RouterProtocol для 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)
}
// Реализация Router через 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 — создание модуля с Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router in VIPER — каждый VIPER-модуль имеет собственный Router, который знает, какие экраны открывать из текущего модуля. Router создаёт ViewController следующего модуля через фабрику и передаёт управление. Router в VIPER — единственный компонент, который содержит import UIKit (кроме View). Это упрощает тестирование Presenter и Interactor: они не зависят от UIKit.
Jetpack Navigation Component — стандартный роутер для Android, рекомендованный Google с 2018 года. Навигация определяется в XML-графе (nav_graph.xml): узлы (fragments, activities, destinations) и рёбра (actions). NavController управляет переходами: вызывает FragmentManager.replace/add и управляет back stack'ом. Navigation Component поддерживает deep links, аргументы, анимации переходов и Safe Args для типобезопасной передачи данных.
// nav_graph.xml — граф навигации
<!-- @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 — навигация через 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 — типобезопасные аргументы
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — интеграция с 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 в Android Navigation — Navigation Component поддерживает явные deep links (PendingIntent + NavDeepLinkBuilder) и неявные (через intent-filter в манифесте). Deep link может вести на любой узел nav_graph. Обработка push-уведомлений: Notification создаёт PendingIntent с NavDeepLinkBuilder, который восстанавливает back stack и открывает нужный экран. Navigation Component автоматически обрабатывает Up/Back кнопки и возвращает пользователя на предыдущий экран.
URL Routing — маршрутизация на основе URL-схем, deep link'ов и universal link'ов. Приложение регистрирует схему (myapp://profile/42) или universal link (https://example.com/profile/42). Router парсит URL, извлекает параметры (userId=42) и открывает соответствующий экран. URL Routing позволяет открывать приложение из вне: по ссылке с сайта, из email, push-уведомления, QR-кода. iOS использует NSUserActivity + universal link, Android — intent-filter + deep link.
// URL Router — парсинг deep link и навигация
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 — сопоставление URL с роутами
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 на практике — приложение должно обрабатывать deep link в трёх состояниях: не запущено (запуск с deep link), в фоне (восстановление из background), активно (onNewIntent/SceneDelegate). URL Router должен корректно восстанавливать back stack: при deep link на экран профиля, пользователь должен иметь возможность нажать Back и вернуться на предыдущий экран. iOS и Android по-разному обрабатывают back stack — Router должен учитывать платформенные особенности.
Router vs Coordinator — Router отвечает на вопрос «как показать экран?» (push/present), Coordinator — «какой экран показать?» (какой flow запустить). Router — инструмент навигации, Coordinator — организатор flow. Router используется Coordinator'ом. В VIPER Router выполняет обе функции: решает, куда перейти и как показать. В MVVM-C Router — отдельный протокол, используемый Coordinator'ом. Паттерны не исключают, а дополняют друг друга.
| Характеристика | Router | Coordinator | Navigator |
|---|---|---|---|
| Фокус | Механика навигации | Flow навигации | Глобальный доступ |
| Уровень | UI-фреймворк (push/present) | Бизнес-логика (flow) | Инфраструктура |
| Создание экранов | Получает готовый VC | Создаёт VC через фабрику | Создаёт VC |
| Жизненный цикл | Один на навигацию | childCoordinators | Singleton |
Router vs Navigator — Navigator — устаревшее название Router в iOS-сообществе. В ранних реализациях Navigator был Singleton-классом с методами переходов. Navigator — это глобальный Router-синглтон. Navigator проще, но создаёт скрытые зависимости. Router на протоколах — тестируемее и модульнее. Navigator подходит для проектов без строгой архитектуры, Router — для Clean Architecture и VIPER.
Часто задаваемые вопросы
Router — абстракция над UINavigationController (push/present/pop). Coordinator — слой над Router, управляющий flow (какой экран показать следующим). Router отвечает за механику, Coordinator — за логику навигации. Coordinator использует Router для выполнения переходов. В VIPER Router совмещает обе функции, в MVVM-C — разделены.
Нет. Для приложений с 3-5 экранами прямые вызовы navigationController.pushViewController — нормальное решение. Router становится полезным при 10+ экранах, deep link'ах, push-уведомлениях и модульной архитектуре. Router упрощает тестирование навигации и позволяет централизованно обрабатывать переходы из любого источника (кнопка, deep link, пуша).
URL Router — расширение Router, парсящее deep link'и и universal link'и. URL Router определяет соответствие между URL и экраном приложения. Например, route myapp://profile/42 → ProfileViewController(userId: 42). URL Router централизует обработку deep link'ов из разных источников: push-уведомления, QR-коды, email-ссылки, универсальные ссылки.
Да. Router может работать самостоятельно — как глобальный Navigator или как компонент VIPER модуля. Router без Coordinator — это просто инкапсуляция UINavigationController за протоколом. Coordinator без Router — сложнее (ViewController сам вызывает push/present), но возможно. Лучшая практика — Router + Coordinator вместе.
Функционально — да, оба централизуют навигацию. Реализация разная: iOS Router — протокол + класс вокруг UINavigationController; Android — Jetpack Navigation Component с XML-графом и NavController. Android Router встроен в фреймворк (Navigation Component), iOS Router — сторонняя имплементация. Android Router поддерживает Safe Args, iOS Router — нет (нужны generics или Codable).
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также