Deferred Navigation — nó là gì, nguyên tắc và triển khai

Tác giả: IT Sectr Đã đăng: 2026-06-10 Thời gian đọc: 9 phút

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 — điều hướng trì hoãn nơi chuyển tiếp xảy ra sau khi hoàn thành các thao tác bất đồng bộ
  • Điều hướng trực tiếp xử lý chuyển tiếp ngay lập tức, deferred chờ dữ liệu
  • Các kịch bản điển hình: xác thực, Deeplink, thông báo push, tải cấu hình
  • Trên Android được triển khai qua Navigation Component với callback và StateFlow
  • Trên iOS sử dụng Combine hoặc async/await với bộ điều phối điều hướng

Deferred Navigation là gì

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.

Khi nào cần điều hướng trì hoãn

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.

Ủy quyền và xác thực

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.

Xử lý Deeplink

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.

Thông báo push có nội dung

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.

Cờ tính năng động

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ếpDeferred Navigation
Ủy quyềnMàn hình trống với mã hết hạnChuyển hướng đến đăng nhập
DeeplinkNgăn xếp quay lại bị hỏngNgăn xếp điều hướng chính xác
PushTải không có ngữ cảnhDữ liệu sẵn sàng trước khi chuyển
Cờ tính năngHiển thị chức năng không khả dụngVùng giữ chỗ hoặc thay thế

Deferred Navigation vs điều hướng trực tiếp

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

Triển khai trên Android: Navigation Component và ViewModel

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.

Deferred Navigation với StateFlow

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.

kotlin
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))
        }
    }
}

Deferred Navigation với NavController

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.

kotlin
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)
                }
            }
        }
    }
}

Triển khai trên iOS: Coordinator Pattern và Combine

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 với Combine

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.

swift
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)
    }
}

Deferred Navigation với async/await

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.

swift
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)
    }
}

Lỗi thường gặp và phương pháp tốt nhất

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: điều hướng trước khi hoàn tất khởi tạo

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.

Lỗi: điều hướng lặp lại khi quay lại màn hình

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.

Phương pháp tốt nhất: một NavController hoặc Coordinator duy nhất

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.

Phương pháp tốt nhất: kiểm tra các kịch bản điều hướng trì hoãn

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 khác với Deep Link như thế nào?

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ó thể kết hợp điều hướng trì hoãn và trực tiếp không?

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

Làm thế nào để tránh race condition với điều hướng trì hoãn?

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

Jetpack Compose có hỗ trợ điều hướng trì hoãn không?

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

Làm thế nào để xử lý điều hướng trì hoãn khi ứng dụng bị thu nhỏ?

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

  • Deferred Navigation — một mẫu nơi chuyển tiếp xảy ra sau khi hoàn thành thao tác bất đồng bộ
  • Các kịch bản chính: xác thực, Deeplink, thông báo push, cờ tính năng
  • StateFlow/SharedFlow trên Android và Combine/async-await trên iOS là công cụ triển khai
  • Deferred Navigation không giống điều hướng trực tiếp, không gây race condition khi tải bất đồng bộ
  • Navigation Component trên Android và Coordinator Pattern trên iOS là kiến trúc cơ bản
  • Một NavController/Coordinator duy nhất bắt buộc để điều hướng nhất quán
  • Kiểm tra điều hướng trì hoãn yêu cầu TestDispatcher và mock cho các dịch vụ bất đồng bộ

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.

Thảo luận dự án

Đọc thêm