Deferred Navigation là một mẫu điều hướng trì hoãn trong đó việc chuyển đến màn hình tiếp theo xảy ra sau khi hoàn thành một thao tác bất đồng bộ, thay vì trực tiếp tại thời điểm hành động của người dùng. Theo Android Developers (2024), điều hướng trì hoãn giúp tránh tình trạng race condition giữa điều hướng và tải dữ liệu, đồng thời đơn giản hóa việc xử lý chuyển tiếp từ thông báo push và Deeplink. Sự khác biệt chính — lộ trình được tính toán sau khi tất cả dữ liệu cần thiết có sẵn.
Những điểm chính
Deferred Navigation là một mẫu kiến trúc trong đó quyết định điều hướng được hoãn lại cho đến khi tất cả dữ liệu cần thiết có sẵn. Không giống như chuyển tiếp trực tiếp nơi người dùng nhấn nút và ngay lập tức đến một màn hình mới, điều hướng trì hoãn tách biệt sự kiện kích hoạt và chuyển tiếp thực tế bằng cách đặt một thao tác bất đồng bộ giữa chúng.
Về mặt kiến trúc, Deferred Navigation được xây dựng dựa trên thay đổi trạng thái: nhấn nút khởi tạo một quy trình bất đồng bộ, và việc đăng ký kết quả của nó kích hoạt điều hướng. Điều này đặc biệt quan trọng trong các ứng dụng có kiến trúc MVVM hoặc MVI, nơi ViewModel quản lý trạng thái và View (Activity, Fragment, SwiftUI View) đăng ký các thay đổi và phản ứng bằng chuyển tiếp. Cách tiếp cận này loại bỏ sự phụ thuộc trực tiếp giữa UI và logic điều hướng.
Theo Google I/O 2023, điều hướng trì hoãn được khuyến nghị cho tất cả các kịch bản nơi điều hướng phụ thuộc vào kết quả của yêu cầu mạng, kiểm tra xác thực, tải cấu hình hoặc quyền. Mẫu này cũng bắt buộc khi xử lý Deeplink, nơi ứng dụng phải khởi chạy trước, tải màn hình gốc và chỉ sau đó mới điều hướng đến lộ trình đích.
Deferred Navigation được sử dụng trong các kịch bản nơi điều hướng trực tiếp dẫn đến trạng thái màn hình không chính xác hoặc lỗi tải. Hãy xem xét bốn trường hợp chính khi điều hướng trì hoãn là cần thiết.
Nếu người dùng chạm vào nội dung được bảo vệ, ứng dụng phải kiểm tra mã thông báo truy cập trước. Điều hướng trực tiếp đến màn hình nội dung sẽ dẫn đến màn hình trống hoặc lỗi 401 nếu mã thông báo hết hạn. Điều hướng trì hoãn kiểm tra mã thông báo, và chỉ khi thành công — mới điều hướng đến màn hình đích. Khi thất bại — chuyển hướng đến màn hình đăng nhập.
Khi một ứng dụng được mở qua liên kết ngoài, trước tiên phải tải màn hình gốc, khôi phục trạng thái điều hướng và chỉ sau đó thực hiện chuyển tiếp Deeplink. Điều hướng trực tiếp đến màn hình đích mà không có ngữ cảnh gốc sẽ dẫn đến các bất thường: ngăn xếp điều hướng trống hoặc ngăn xếp quay lại bị hỏng.
Khi chạm vào thông báo push, ứng dụng có thể ở một trong ba trạng thái: đã đóng, ở nền hoặc đang hoạt động. Deferred Navigation xác định trạng thái ứng dụng, tải nội dung cần thiết và chỉ sau đó hiển thị màn hình đích. iOS cho phép xử lý kịch bản này qua UNNotificationContentExtension.
Nếu chức năng của màn hình được kiểm soát bởi cờ tính năng từ máy chủ, điều hướng trì hoãn cho phép trước tiên yêu cầu cấu hình và chỉ sau đó hiển thị màn hình. Nếu tính năng bị tắt — người dùng thấy nội dung thay thế hoặc vùng giữ chỗ thay vì màn hình trống.
| Kịch bản | Điều hướng trực tiếp | Deferred Navigation |
|---|---|---|
| Ủy quyền | Màn hình trống với mã hết hạn | Chuyển hướng đến đăng nhập |
| Deeplink | Ngăn xếp quay lại bị hỏng | Ngăn xếp điều hướng chính xác |
| Push | Tải không có ngữ cảnh | Dữ liệu sẵn sàng trước khi chuyển |
| Cờ tính năng | Hiển thị chức năng không khả dụng | Vùng giữ chỗ hoặc thay thế |
Điều hướng trực tiếp là cách tiếp cận truyền thống nơi chuyển tiếp được thực hiện ngay lập tức để phản hồi một sự kiện. Người dùng nhấn nút và bộ định tuyến UI ngay lập tức chuyển màn hình. Cách tiếp cận này đơn giản và có thể dự đoán nhưng bị giới hạn trong các kịch bản cần dữ liệu từ máy chủ hoặc kiểm tra điều kiện.
Deferred Navigation thêm một lớp dưới dạng trạng thái bất đồng bộ. Sự kiện người dùng bắt đầu một thao tác và việc đăng ký kết quả kiểm soát điều hướng. Điều này làm tăng độ phức tạp của mã nhưng mang lại sự linh hoạt: cùng một trình kích hoạt có thể dẫn đến các màn hình khác nhau tùy thuộc vào dữ liệu đã tải.
Việc lựa chọn giữa hai cách tiếp cận phụ thuộc vào yêu cầu: nếu hiển thị màn hình không yêu cầu dữ liệu bất đồng bộ — hãy sử dụng điều hướng trực tiếp. Nếu màn hình phụ thuộc vào kết quả của yêu cầu, ủy quyền hoặc điều kiện bên ngoài — điều hướng trì hoãn là cần thiết. Cách tiếp cận kết hợp, nơi một số chuyển tiếp trực tiếp và một số trì hoãn, là phương pháp phổ biến nhất trong các ứng dụng công nghiệp.
Android Jetpack cung cấp các cơ chế để triển khai Deferred Navigation ở cấp độ kiến trúc. Ý tưởng chính — ViewModel quản lý trạng thái, trong khi Activity hoặc Fragment đăng ký các thay đổi và kích hoạt điều hướng qua NavController.
StateFlow trong coroutines Kotlin là công cụ lý tưởng cho điều hướng trì hoãn. ViewModel cập nhật StateFlow với sự kiện điều hướng và Activity quan sát nó và thực hiện chuyển tiếp. Khi sự kiện được xử lý, StateFlow được xóa, ngăn chặn điều hướng lặp lại.
class MainViewModel : ViewModel() {
private val _navigation = MutableSharedFlow<NavigationEvent>()
val navigation: SharedFlow<NavigationEvent> = _navigation
fun onDeepLinkReceived(link: String) {
viewModelScope.launch {
val data = resolveDeepLink(link)
_navigation.emit(NavigationEvent.GoToScreen(data))
}
}
}
Trong Activity, đăng ký điều hướng kích hoạt NavController với lộ trình từ ViewModel. Để ngăn chặn điều hướng lặp lại khi xoay màn hình, một wrapper NavigationEventWrapper được sử dụng, chỉ xử lý sự kiện một lần. Jetpack Navigation 2.7+ hỗ trợ Safe Args để truyền đối số an toàn về kiểu.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vm: MainViewModel by viewModels()
repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.navigation.collect { event ->
when (event) {
is NavigationEvent.GoToScreen ->
findNavController(R.id.nav_host)
.navigate(event.route)
}
}
}
}
}
iOS không có Navigation Component tích hợp tương tự Android Jetpack, vì vậy các nhà phát triển triển khai Deferred Navigation qua Coordinator Pattern kết hợp với Combine hoặc async/await. Coordinator quản lý ngăn xếp màn hình và đưa ra quyết định điều hướng dựa trên dữ liệu đã tải.
Coordinator là một đối tượng quản lý điều hướng giữa các ViewController. Kết hợp với Combine, ViewModel xuất bản các sự kiện qua PassthroughSubject và Coordinator đăng ký chúng và thực hiện chuyển tiếp. Cách tiếp cận này hoàn toàn tách biệt UI khỏi logic điều hướng và tuân theo các khuyến nghị của Apple về kiến trúc ứng dụng.
final class AppCoordinator {
private var cancellables = Set<AnyCancellable>()
func start(viewModel: MainViewModel) {
viewModel.$navigationDestination
.compactMap { $0 }
.sink { [weak self] destination in
self?.navigateTo(destination)
}
.store(in: &cancellables)
}
}
Swift 5.5 đã giới thiệu đồng thời có cấu trúc, cho phép triển khai điều hướng trì hoãn qua async/await mà không cần Combine. ViewModel cung cấp một hàm async trả về lộ trình sau khi tải dữ liệu. Coordinator gọi hàm này trong một Task và thực hiện chuyển tiếp dựa trên lộ trình nhận được.
class AuthViewModel: ObservableObject {
func resolveDeeplink(_ url: URL) async -> AppRoute? {
guard let token = await AuthService.shared.getValidToken() else { return .login }
return await DeeplinkRouter.resolve(url, token: token)
}
}
// In Coordinator:
Task {
if let route = await viewModel.resolveDeeplink(url) {
navigateTo(route)
}
}
Deferred Navigation đơn giản hóa việc xử lý các kịch bản bất đồng bộ nhưng yêu cầu cách tiếp cận có kỷ luật đối với quản lý trạng thái. Hãy xem xét các lỗi chính mà nhà phát triển mắc phải khi triển khai điều hướng trì hoãn.
Lỗi phổ biến nhất — cố gắng thực hiện điều hướng trì hoãn trước khi màn hình gốc được khởi tạo hoàn toàn và NavController hoặc Coordinator sẵn sàng chuyển tiếp. Trên Android điều này dẫn đến IllegalStateException, trên iOS — đến trạng thái UI không xác định. Giải pháp — đảm bảo vòng đời của thành phần ở trạng thái STARTED hoặc RESUMED trước khi kích hoạt điều hướng.
Nếu StateFlow hoặc Subject không xóa sự kiện sau khi xử lý, khi quay lại màn hình trước đó người dùng có thể tự động bị chuyển hướng đến cùng màn hình đó. Sử dụng SharedFlow với replay=0 trên Android hoặc CurrentValueSubject với nil sau khi xử lý trên iOS, để sự kiện điều hướng chỉ kích hoạt một lần.
Sử dụng một thành phần trung tâm cho tất cả điều hướng trong ứng dụng. Khi mỗi Activity, Fragment hoặc ViewController có bộ điều khiển điều hướng riêng, điều hướng trì hoãn giữa các phần khác nhau của ứng dụng trở nên hỗn loạn. Một Coordinator duy nhất giúp đơn giản hóa việc gỡ lỗi và kiểm tra các kịch bản điều hướng.
Deferred Navigation khó kiểm tra hơn điều hướng trực tiếp vì các thao tác bất đồng bộ đưa vào yếu tố thời gian. Sử dụng TestDispatcher trên Android (kotlinx-coroutines-test) và XCTestExpectation trên iOS để mô phỏng tải dữ liệu và xác minh rằng điều hướng theo lộ trình mong đợi. Giả lập các dịch vụ ủy quyền và deeplink để kiểm tra riêng lẻ từng kịch bản.
Câu hỏi thường gặp
Deferred Navigation là một mẫu chuyển tiếp trì hoãn có thể được áp dụng trong bất kỳ kịch bản bất đồng bộ nào. Deep Link là một trình kích hoạt cho điều hướng trì hoãn, nhưng không phải là duy nhất. Xác thực và cờ tính năng cũng sử dụng điều hướng trì hoãn.
Có, hầu hết các ứng dụng sử dụng cách tiếp cận kết hợp. Màn hình danh sách sản phẩm (không có phụ thuộc bất đồng bộ) có thể sử dụng điều hướng trực tiếp, trong khi màn hình chi tiết có tải dữ liệu sử dụng trì hoãn. Sự phân chia được xác định bởi kiến trúc của từng màn hình cụ thể.
Sử dụng SharedFlow không replay trên Android và combineLatest không đệm trên iOS. Hủy các đăng ký trước đó khi có trình kích hoạt mới. Điều này đảm bảo chỉ sự kiện điều hướng mới nhất được xử lý.
Có, trong Compose điều hướng trì hoãn được triển khai qua đăng ký StateFlow của ViewModel và gọi NavController.navigate trong LaunchedEffect. Google khuyến nghị sử dụng Navigation Compose với mô hình hướng sự kiện cho các kịch bản trì hoãn.
Trì hoãn điều hướng cho đến khi ứng dụng hoạt động trở lại. Trên Android sử dụng Lifecycle.State.STARTED để lọc sự kiện. Trên iOS kiểm tra UIApplication.State trong các khối Combine hoặc async/await.
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