Đang tải...

Scaling Navigation trong Jetpack Compose: từ app nhỏ đến production thực tế

08/05/2026
4 phút đọc
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?...

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 stack
  • NavHost: container chứa các màn hình
  • composable(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

  1. Không hardcode route
  2. Không để ViewModel giữ NavController
  3. Tách navigation theo feature
  4. 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:

  1. Basic NavHost
  2. Tách route và structure
  3. Event-based navigation
  4. 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.

📚 Nguồn: Viblo

Chia sẻ bài viết

Cần tư vấn?

Liên hệ với chúng tôi để được hỗ trợ

Liên hệ ngay

Bài viết liên quan

Đừng để Queue Worker "đột tử": Giải phẫu DB::disableQueryLog() chống Memory Leak trong Laravel
09/05/2026

Đừng để Queue Worker "đột tử": Giải phẫu DB::disableQueryLog() chống Memory Leak trong Laravel

Chào anh em cộng đồng Viblo! Hôm nay chúng ta sẽ cùng mổ xẻ một vấn đề mà tôi cá là 90% anh em làm backend Laravel sớm muộn gì cũng sẽ gặp phải khi hệ thống ...

Đọc thêm
How I Built A Real-time Streaming Market Data with .NET and ReactJS
09/05/2026

How I Built A Real-time Streaming Market Data with .NET and ReactJS

![Architecture of RealMarketAPI](https://images.viblo.asia/513fabb5-9801-4991-946a-c9dcbb5de844.png) **Last month** I shared how I turned a side project into a SaaS. Today, I’m opening the hood and ...

Đọc thêm
Nghiệp vụ Checkout COD: Không chỉ là một nút bấm - Tư duy xử lý "vạn đơn" cho Backend
09/05/2026

Nghiệp vụ Checkout COD: Không chỉ là một nút bấm - Tư duy xử lý "vạn đơn" cho Backend

### 1. Mở đầu: "Cạm bẫy" của sự đơn giản Trong thanh toán online, chúng ta dựa vào kết quả trả về từ Gateway (Paypal, VNPay...). Với COD, "hợp đồng" thanh toán ...

Đọc thêm

Bắt đầu dự án của bạn

Hãy để Flash Dev đồng hành cùng bạn

Liên hệ ngay