Vai trò của giao diện người dùng là hiển thị dữ liệu ứng dụng trê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ị.
![Trong một cấu trúc thông thường, các phần tử trên giao diện người dùng của lớp giao diện người dùng phụ thuộc vào phần tử giữ trạng thái. Phần tử này phụ thuộc vào các lớp (class) từ lớp dữ liệu hoặc lớp miền không bắt buộc.](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-ui-overview.png?authuser=2&hl=vi)
Nghiên cứu điển hình cơ bản
Giả sử có 1 ứng dụng tìm nạp các bài viết tin tức để người dùng đọc. Ứng dụng có màn hình bài viết trình bày các bài viết có sẵn để đọc, đồng thời cũng cho phép người dùng đã đăng nhập đánh dấu các bài viết thực sự nổi bật. Do có rất nhiều bài viết tại mọi thời điểm trong ngày, nên người đọc cần tính năng duyệt qua bài viết theo danh mục. Tóm lại, ứng dụng cho phép người dùng thực hiện các hành động sau:
- Xem các bài viết có sẵn để đọc.
- Duyệt qua các bài viết theo danh mục.
- Đăng nhập và đánh dấu các bài viết nhất định.
- Truy cập một số tính năng nâng cao nếu đủ điều kiện.
![](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-ui-basic-case-study.png?authuser=2&hl=vi)
Các phần sau đây sử dụng ví dụ này làm nghiên cứu điển hình để giới thiệu các nguyên tắc về luồng dữ liệu một chiều, cũng như minh hoạ các vấn đề mà các nguyên tắc này giúp giải quyết trong bối cảnh cấu trúc ứng dụng cho lớp giao diện người dùng.
Cấu trúc lớp giao diện người dùng
Thuật ngữGiao diện người dùng đề cập đến các phần tử trên giao diện người dùng như các hoạt động và các mảnh hiển thị dữ liệu, độc lập với các API mà các phần tử/mảnh đó sử dụng để thực hiện việc này (Views hoặc Jetpack Compose ). Vì vai trò củalớp dữ liệu là giữ, quản lý và cung cấp quyền truy cập vào dữ liệu ứng dụng, nên lớp giao diện người dùng phải thực hiện các bước sau:
- Sử dụng và biến đổi dữ liệu ứng dụng thành dữ liệu mà giao diện người dùng (UI) có thể dễ dàng kết xuất.
- Sử dụng và biến đổi dữ liệu kết xuất được trên giao diện người dùng thành các phần tử UI để người dùng thấy.
- Sử dụng các sự kiện đầu vào từ hoạt động của người dùng từ các phần tử UI được tập hợp đó và phản ánh hiệu ứng của chúng trong dữ liệu UI nếu cần.
- Lặp lại các bước từ 1 đến 3 khi cần.
Phần còn lại của hướng dẫn này minh hoạ cách triển khai lớp giao diện người dùng thực hiện các bước này. Cụ thể, hướng dẫn này bao gồm các tác vụ và khái niệm sau:
- Cách xác định trạng thái giao diện người dùng.
- Luồng dữ liệu một chiều (UDF) làm phương tiện để tạo và quản lý trạng thái giao diện người dùng.
- Cách hiển thị trạng thái giao diện người dùng có các kiểu dữ liệu có thể quan sát được theo nguyên tắc UDF.
- Cách triển khai giao diện người dùng sử dụng trạng thái giao diện có thể quan sát được.
Tác vụ cơ bản nhất trong số này là xác định trạng thái giao diện người dùng.
Xác định trạng thái giao diện người dùng
Tham khảo nghiên cứu điển hình đã nêu ở trên. Tóm lại, giao diện người dùng hiển thị danh sách các bài viết cùng với một số siêu dữ liệu của mỗi bài viết. Thông tin này chính là trạng thái giao diện người dùng mà ứng dụng hiển thị cho người dùng.
Nói cách khác: nếu giao diện người dùng là nội dung mà người dùng nhìn thấy, thì trạng thái giao diện người dùng chính là nội dung mà ứng dụng biểu thị người dùng sẽ thấy. Giống như hai mặt của một đồng xu, giao diện người dùng là đại diện hình ảnh của trạng thái giao diện người dùng. Bất kỳ thay đổi nào đối với trạng thái giao diện người dùng đều được phản ánh ngay lập tức trong giao diện người dùng.
![Giao diện người dùng là kết quả của việc liên kết các phần tử trên giao diện người dùng trên màn hình với trạng thái giao diện người dùng.](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-ui-elements-state.png?authuser=2&hl=vi)
Hãy cân nhắc nghiên cứu điển hình; để đáp ứng các yêu cầu của ứng dụng Tin tức, thông tin
cần phải có để hiển thị đầy đủ giao diện người dùng có thể được gói gọn trong một lớp dữ liệu
NewsUiState
được xác định như sau:
data class NewsUiState(
val isSignedIn: Boolean = false,
val isPremium: Boolean = false,
val newsItems: List<NewsItemUiState> = listOf(),
val userMessages: List<Message> = listOf()
)
data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
...
)
Bất biến
Định nghĩa về trạng thái giao diện người dùng trong ví dụ ở trên là bất biến. Lợi ích chính của thuộc tính này là các đối tượng bất biến cung cấp các đảm bảo đối với trạng thái của ứng dụng ngay lập tức. Việc này giải phóng giao diện người dùng để tập trung vào một vai trò duy nhất: đọc trạng thái và cập nhật các phần tử trên giao diện người dùng cho phù hợp. Do đó, bạn không bao giờ nên trực tiếp sửa đổi trạng thái giao diện người dùng trong giao diện người dùng, trừ khi giao diện người dùng đó là nguồn duy nhất của dữ liệu. Việc vi phạm nguyên tắc này sẽ dẫn đến nhiều nguồn thông tin chính xác cho cùng một phần thông tin, dẫn đến những điểm thiếu đồng nhất của dữ liệu và các lỗi nhỏ.
Ví dụ: nếu cập nhật thao tác gắn cờ bookmarked
vào trong đối tượng NewsItemUiState
từ trạng thái giao diện người dùng
trong nghiên cứu điển hình tại lớp Activity
, thì cờ đó sẽ
xung đột với lớp dữ liệu làm nguồn trạng thái được đánh dấu trang của
một bài viết. Các lớp dữ liệu bất biến rất hữu ích để ngăn chặn kiểu
phản mẫu này.
Quy ước đặt tên trong hướng dẫn này
Trong hướng dẫn này, các lớp trạng thái giao diện người dùng được đặt tên dựa trên chức năng của màn hình hoặc một phần của màn hình mà lớp đó mô tả. Quy ước như sau:
functionality + UiState.
Ví dụ: trạng thái của màn hình hiển thị tin tức có thể gọi lệnh
NewsUiState
, còn trạng thái của một mục tin tức trong danh sách mục tin tức có thể là
NewsItemUiState
.
Quản lý trạng thái bằng Luồng dữ liệu một chiều
Phần trước đã thiết lập trạng thái giao diện người dùng là hiện trạng bất biến của các chi tiết cần thiết để giao diện người dùng hiển thị. Tuy nhiên, tính chất động của dữ liệu trong ứng dụng có nghĩa là trạng thái có thể thay đổi theo thời gian. Điều này có thể là do sự tương tác của người dùng hoặc các sự kiện khác làm thay đổi dữ liệu cơ bản được dùng để điền sẵn ứng dụng.
Những hoạt động tương tác này có thể hưởng lợi từ một trình trung gian để xử lý chúng, việc xác định logic áp dụng cho từng sự kiện và việc chuyển đổi cần thiết đối với các nguồn dữ liệu hỗ trợ để tạo trạng thái giao diện người dùng. Những tương tác này và logic của chúng có thể được đặt trong chính giao diện người dùng. Nhưng điều này có thể nhanh chóng trở nên khó sử dụng vì giao diện người dùng bắt đầu chuyển đổi trở nên phức tạp hơn so với tên gọi của nó: giao diện người dùng trở thành chủ sở hữu dữ liệu, nơi sản xuất, nơi chuyển đổi, v.v Hơn nữa, hoạt động xử lý này có thể ảnh hưởng đến khả năng thử nghiệm vì mã kết quả là một sự kết hợp chặt chẽ mà không có ranh giới rõ ràng. Cuối cùng, hoạt động giảm gánh nặng đó sẽ có lợi cho giao diện người dùng. Trừ khi trạng thái giao diện người dùng rất đơn giản, trách nhiệm duy nhất của giao diện người dùng là sử dụng và hiển thị trạng thái giao diện người dùng.
Phần này thảo luận về Luồng dữ liệu một chiều (UDF), một mẫu cấu trúc giúp thực thi quá trình phân tách trách nhiệm lành mạnh này.
Phần tử giữ trạng thái
Các lớp chịu trách nhiệm tạo trạng thái giao diện người dùng và chứa logic cần thiết mà từ đó tác vụ được gọi là phần tử giữ trạng thái. Các phần tử giữ trạng thái có nhiều kích thước, tuỳ thuộc vào phạm vi của các phần tử trên giao diện người dùng tương ứng mà người dùng quản lý, từ một tiện ích đơn lẻ như thanh ứng dụng ở dưới cùng cho đến toàn bộ màn hình hoặc một đích đến điều hướng.
Trong trường hợp sau, hoạt động triển khai điển hình là phiên bản của
ViewModel, mặc dù tuỳ thuộc vào
các yêu cầu của ứng dụng, nhưng bạn chỉ nên sử dụng một lớp đơn giản. Ví dụ: Ứng dụng News (Tin tức) trong nghiên cứu điển hình sử dụng một lớp NewsViewModel
làm phần tử giữ trạng thái để tạo trạng thái giao diện người dùng cho màn hình hiển thị trong phần đó.
Có nhiều cách để lập mô hình hiện tượng đồng phụ thuộc giữa giao diện người dùng và tính năng nhà sản xuất trạng thái
của giao diện đó. Tuy nhiên, do sự tương tác giữa giao diện người dùng và lớp ViewModel
của giao diện phần lớn có thể được hiểu là sự kiện đầu vào và đảm bảo trạng thái đầu ra sau đó,
mối quan hệ có thể được thể hiện như trong sơ đồ sau:
![Các dữ liệu ứng dụng di chuyển từ lớp dữ liệu sang ViewModel. Trạng thái giao diện người dùng
di chuyển từ ViewModel sang các phần tử trên giao diện người dùng và các sự kiện di chuyển từ các phần tử trên giao diện người dùng
quay trở lại ViewModel.](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-ui-udf.png?authuser=2&hl=vi)
Mô hình mà trạng thái giảm xuống và sự kiện tăng lên được gọi là luồng dữ liệu một chiều (UDF). Các hoạt động di chuyển kéo theo của mô hình này cho cấu trúc ứng dụng là như sau:
- Tính năng ViewModel giữ và hiển thị trạng thái mà giao diện người dùng sử dụng. Trạng thái giao diện người dùng là dữ liệu ứng dụng được ViewModel chuyển đổi.
- Giao diện người dùng thông báo cho ViewModel về các sự kiện của người dùng.
- Tính năng ViewModel xử lý các thao tác của người dùng và cập nhật trạng thái.
- Trạng thái đã cập nhật được đưa trở lại giao diện người dùng để hiển thị.
- Các thao tác trên được lặp lại cho bất kỳ sự kiện nào gây ra hiện tượng đột biến của trạng thái.
Đối với các đích đến hoặc màn hình điều hướng, ViewModel hoạt động với các kho lưu trữ hoặc sử dụng các lớp điển hình để lấy và chuyển đổi dữ liệu thành trạng thái giao diện người dùng trong khi kết hợp tác động của các sự kiện có thể gây ra đột biến trạng thái. Nghiên cứu điển hình đã đề cập trước đó có chứa một danh sách các bài viết, mỗi bài viết có tiêu đề, phần mô tả, nguồn, tên tác giả, ngày xuất bản và có được đánh dấu trang hay không. Giao diện người dùng cho mỗi mục trong bài viết như sau:
![](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-ui-basic-case-study-item.png?authuser=2&hl=vi)
Việc một người dùng yêu cầu đánh dấu trang một bài viết là ví dụ về một sự kiện có thể gây ra những đột biến trạng thái. Là nhà sản xuất trạng thái, trách nhiệm của ViewModel là xác định tất cả các logic cần phải đó để điền sẵn tất cả các trường trong trạng thái giao diện người dùng và xử lý các sự kiện cần thiết để giao diện người dùng hiển thị đầy đủ.
![Sự kiện giao diện người dùng xảy ra khi người dùng đánh dấu trang một bài viết. ViewModel
thông báo cho lớp dữ liệu về sự thay đổi trạng thái. Lớp dữ liệu duy trì
thay đổi dữ liệu và cập nhật dữ liệu ứng dụng. Dữ liệu ứng dụng mới với
bài viết được đánh dấu trang sẽ được chuyển lên đến ViewModel, sau đó tạo ra
trạng thái giao diện người dùng mới và chuyển sang các phần tử trên giao diện người dùng để hiển thị.](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-ui-udf-in-action.png?authuser=2&hl=vi)
Các phần sau sẽ tìm hiểu kỹ hơn về các sự kiện gây ra thay đổi trạng thái và cách xử lý chúng bằng UDF.
Các loại logic
Đánh dấu một bài viết là ví dụ về logic kinh doanh vì bài viết này cung cấp giá trị cho ứng dụng của bạn. Để tìm hiểu thêm về điều này, hãy xem tranglớp dữ liệu. Tuy nhiên, có nhiều loại logic quan trọng cần xác định:
- Logic nghiệp vụ là việc triển khai các yêu cầu của sản phẩm về dữ liệu ứng dụng. Như đã đề cập, ví dụ là đánh dấu trang một bài viết trong ứng dụng nghiên cứu điển hình. Logic kinh doanh thường được đặt trong lớp miền hoặc lớp dữ liệu, nhưng không bao giờ được đặt trong lớp giao diện người dùng.
- Logic hành vi giao diện người dùng hoặc logic giao diện người dùng là cách hiển thị các thay đổi về trạng thái trên màn hình. Các ví dụ bao gồm việc lấy văn bản phù hợp để hiển thị trên màn hình bằng Android
Resources
, chuyển đến màn hình cụ thể khi người dùng nhấp vào nút hoặc hiển thị thông báo của người dùng trên màn hình bằng một thông báo ngắn hoặc thanh thông báo nhanh.
Logic giao diện người dùng, đặc biệt là khi liên quan đến các loại giao diện người dùng như Context
, phải nằm trong giao diện người dùng chứ không phải trong ViewModel. Nếu giao diện người dùng phát triển phức tạp và bạn muốn uỷ quyền logic giao diện người dùng cho một lớp khác để ưu tiên khả năng kiểm thử và phân tách các mối quan ngại, bạn có thể tạo một lớp đơn giản làm phần tử giữ trạng thái. Các lớp đơn giản được tạo trong giao diện người dùng
có thể sử dụng các phần phụ thuộc SDK Android vì các lớp này tuân theo vòng đời của giao diện người dùng;
các đối tượng ViewModel có thời gian tồn tại lâu hơn.
Để biết thêm thông tin về phần tử giữ trạng thái và mức độ phù hợp với bối cảnh của chúng trong việc giúp xây dựng giao diện người dùng, hãy xem hướng dẫn trạng thái trong Jetpack Compose.
Tại sao nên sử dụng UDF?
UDF lập mô hình chu kỳ tạo trạng thái như hiển thị trong Hình 4. Cơ chế này cũng tách biệt nơi bắt đầu thay đổi trạng thái, nơi có thay đổi về trạng thái và cuối cùng nơi sử dụng trạng thái. Việc phân tách này cho phép giao diện người dùng thực hiện chính xác nội dung của tên giao diện: hiển thị thông tin bằng cách quan sát các thay đổi trạng thái, và chuyển tiếp ý định của người dùng bằng cách truyền các thay đổi đó tới ViewModel.
Nói cách khác, UDF cho phép nội dung sau:
- Tính nhất quán của dữ liệu. Có một nguồn thông tin xác thực duy nhất cho giao diện người dùng.
- Khả năng thử nghiệm. Nguồn trạng thái bị tách riêng và do đó có thể thử nghiệm độc lập với giao diện người dùng.
- Khả năng bảo trì. Đột biến của trạng thái tuân theo một mẫu được xác định rõ ràng trong đó đột biến là kết quả của cả các sự kiện của người dùng và nguồn dữ liệu mà chúng lấy ra.
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.
Số lượt xem
class NewsViewModel(...) : ViewModel() {
val uiState: StateFlow<NewsUiState> = …
}
Compose
class NewsViewModel(...) : ViewModel() {
val uiState: NewsUiState = …
}
Để biết thông tin giới thiệu về LiveData
với tư cách là chủ thể dữ liệu có thể quan sát, hãy xem codelab
này. Để có phần giới thiệu tương tự
về luồng Kotlin, hãy xem các luồng Kotlin trên Android.
Trong các trường hợp dữ liệu được hiển thị với giao diện người dùng tương đối đơn giản, thì thường nên gói dữ liệu trong một loại trạng thái giao diện người dùng vì loại trạng thái này truyền tải mối quan hệ giữa sự phát xạ của phần tử giữ trạng thái và màn hình hoặc phần tử trên giao diện người dùng được liên kết. Hơn nữa, khi phần tử trên giao diện người dùng trở nên dùng phức tạp hơn, sẽ dễ dàng hơn khi thêm định nghĩa về trạng thái giao diện người dùng nhằm cung cấp thêm thông tin cần thiết để hiển thị phần tử trên giao diện người dùng.
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>
.
Số lượt xem
class NewsViewModel(...) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
...
}
Compose
class NewsViewModel(...) : ViewModel() {
var uiState by mutableStateOf(NewsUiState())
private set
...
}
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.
Số lượt xem
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)
}
}
}
}
}
Compose
class NewsViewModel(
private val repository: NewsRepository,
...
) : ViewModel() {
var uiState by mutableStateOf(NewsUiState())
private set
private var fetchJob: Job? = null
fun fetchArticles(category: String) {
fetchJob?.cancel()
fetchJob = viewModelScope.launch {
try {
val newsItems = repository.newsItemsForCategory(category)
uiState = uiState.copy(newsItems = newsItems)
} catch (ioe: IOException) {
// Handle the error and notify the UI when appropriate.
val messages = getMessagesFromThrowable(ioe)
uiState = uiState.copy(userMessages = messages)
}
}
}
}
Trong ví dụ trên, lớp NewsViewModel
thử tìm nạp các bài viết cho
một danh mục nhất định, rồi phản ánh kết quả của lần thử—cho dù thành công hay
không thành công—ở trạng thái giao diện người dùng trong đó giao diện người dùng có thể phản ứng với lớp một cách phù hợp. Hãy xem mục Hiển thị lỗi trên màn hình để tìm hiểu thêm về cách xử lý
lỗi.
Các yếu tố cần cân nhắc khác
Ngoài hướng dẫn trước, hãy cân nhắc các điều sau đây khi hiển thị trạng thái giao diện người dùng:
Đối tượng trạng thái giao diện người dùng phải xử lý các trạng thái có liên quan với nhau. Điều này giúp giảm bớt các điểm không thống nhất và giúp mã dễ hiểu hơn. Nếu bạn hiển thị danh sách các mục tin tức và số lượng dấu trang trong hai trình phát trực tiếp khác nhau, bạn có thể gặp phải trường hợp một nội dung đã được cập nhật và mục còn lại thì không. Khi bạn sử dụng một trình phát trực tiếp duy nhất, cả hai thành phần đều được cập nhật. Ngoài ra, một số logic kinh doanh có thể yêu cầu sự kết hợp của các nguồn. Ví dụ: bạn có thể chỉ cần hiển thị nút dấu trang nếu người dùng đăng nhập và người dùng đó là người đăng ký gói thuê bao dịch vụ tin tức cao cấp. Bạn có thể xác định lớp trạng thái giao diện người dùng như sau:
data class NewsUiState( val isSignedIn: Boolean = false, val isPremium: Boolean = false, val newsItems: List<NewsItemUiState> = listOf() ) val NewsUiState.canBookmarkNews: Boolean get() = isSignedIn && isPremium
Trong khai báo này, chế độ hiển thị của nút dấu trang là một thuộc tính phái sinh của hai thuộc tính khác. Khi logic kinh doanh ngày càng phức tạp, việc có một lớp
UiState
duy nhất, trong đó tất cả thuộc tính có sẵn ngay lập tức ngày càng trở nên quan trọng.Trạng thái giao diện người dùng: một trình phát trực tiếp hay nhiều trình phát trực tiếp? Nguyên tắc hướng dẫn chính để lựa chọn giữa việc hiển thị trạng thái giao diện người dùng trong một trình phát trực tiếp hoặc trong nhiều trình phát trực tiếp là dấu đầu dòng trước đó: mối quan hệ giữa các mục đã phát ra. Ưu điểm lớn nhất của khả năng hiển thị quảng cáo phát trực tiếp là sự tiện lợi và tính nhất quán của dữ liệu: người tiêu dùng trạng thái luôn có thông tin mới nhất ngay lập tức tại bất kỳ thời điểm nào. Tuy nhiên, có những trường hợp mà các trình phát trực tiếp riêng biệt từ ViewModel có thể phù hợp:
Các loại dữ liệu không liên quan: Một số trạng thái cần thiết để hiển thị giao diện người dùng có thể hoàn toàn độc lập với nhau. Trong những trường hợp như vậy, chi phí của việc gộp các trạng thái khác nhau này lại với nhau có thể lớn hơn lợi ích, đặc biệt là nếu một trong các trạng thái này được cập nhật thường xuyên hơn các trạng thái còn lại.
Di chuyển
UiState
: Càng có nhiều trường trong một đối tượngUiState
, càng có nhiều khả năng trình phát trực tiếp sẽ phát do một trong các trường của nó đang được cập nhật. Bởi vì các khung hiển thị không có một cơ chế so sánh để hiểu liệu các lượt phát liên tiếp là khác nhau hay giống nhau, mọi lượt phát đều tạo ra một bản cập nhật cho khung hiển thị. Điều này có nghĩa là có thể cần giảm thiểu sử dụng các APIFlow
hoặc những phương thức nhưdistinctUntilChanged()
trênLiveData
.
Sử dụng trạng thái giao diện người dùng
Để sử dụng trình phát trực tiếp của đối tượng UiState
trong giao diện người dùng, bạn sử dụng toán tử đầu cuối
cho loại dữ liệu có khả năng ghi nhận mà bạn đang sử dụng. Ví dụ: đối với
LiveData
, bạn sử dụng phương thức observe()
và đối với các luồng Kotlin, bạn sử dụng phương thức
collect()
hoặc các biến thể của phương thức này.
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
:
Số lượt xem
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
}
}
}
}
}
Compose
@Composable
fun LatestNewsScreen(
viewModel: NewsViewModel = viewModel()
) {
// Show UI elements based on the viewModel.uiState
}
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.
Số lượt xem
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 }
}
}
}
}
Compose
@Composable
fun LatestNewsScreen(
modifier: Modifier = Modifier,
viewModel: NewsViewModel = viewModel()
) {
Box(modifier.fillMaxSize()) {
if (viewModel.uiState.isFetchingArticles) {
CircularProgressIndicator(Modifier.align(Alignment.Center))
}
// Add other UI elements. For example, the list.
}
}
Hiển thị lỗi trên màn hình
Việc hiển thị lỗi trong giao diện người dùng sẽ tương tự như việc hiển thị thao tác đang diễn ra vì chúng đều dễ dàng được biểu thị bằng các giá trị boolean cho biết chúng có hiện diện hay không. Tuy nhiên, lỗi cũng có thể bao gồm thông báo liên quan để chuyển tiếp trở lại tới người dùng hoặc một hành động được liên kết với chúng để thử lại thao tác không thành công. Do đó, mặc dù thao tác đang diễn ra là thực hiện tải hoặc không tải dữ liệu, nhưng các trạng thái lỗi có thể cần được mô hình hoá với các lớp dữ liệu lưu trữ siêu dữ liệu phù hợp với ngữ cảnh của lỗi.
Ví dụ: hãy xem xét ví dụ từ phần trước, hiển thị một thanh tiến trình trong khi tìm nạp bài viết. Nếu thao tác này dẫn đến lỗi, bạn có thể muốn hiển thị một hoặc nhiều thông báo cho người dùng biết chi tiết về sự cố.
data class Message(val id: Long, val message: String)
data class NewsUiState(
val userMessages: List<Message> = listOf(),
...
)
Sau đó, thông báo lỗi có thể được hiển thị cho người dùng dưới dạng các thành phần giao diện người dùng như thanh thông báo nhanh. Điều này liên quan đến cách các sự kiện giao diện người dùng được tạo và sử dụng, hãy xem trang các sự kiện giao diện người dùng để biết thêm thông tin.
Chuỗi lệnh và tính đồng thời
Mọi tác vụ được thực hiện trong ViewModel nên là luồng chính–an toàn để gọi lệnh từ chuỗi lệnh chính. Lý do là do dữ liệu và các lớp miền chịu trách nhiệm về việc chuyển tác vụ sang một chuỗi lệnh khác.
Nếu một ViewModel thực hiện các tác vụ lâu dài, sao đó nó cũng chịu trách nhiệm di chuyển logic đó sang một chuỗi lệnh ở chế độ nền. Các coroutine Kotlin là một cách tuyệt vời để quản lý các thao tác đồng thời và chúng được thành phần cấu trúc Jetpack cung cấp hỗ trợ tích hợp. Để tìm hiểu thêm về cách sử dụng coroutine trong các ứng dụng Android, hãy xem Coroutine Kotlin trên Android.
Tính năng điều hướng
Các thay đổi trong điều hướng ứng dụng thường được các sự kiện tương tự trình phát tạo ra. Ví dụ:
sau khi một lớp SignInViewModel
thực hiện đăng nhập, UiState
có thể có một
trường isSignedIn
đặt với true
. Những trình kích hoạt như vậy nên được sử dụng
giống như các trình kích hoạt được đề cập trong mục Sử dụng trạng thái giao diện người dùng
bên trên, ngoại trừ hoạt động triển khai cần phải tuân theo
Thành phần điều hướng
Đánh số trang
Thư viện phân trang được
sử dụng trong giao diện người dùng với một loại lệnh gọi PagingData
. Vì PagingData
đại diện và chứa các mục có thể thay đổi theo thời gian. Nói cách khác, thuộc tính này không phải là một loại bất biến nên không nên được
biểu thị ở trạng thái giao diện người dùng bất biến.
Thay vào đó, bạn nên hiển thị giao diện từ ViewModel một cách độc lập trong
trình phát trực tiếp riêng của nó. Hãy xem lớp học lập trình Đánh số trang Android để biết
ví dụ cụ thể về nội dung này.
Ả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 đó. Để biết thêm thông tin chi tiết và các thông số kỹ thuật triển khai cụ thể, hãy xem Mẫu Chuyển động dành cho Android.
Mẫu
Các mẫu sau đây của Google minh hoạ cách sử dụng lớp giao diện người dùng. Hãy khám phá những mẫu đó để xem hướng dẫn này trong thực tế:
Đề 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