Lớp giao diện người dùng (Khung hiển thị)

Các khái niệm và cách triển khai Jetpack Compose

Vai trò của giao diện người dùng (UI) là hiển thị dữ liệu ứng dụng lên màn hình và là điểm chính trong quá trình tương tác của người dùng. Bất cứ khi nào dữ liệu thay đổi – do sự tương tác của người dùng (như nhấn vào một nút) hoặc nhận dữ liệu bên ngoài (như phản hồi mạng) – giao diện người dùng sẽ cập nhật để phản ánh các thay đổi đó. Vậy, tóm lại là, giao diện người dùng là sự diễn tả trực quan của trạng thái ứng dụng khi truy xuất từ lớp dữ liệu.

Tuy nhiên, dữ liệu ứng dụng bạn nhận được từ lớp dữ liệu thường ở một định dạng khác với thông tin bạn cần hiển thị. Ví dụ: bạn có thể chỉ cần một phần dữ liệu cho giao diện người dùng hoặc bạn có thể cần hợp nhất 2 nguồn dữ liệu khác nhau để trình bày thông tin liên quan cho người dùng. Bất kể logic bạn áp dụng là gì, bạn đều cần chuyển cho giao diện người dùng tất cả các thông tin cần thiết để giao diện kết xuất đầy đủ. Lớp giao diện người dùng là quy trình chuyển đổi các thay đổi dữ liệu ứng dụng thành dạng thức mà giao diện người dùng có thể trình bày và sau đó hiển thị.

Hiển thị trạng thái giao diện người dùng

Sau khi bạn xác định trạng thái giao diện người dùng và xác định cách bạn sẽ quản lý việc tạo trạng thái, bước tiếp theo là hiển thị trạng thái đã được tạo cho giao diện người dùng. Vì bạn đang sử dụng UDF để quản lý việc tạo ra trạng thái, bạn có thể coi trạng thái đã được tạo là một sự kiện phát trực tiếp—nói cách khác, nhiều phiên bản của trạng thái sẽ được tạo theo thời gian. Do đó, bạn nên hiển thị trạng thái giao diện người dùng trong một đối tượng giữ dữ liệu có thể ghi nhận được như LiveData hoặc StateFlow. Lý do là vì giao diện người dùng có thể phản ứng với bất kỳ thay đổi nào được thực hiện trong trạng thái không phải lấy dữ liệu trực tiếp từ ViewModel. Các loại này có lợi ích là luôn lưu vào bộ nhớ đệm phiên bản mới nhất của trạng thái giao diện người dùng. Hoạt động này rất hữu ích trong việc khôi phục trạng thái nhanh chóng sau khi thay đổi cấu hình.

class NewsViewModel(...) : ViewModel() {

    val uiState: StateFlow<NewsUiState> = 
}

Một cách phổ biến để tạo một trình phát trực tiếp UiState là bằng cách hiển thị một trình phát trực tiếp có thể thay đổi hỗ trợ dưới dạng trình phát bất biến từ ViewModel—ví dụ: hiển thị MutableStateFlow<UiState> dưới dạng StateFlow<UiState>.

class NewsViewModel(...) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    ...

}

Khi đó, ViewModel có thể hiển thị các phương thức gây đột biến bên trong trạng thái, mà các đột biến này sẽ phát hành thông tin cập nhật để giao diện người dùng sử dụng. Ví dụ: trong trường hợp một hành động không đồng bộ cần được thực hiện; một coroutine có thể khởi chạy bằng cách sử dụng viewModelScope và trạng thái bất biến có khả năng được cập nhật sau khi hoàn thành.

class NewsViewModel(
    private val repository: NewsRepository,
    ...
) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    private var fetchJob: Job? = null

    fun fetchArticles(category: String) {
        fetchJob?.cancel()
        fetchJob = viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                _uiState.update {
                    it.copy(newsItems = newsItems)
                }
            } catch (ioe: IOException) {
                // Handle the error and notify the UI when appropriate.
                _uiState.update {
                    val messages = getMessagesFromThrowable(ioe)
                    it.copy(userMessages = messages)
                }
            }
        }
    }
}

Sử dụng trạng thái giao diện người dùng

Khi sử dụng chủ thể sở hữu dữ liệu có thể quan sát trong giao diện người dùng, hãy đảm bảo rằng bạn xem xét vòng đời của giao diện người dùng. Việc này rất quan trọng vì giao diện người dùng sẽ không quan sát trạng thái giao diện người dùng khi chế độ xem không hiển thị cho người dùng. Để tìm hiểu thêm về chủ đề này, hãy xem bài đăng trên blog này. Khi sử dụng LiveData, LifecycleOwner ngầm quan tâm tới các vấn đề liên quan vòng đời. Khi sử dụng luồng, cách tốt nhất là xử lý luồng với phạm vi coroutine thích hợp và API repeatOnLifecycle:

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Hiển thị thao tác đang diễn ra

Một cách đơn giản để biểu thị các trạng thái đang tải trong một lớp UiState là có trường boolean:

data class NewsUiState(
    val isFetchingArticles: Boolean = false,
    ...
)

Giá trị của cờ này đại diện cho việc có hoặc không có thanh tiến trình trong giao diện người dùng.

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Bind the visibility of the progressBar to the state
                // of isFetchingArticles.
                viewModel.uiState
                    .map { it.isFetchingArticles }
                    .distinctUntilChanged()
                    .collect { progressBar.isVisible = it }
            }
        }
    }
}

Ảnh động

Để cung cấp chuyển đổi điều hướng ở cấp cao nhất một cách linh hoạt và nhịp nhàng, có thể bạn cần đợi màn hình thứ hai tải dữ liệu trước khi bắt đầu ảnh động. Khung chế độ xem Android cung cấp các giao diện kết nối để trì hoãn việc chuyển đổi giữa các đích đến của mảnh với các API postponeEnterTransition()startPostponedEnterTransition(). Các API này giúp đảm bảo rằng các thành phần giao diện người dùng trên màn hình thứ hai (thường là một hình ảnh tìm nạp từ mạng) sẵn sàng hiển thị trước khi giao diện người dùng tạo hiệu ứng động cho quá trình chuyển đổi sang màn hình đó.