Router — an architectural pattern that centralizes navigation logic between application screens. Router determines where and how to navigate when an event occurs. In iOS, Router abstracts UINavigationController and segue; in Android — FragmentManager and NavController from Jetpack Navigation. Router can work as a standalone pattern or as a component of Coordinator (Router-per-Coordinator) and VIPER (Router-per-module). More details — in Android Navigation Component Guide.
Key Takeaways
Router — a pattern that encapsulates the logic of transitioning between screens. The main idea: ViewController (or Presenter/ViewModel) does not call navigationController.pushViewController directly, but notifies the Router of the intent to navigate. The Router decides which ViewController to create and how to display it. Router can be simple (a set of navigation methods) or complex (with URL scheme routing, deep links, and push notifications).
Three approaches to Router — Router as a service (global Navigator singleton), Router as a module component (VIPER approach), Router as a protocol (abstraction for Coordinator). Global Router — simplicity (one instance per app) — popular in small projects. Modular Router — each feature has its own Router — VIPER and Clean Architecture standard. Protocol Router — used in Coordinator: RouterProtocol with push/present/pop/dismiss methods.
| Approach | Structure | When to Use |
|---|---|---|
| Global Navigator | Singleton with navigation methods | Small projects, prototypes |
| Modular Router | Router inside each module | VIPER, Clean Architecture |
| Protocol Router | Protocol + implementation for Coordinator | MVVM-C, Coordinator pattern |
History of the pattern — Router appeared in web frameworks (URL Router — Rails Routes, Express.js) and was adapted for mobile development. In iOS, Router became popular with the rise of VIPER (2015-2017). In Android, Router is part of Jetpack Navigation (2018), Google's standard navigation solution. Today, Router is an integral part of any modular mobile application architecture.
iOS Router — a protocol with methods for various navigation types. The basic implementation wraps UINavigationController. Router can handle modal presentations (present/dismiss), push/pop in the navigation stack, showDetail for Split View Controller, and full-screen presentations (iOS 13+). Router does not create ViewControllers — it receives a ready instance and displays it. ViewController creation is handled by a factory (Assembly, DI container) or Coordinator.
// RouterProtocol for 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 implementation 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 — creating a module with Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router in VIPER — each VIPER module has its own Router, which knows which screens to open from the current module. The Router creates the next module's ViewController through a factory and passes control. Router in VIPER is the only component that contains import UIKit (except View). This simplifies testing of Presenter and Interactor: they do not depend on UIKit.
Jetpack Navigation Component — the standard router for Android, recommended by Google since 2018. Navigation is defined in an XML graph (nav_graph.xml): nodes (fragments, activities, destinations) and edges (actions). NavController manages transitions: calls FragmentManager.replace/add and manages the back stack. Navigation Component supports deep links, arguments, transition animations, and Safe Args for type-safe data passing.
// nav_graph.xml — navigation graph
<!-- @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 — type-safe arguments
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — integration with 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 in Android Navigation — Navigation Component supports explicit deep links (PendingIntent + NavDeepLinkBuilder) and implicit ones (through intent-filter in the manifest). A deep link can lead to any nav_graph node. Push notification handling: Notification creates a PendingIntent with NavDeepLinkBuilder, which restores the back stack and opens the desired screen. Navigation Component automatically handles Up/Back buttons and returns the user to the previous screen.
URL Routing — routing based on URL schemes, deep links, and universal links. The app registers a scheme (myapp://profile/42) or universal link (https://example.com/profile/42). The Router parses the URL, extracts parameters (userId=42), and opens the corresponding screen. URL Routing allows opening the app from outside: via a link from a website, email, push notification, QR code. iOS uses NSUserActivity + universal link, Android — intent-filter + deep link.
// URL Router — deep link parsing and 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 — mapping URLs to 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
}
}
}
}
Deep link routing in practice — the app must handle deep links in three states: not running (launch with deep link), in background (restore from background), active (onNewIntent/SceneDelegate). The URL Router must correctly restore the back stack: when deep linking to a profile screen, the user should be able to press Back and return to the previous screen. iOS and Android handle back stacks differently — the Router must account for platform-specific features.
Router vs Coordinator — Router answers the question "how to show a screen?" (push/present), Coordinator — "which screen to show?" (which flow to start). Router is a navigation tool, Coordinator is a flow organizer. Router is used by Coordinator. In VIPER, Router performs both functions: decides where to go and how to show it. In MVVM-C, Router is a separate protocol used by Coordinator. The patterns do not exclude but complement each other.
| Characteristic | Router | Coordinator | Navigator |
|---|---|---|---|
| Focus | Navigation mechanics | Navigation flow | Global access |
| Level | UI framework (push/present) | Business logic (flow) | Infrastructure |
| Screen creation | Receives ready VC | Creates VC via factory | Creates VC |
| Lifecycle | One per navigation | childCoordinators | Singleton |
Router vs Navigator — Navigator is an older name for Router in the iOS community. In early implementations, Navigator was a Singleton class with navigation methods. Navigator is a global Router singleton. Navigator is simpler but creates hidden dependencies. Protocol-based Router is more testable and modular. Navigator suits projects without strict architecture, Router — for Clean Architecture and VIPER.
Frequently Asked Questions
Router is an abstraction over UINavigationController (push/present/pop). Coordinator is a layer above Router that manages flow (which screen to show next). Router handles the mechanics, Coordinator handles the navigation logic. Coordinator uses Router to perform transitions. In VIPER, Router combines both functions; in MVVM-C, they are separated.
No. For apps with 3-5 screens, direct calls to navigationController.pushViewController are a perfectly fine solution. Router becomes useful with 10+ screens, deep links, push notifications, and modular architecture. Router simplifies navigation testing and allows centralized handling of transitions from any source (button, deep link, push notification).
URL Router is an extension of Router that parses deep links and universal links. URL Router determines the mapping between URLs and app screens. For example, route myapp://profile/42 → ProfileViewController(userId: 42). URL Router centralizes deep link handling from different sources: push notifications, QR codes, email links, universal links.
Yes. Router can work independently — as a global Navigator or as a VIPER module component. Router without Coordinator is simply encapsulating UINavigationController behind a protocol. Coordinator without Router is more complex (ViewController calls push/present directly), but possible. Best practice is Router + Coordinator together.
Functionally — yes, both centralize navigation. Implementation is different: iOS Router — protocol + class around UINavigationController; Android — Jetpack Navigation Component with XML graph and NavController. Android Router is built into the framework (Navigation Component), iOS Router is a third-party implementation. Android Router supports Safe Args, iOS Router does not (needs generics or Codable).
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also