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()
và
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 đó.
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Tạo trạng thái giao diện người dùng
- Phần tử giữ trạng thái và trạng thái giao diện người dùng {:#mad-arch}
- Hướng dẫn về cấu trúc ứng dụng