0

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 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.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí