UI 的作用是在螢幕上顯示應用程式資料,並提供使用者與系統互動的主要接觸方式。只要資料因使用者互動 (例如按下按鈕) 或外部輸入 (例如網路回應) 而改變,UI 都應更新以反映變更。實際上,UI 是用視覺方式呈現從資料層擷取的應用程式狀態。
不過,從資料層取得的應用程式資料通常與需要顯示資訊的格式不同。舉例來說,UI 可能只需要使用部分資料,或者可能需要合併兩個不同的資料來源,以顯示與使用者相關的資訊。無論套用何種邏輯,您都必須傳遞所有所需資訊至 UI,才能完整轉譯所需的所有資訊。UI 層是一條管道,可以轉換應用程式的資料變更形式,讓 UI 可以呈現並顯示出來。
公開 UI 狀態
在定義 UI 狀態並決定如何管理狀態產生方式之後,下一步就是向 UI 顯示已產生的狀態。由於您使用 UDF 管理狀態的產生,因此您可以將產生狀態視為串流,換句話說,系統會隨著時間產生的多個狀態版本。因此,您應在可觀測資料容器 (例如 LiveData 或 StateFlow) 中顯示 UI 狀態。如此一來,UI 會回應狀態發生的所有變更,而不必手動直接從 ViewModel 提取資料。上述類型也具備始終快取最新版 UI 狀態的好處,在設定變更後,該設定相當適合用於快速還原狀態。
class NewsViewModel(...) : ViewModel() {
val uiState: StateFlow<NewsUiState> = …
}
建立 UiState 串流的其中一種常見方式,就是將支援的可變更串流顯示為 ViewModel 中不可變更的串流,例如將 MutableStateFlow<UiState> 顯示為 StateFlow<UiState>。
class NewsViewModel(...) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
...
}
然後 ViewModel 可以顯示內部變更狀態的方法,發布更新以供 UI 使用。舉例來說,如果需要執行非同步動作,系統可以使用 viewModelScope 啟動協同程式,並在完成作業時更新可變動狀態。
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)
}
}
}
}
}
取用 UI 狀態
在 UI 中使用可觀察資料持有者時,請務必考量 UI 的生命週期。這個過程相當重要,因為 UI 不應在向使用者顯示檢視畫面時觀測 UI 狀態。如要進一步瞭解這個主題,請參閱這篇網誌文章。使用 LiveData 時,LifecycleOwner 會以隱含形式處理生命週期問題。使用流程時,建議您使用適當的協同程式範圍和 repeatOnLifecycle API 以處理這種情況:
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
}
}
}
}
}
顯示進行中的作業
表示 UiState 類別中載入狀態的簡單方法,是使用布林值欄位:
data class NewsUiState(
val isFetchingArticles: Boolean = false,
...
)
此標記值代表 UI 中的進度列是否存在。
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 }
}
}
}
}
動畫
為了提供流暢且順暢的頂層導覽轉換,您可能需要等到第二個畫面載入資料後,再開始執行動畫。Android 檢視架構提供掛鉤,可延遲 postponeEnterTransition() 和 startPostponedEnterTransition() API 片段目的地之間的轉換。這些 API 可讓您確保第二個畫面的 UI 元素 (通常是從網路擷取的圖片) 在 UI 以動畫呈現到該螢幕之前,已經準備好顯示。
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 產生 UI 狀態
- 狀態容器和 UI 狀態 {:#mad-arch}
- 應用程式架構指南