Router (bộ định tuyến) — một mẫu kiến trúc tập trung hóa logic điều hướng giữa các màn hình ứng dụng. Router xác định điều hướng đến đâu và như thế nào khi một sự kiện xảy ra. Trong iOS, Router trừu tượng hóa UINavigationController và segue; trong Android — FragmentManager và NavController từ Jetpack Navigation. Router có thể hoạt động như một mẫu độc lập hoặc như một thành phần của Coordinator (Router-per-Coordinator) và VIPER (Router-per-module). Chi tiết thêm — trong Android Navigation Component Guide.
Những điểm chính
Router — một mẫu đóng gói logic chuyển đổi giữa các màn hình. Ý tưởng chính: ViewController (hoặc Presenter/ViewModel) không gọi trực tiếp navigationController.pushViewController, mà thông báo cho Router về ý định điều hướng. Router quyết định ViewController nào sẽ tạo và cách hiển thị nó. Router có thể đơn giản (một tập hợp các phương thức điều hướng) hoặc phức tạp (với định tuyến lược đồ URL, deep links và thông báo push).
Ba cách tiếp cận Router — Router như dịch vụ (Navigator singleton toàn cục), Router như thành phần mô-đun (cách tiếp cận VIPER), Router như giao thức (trừu tượng hóa cho Coordinator). Router toàn cục — đơn giản (một phiên bản cho mỗi ứng dụng) — phổ biến trong các dự án nhỏ. Router mô-đun — mỗi tính năng có Router riêng — tiêu chuẩn VIPER và Clean Architecture. Router giao thức — được sử dụng trong Coordinator: RouterProtocol với các phương thức push/present/pop/dismiss.
| Cách tiếp cận | Cấu trúc | Khi nào sử dụng |
|---|---|---|
| Navigator toàn cục | Singleton với các phương thức điều hướng | Dự án nhỏ, nguyên mẫu |
| Router mô-đun | Router bên trong mỗi mô-đun | VIPER, Clean Architecture |
| Router giao thức | Giao thức + triển khai cho Coordinator | MVVM-C, Coordinator pattern |
Lịch sử của mẫu — Router xuất hiện trong các framework web (URL Router — Rails Routes, Express.js) và được điều chỉnh cho phát triển di động. Trong iOS, Router trở nên phổ biến với sự phát triển của VIPER (2015-2017). Trong Android, Router là một phần của Jetpack Navigation (2018), giải pháp điều hướng tiêu chuẩn của Google. Ngày nay, Router là một phần không thể thiếu của bất kỳ kiến trúc ứng dụng di động mô-đun nào.
iOS Router — một giao thức với các phương thức cho nhiều loại điều hướng khác nhau. Triển khai cơ bản bao bọc UINavigationController. Router có thể xử lý các trình bày phương thức (present/dismiss), push/pop trong ngăn xếp điều hướng, showDetail cho Split View Controller và trình bày toàn màn hình (iOS 13+). Router không tạo ViewControllers — nó nhận một phiên bản sẵn sàng và hiển thị nó. Việc tạo ViewController được xử lý bởi một nhà máy (Assembly, vùng chứa DI) hoặc Coordinator.
// RouterProtocol cho 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)
}
// Triển khai Router qua 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 — tạo mô-đun với Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router trong VIPER — mỗi mô-đun VIPER có Router riêng, biết màn hình nào cần mở từ mô-đun hiện tại. Router tạo ViewController của mô-đun tiếp theo thông qua một nhà máy và chuyển quyền điều khiển. Router trong VIPER là thành phần duy nhất chứa import UIKit (trừ View). Điều này đơn giản hóa việc kiểm thử Presenter và Interactor: chúng không phụ thuộc vào UIKit.
Jetpack Navigation Component — bộ định tuyến tiêu chuẩn cho Android, được Google khuyến nghị từ năm 2018. Điều hướng được định nghĩa trong một đồ thị XML (nav_graph.xml): các nút (fragments, activities, destinations) và các cạnh (actions). NavController quản lý các chuyển đổi: gọi FragmentManager.replace/add và quản lý ngăn xếp quay lại. Navigation Component hỗ trợ deep links, đối số, hoạt ảnh chuyển tiếp và Safe Args để truyền dữ liệu type-safe.
// nav_graph.xml — đồ thị điều hướng
<!-- @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 — điều hướng qua 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 — đối số type-safe
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — tích hợp với 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 trong Android Navigation — Navigation Component hỗ trợ deep links rõ ràng (PendingIntent + NavDeepLinkBuilder) và ngầm định (thông qua intent-filter trong tệp kê khai). Một deep link có thể dẫn đến bất kỳ nút nào của nav_graph. Xử lý thông báo push: Notification tạo PendingIntent với NavDeepLinkBuilder, khôi phục ngăn xếp quay lại và mở màn hình mong muốn. Navigation Component tự động xử lý các nút Up/Back và đưa người dùng trở lại màn hình trước đó.
URL Routing — định tuyến dựa trên lược đồ URL, deep links và universal links. Ứng dụng đăng ký một lược đồ (myapp://profile/42) hoặc universal link (https://example.com/profile/42). Router phân tích cú pháp URL, trích xuất tham số (userId=42) và mở màn hình tương ứng. URL Routing cho phép mở ứng dụng từ bên ngoài: qua liên kết từ trang web, email, thông báo push, mã QR. iOS sử dụng NSUserActivity + universal link, Android — intent-filter + deep link.
// URL Router — phân tích cú pháp deep link và điều hướng
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 — ánh xạ URL với các tuyến đường
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
}
}
}
}
Định tuyến deep link trong thực tế — ứng dụng phải xử lý deep links ở ba trạng thái: chưa chạy (khởi chạy với deep link), trong nền (khôi phục từ nền), đang hoạt động (onNewIntent/SceneDelegate). URL Router phải khôi phục chính xác ngăn xếp quay lại: khi deep link đến màn hình hồ sơ, người dùng phải có thể nhấn Quay lại và trở về màn hình trước đó. iOS và Android xử lý ngăn xếp quay lại khác nhau — Router phải tính đến các đặc điểm riêng của từng nền tảng.
Router vs Coordinator — Router trả lời câu hỏi "làm thế nào để hiển thị màn hình?" (push/present), Coordinator — "hiển thị màn hình nào?" (luồng nào để bắt đầu). Router là công cụ điều hướng, Coordinator là người tổ chức luồng. Coordinator sử dụng Router. Trong VIPER, Router thực hiện cả hai chức năng: quyết định đi đâu và hiển thị như thế nào. Trong MVVM-C, Router là một giao thức riêng biệt được Coordinator sử dụng. Các mẫu không loại trừ nhau mà bổ sung cho nhau.
| Đặc điểm | Router | Coordinator | Navigator |
|---|---|---|---|
| Trọng tâm | Cơ chế điều hướng | Luồng điều hướng | Truy cập toàn cục |
| Cấp độ | Framework UI (push/present) | Logic kinh doanh (luồng) | Cơ sở hạ tầng |
| Tạo màn hình | Nhận VC sẵn sàng | Tạo VC qua nhà máy | Tạo VC |
| Vòng đời | Một cho mỗi điều hướng | childCoordinators | Singleton |
Router vs Navigator — Navigator là tên cũ hơn của Router trong cộng đồng iOS. Trong các triển khai ban đầu, Navigator là một lớp Singleton với các phương thức điều hướng. Navigator là một Router singleton toàn cục. Navigator đơn giản hơn nhưng tạo ra các phụ thuộc ẩn. Router dựa trên giao thức có thể kiểm thử và mô-đun hơn. Navigator phù hợp cho các dự án không có kiến trúc chặt chẽ, Router — cho Clean Architecture và VIPER.
Các câu hỏi thường gặp
Router là một sự trừu tượng hóa trên UINavigationController (push/present/pop). Coordinator là một lớp trên Router quản lý luồng (màn hình nào sẽ hiển thị tiếp theo). Router xử lý cơ chế, Coordinator xử lý logic điều hướng. Coordinator sử dụng Router để thực hiện các chuyển đổi. Trong VIPER, Router kết hợp cả hai chức năng; trong MVVM-C, chúng được tách biệt.
Không. Đối với ứng dụng có 3-5 màn hình, gọi trực tiếp navigationController.pushViewController là một giải pháp hoàn toàn chấp nhận được. Router trở nên hữu ích với 10+ màn hình, deep links, thông báo push và kiến trúc mô-đun. Router đơn giản hóa việc kiểm thử điều hướng và cho phép xử lý tập trung các chuyển đổi từ bất kỳ nguồn nào (nút bấm, deep link, thông báo push).
URL Router là một phần mở rộng của Router phân tích cú pháp deep links và universal links. URL Router xác định ánh xạ giữa URL và màn hình ứng dụng. Ví dụ: tuyến đường myapp://profile/42 → ProfileViewController(userId: 42). URL Router tập trung hóa việc xử lý deep links từ các nguồn khác nhau: thông báo push, mã QR, liên kết email, liên kết phổ quát.
Có. Router có thể hoạt động độc lập — như một Navigator toàn cục hoặc như một thành phần mô-đun VIPER. Router không có Coordinator chỉ đơn giản là đóng gói UINavigationController đằng sau một giao thức. Coordinator không có Router phức tạp hơn (ViewController gọi push/present trực tiếp), nhưng có thể. Thực hành tốt nhất là sử dụng Router + Coordinator cùng nhau.
Về mặt chức năng — có, cả hai đều tập trung hóa điều hướng. Triển khai khác nhau: iOS Router — giao thức + lớp bao quanh UINavigationController; Android — Jetpack Navigation Component với đồ thị XML và NavController. Android Router được tích hợp vào framework (Navigation Component), iOS Router là triển khai của bên thứ ba. Android Router hỗ trợ Safe Args, iOS Router thì không (cần generics hoặc Codable).
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm