Scaling Navigation trong Jetpack Compose: từ app nhỏ đến production thực tế
Navigation trong Jetpack Compose lúc mới bắt đầu thì khá đơn giản.
Nhưng khi app lớn dần (multi-module, nhiều flow, nhiều entry point), nếu không thiết kế từ đầu, bạn sẽ rất dễ rơi vào tình trạng:
- Navigation rối
- Khó maintain
- Khó test
Bài viết này đi từ basic đến architecture thực tế, giúp bạn scale navigation đúng cách.
Navigation trong Compose thực chất là gì?
Ở mức cơ bản, Navigation trong Compose gồm:
NavController: quản lý điều hướng và back stackNavHost: container chứa các màn hìnhcomposable(route): định nghĩa destination
Đây là nền tảng chính để điều hướng giữa các màn hình.
Giai đoạn 1 – App nhỏ (simple navigation)
Ở app đơn giản, bạn thường làm kiểu này:
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen() }
composable("detail") { DetailScreen() }
}
Mọi thứ hoạt động tốt, dễ hiểu và dễ debug.
Nhưng…
Vấn đề bắt đầu xuất hiện khi app lớn dần
Khi app scale lên, bạn sẽ gặp:
- Route string hardcode khắp nơi
- Khó truyền dữ liệu phức tạp
- Navigation logic bị rải rác
- Khó reuse screen
Đây là lúc cần chuyển sang architecture rõ ràng hơn.
Giai đoạn 2 – Tách Navigation thành layer riêng
Một nguyên tắc quan trọng:
Navigation không nên nằm trực tiếp trong UI.
Thay vì:
navController.navigate("detail/$id")
Bạn nên:
- Tạo class định nghĩa route
- Centralize navigation logic
Ví dụ:
sealed class Screen(val route: String) {
object Home : Screen("home")
object Detail : Screen("detail/{id}")
}
Lợi ích:
- Type-safe hơn
- Tránh typo
- Dễ maintain
Giai đoạn 3 – Event-based navigation
Một sai lầm phổ biến:
Truyền NavController vào ViewModel
Cách tốt hơn:
ViewModel chỉ emit event:
sealed class UiEvent {
data class Navigate(val route: String) : UiEvent()
}
UI layer sẽ handle:
LaunchedEffect(Unit) {
viewModel.events.collect { event ->
when (event) {
is UiEvent.Navigate -> navController.navigate(event.route)
}
}
}
Ưu điểm:
- Tách biệt rõ ràng UI và logic
- Dễ test
- Không leak context
Giai đoạn 4 – Modular navigation (chuẩn production)
Khi app lớn (multi-module):
Mỗi feature nên có:
- Navigation graph riêng
- Route riêng
- Entry point rõ ràng
Ví dụ:
fun NavGraphBuilder.authGraph(navController: NavController) {
composable("login") { LoginScreen() }
composable("register") { RegisterScreen() }
}
Sau đó combine:
NavHost(navController, startDestination = "auth") {
authGraph(navController)
mainGraph(navController)
}
Lợi ích:
- Scale tốt
- Team làm song song được
- Không conflict
Deep Link và navigation nâng cao
Compose Navigation hỗ trợ:
- Deep link từ URL
- Nested navigation graph
- Adaptive navigation UI
Ví dụ:
composable(
route = "profile/{id}",
deepLinks = listOf(
navDeepLink { uriPattern = "https://example.com/profile/{id}" }
)
) { ... }
Cho phép mở app từ link bên ngoài.
Nguyên tắc quan trọng khi scale navigation
- Không hardcode route
- Không để ViewModel giữ NavController
- Tách navigation theo feature
- Luôn nghĩ đến testability
Một insight quan trọng
Navigation không chỉ là UI.
Nó chính là flow của business logic.
Ví dụ:
- User login xong đi đâu
- User chưa onboard thì xử lý thế nào
Đây là logic cấp ứng dụng, không phải UI thuần.
Tổng kết
Scaling navigation trong Compose thường đi qua các bước:
- Basic NavHost
- Tách route và structure
- Event-based navigation
- Modular architecture
Nếu làm đúng từ đầu:
- Code sạch hơn
- Dễ maintain
- Scale tốt hơn
Kết luận
Navigation trong Compose không khó.
Nhưng nếu không thiết kế sớm, nó sẽ nhanh chóng trở thành phần khó kiểm soát nhất trong app.
All rights reserved