“界面事件”是指應由界面或 ViewModel 在界面層處理的操作(zuò)。最常見(jiàn)的事件類型是“用戶事件”。用戶通(tōng)過與應用互動(例如(rú),點按屏幕或生成手勢)來生成用戶事件。随後,界面會使用 onClick() 監聽器等回調來使用這(zhè)些事件。
關鍵術(shù)語:
界面:用于處理界面的基于視(shì)圖的代碼或 Compose 代碼。
界面事件:應在界面層處理的操作(zuò)。
用戶事件:用戶在與應用互動時(shí)生成的事件。
ViewModel 通(tōng)常負責處理特定用戶事件的業務邏輯。例如(rú),用戶點擊某個按鈕以刷新部分數(shù)據。ViewModel 通(tōng)常通(tōng)過公開(kāi)界面可(kě)以調用的函數(shù)來處理這(zhè)種情況。用戶事件可(kě)能(néng)還有界面可(kě)以直接處理的界面行(xíng)為(wèi)邏輯。例如(rú)轉到其他(tā)屏幕或顯示 Snackbar。
雖然同一(yī)應用的業務邏輯在不同移動平台或設備類型上(shàng)保持不變,但(dàn)界面行(xíng)為(wèi)邏輯在實現方面可(kě)能(néng)有所不同。界面層頁定義了(le)這(zhè)些類型的邏輯,如(rú)下(xià)所示:
業務邏輯是指如(rú)何處理狀态更改,例如(rú)付款或存儲用戶偏好設置。網域和(hé)數(shù)據層通(tōng)常負責處理此邏輯。在本指南中,架構組件 ViewModel 類用作(zuò)處理業務邏輯的類的特色解決方案。
界面行(xíng)為(wèi)邏輯(即界面邏輯)是指如(rú)何顯示狀态更改,例如(rú)導航邏輯或如(rú)何向用戶顯示消息。界面會處理此邏輯。
注意:本頁中提供的建議(yì)和(hé)最佳實踐可(kě)應用于廣泛的應用。遵循這(zhè)些建議(yì)和(hé)最佳實踐可(kě)以提升應用的可(kě)擴展性、質量和(hé)穩健性,并使應用更易于測試。不過,您應該将這(zhè)些提示視(shì)為(wèi)指南,并視(shì)需要(yào)進行(xíng)調整來滿足您的要(yào)求。
界面事件決策樹(shù)
下(xià)圖這(zhè)個決策樹(shù)展示了(le)如(rú)何尋找處理特定事件使用場景的最佳實踐。本指南的其餘部分将詳細介紹這(zhè)些方法。
處理用戶事件
如(rú)果用戶事件與修改界面元素的狀态(如(rú)可(kě)展開(kāi)項的狀态)相關,界面便可(kě)以直接處理這(zhè)些事件。如(rú)果事件需要(yào)執行(xíng)業務邏輯(如(rú)刷新屏幕上(shàng)的數(shù)據),則應用由 ViewModel 處理此事件。
RecyclerView 中的用戶事件
如(rú)果操作(zuò)是在界面樹(shù)中比較靠下(xià)一(yī)層生成的,例如(rú)在 RecyclerView 項或自(zì)定義 View 中,ViewModel 應仍是處理用戶事件的操作(zuò)。
例如(rú),假設 NewsActivity 中的所有新聞項都(dōu)包含一(yī)個書(shū)簽按鈕。ViewModel 需要(yào)知道(dào)已添加書(shū)簽的新聞項目的 ID。當用戶為(wèi)新聞內(nèi)容添加書(shū)簽時(shí),RecyclerView 适配器不會調用 ViewModel 中已公開(kāi)的 addBookmark(newsId) 函數(shù),該函數(shù)需要(yào)一(yī)個對 ViewModel 的依賴項。取而代之的是,ViewModel 會公開(kāi)一(yī)個名為(wèi) NewsItemUiState 的狀态對象,其中包含用于處理事件的實現:
data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
val publicationDate: String,
val onBookmark: () -> Unit
)
class LatestNewsViewModel(
private val formatDateUseCase: FormatDateUseCase,
private val repository: NewsRepository
)
val newsListUiItems = repository.latestNews.map { news ->
NewsItemUiState(
title = news.title,
body = news.body,
bookmarked = news.bookmarked,
publicationDate = formatDateUseCase(news.publicationDate),
// Business logic is passed as a lambda function that the
// UI calls on click events.
onBookmark = {
repository.addBookmark(news.id)
}
)
}
}
這(zhè)樣,RecyclerView 适配器就會僅使用它需要(yào)的數(shù)據:NewsItemUiState 對象列表。該适配器無法訪問(wèn)整個 ViewModel,因此不太可(kě)能(néng)濫用 ViewModel 公開(kāi)的功能(néng)。如(rú)果僅允許 activity 類使用 ViewModel,即表示職責已分開(kāi)。這(zhè)樣可(kě)确保界面專屬對象(如(rú)視(shì)圖或 RecyclerView 适配器)不會直接與 ViewModel 互動。
警告:将 ViewModel 傳入 RecyclerView 适配器是一(yī)種不妥的做(zuò)法,因為(wèi)它會将适配器與 ViewModel 類緊密耦合。
注意:另一(yī)種常見(jiàn)方案是讓 RecyclerView 适配器具有用于用戶操作(zuò)的 Callback 接口。在這(zhè)種情況下(xià),activity 或 fragment 可(kě)以處理綁定,并直接從(cóng)回調接口調用 ViewModel 函數(shù)。
用戶事件函數(shù)的命名慣例
在本指南中,用于處理用戶事件的 ViewModel 函數(shù)根據其處理的操作(zuò)(例如(rú),addBookmark(id) 或 logIn(username, password))以動詞命名。
處理 ViewModel 事件
源自(zì) ViewModel 的界面操作(zuò)(ViewModel 事件)應始終引發界面狀态更新。這(zhè)符合單向數(shù)據流的原則。讓事件在配置更改後可(kě)重現,并保證界面操作(zuò)不會丢失。如(rú)果您使用已保存的狀态模塊,則還可(kě)以讓事件在進程終止後可(kě)重現(可(kě)選操作(zuò))。
将界面操作(zuò)映射到界面狀态并不總是一(yī)個簡單的過程,但(dàn)确實可(kě)以簡化邏輯。例如(rú),您不單單要(yào)想辦法确定如(rú)何将界面導航到特定屏幕,還需要(yào)進一(yī)步思考如(rú)何在界面狀态中表示該用戶流。換句話說:不需要(yào)考慮界面需要(yào)執行(xíng)哪些操作(zuò),而是要(yào)思考這(zhè)些操作(zuò)會對界面狀态造成什麽影響。
要(yào)點:ViewModel 事件應始終會引發界面狀态更新。
例如(rú),要(yào)考慮在用戶登錄時(shí)從(cóng)登錄屏幕切換到主屏幕的情況。您可(kě)以在界面狀态中進行(xíng)如(rú)下(xià)建模:
data class LoginUiState(
val isLoading: Boolean = false,
val errorMessage: String? = null,
val isUserLoggedIn: Boolean = false
)
使用事件可(kě)能(néng)會觸發狀态更新
使用界面中的某些 ViewModel 事件可(kě)能(néng)會引發其他(tā)界面狀态更新。例如(rú),當屏幕上(shàng)顯示瞬時(shí)消息以告知用戶發生的情況時(shí),界面需要(yào)通(tōng)知 ViewModel 以在消息顯示于屏幕上(shàng)時(shí)觸發另一(yī)狀态更新。用戶處理消息(通(tōng)過關閉消息或超時(shí))後發生的事件可(kě)被視(shì)為(wèi)“用戶輸入”,因此 ViewModel 應該知道(dào)這(zhè)一(yī)點。在這(zhè)種情況下(xià),界面狀态可(kě)按以下(xià)方式建模:
// Models the UI state for the Latest news screen.
data class LatestNewsUiState(
val news: List
val isLoading: Boolean = false,
val userMessage: String? = null
)
導航事件
使用事件可(kě)能(néng)會觸發狀态更新部分詳細介紹了(le)如(rú)何使用界面狀态在屏幕上(shàng)顯示用戶消息。導航事件也是 Android 應用中的一(yī)種常見(jiàn)事件類型。
如(rú)果因用戶點按某個按鈕而在界面中觸發了(le)該事件,界面便會通(tōng)過以下(xià)方式處理該事件:調用導航控制器,或将該事件公開(kāi)給調用方可(kě)組合項。
目的地(dì)保留在返回堆棧中時(shí)的導航事件
如(rú)果 ViewModel 設置了(le)某種狀态,使其生成從(cóng)屏幕 A 到屏幕 B 的導航事件,并且屏幕 A 保留在導航返回堆棧中,您可(kě)能(néng)需要(yào)其他(tā)邏輯,以免繼續自(zì)動進入屏幕 B。為(wèi)實現這(zhè)一(yī)點,您必須設置其他(tā)狀态,以指示界面是否應該考慮前往其他(tā)屏幕。通(tōng)常,該狀态會保留在界面中,因為(wèi)導航邏輯與界面有關,而與 ViewModel 無關。為(wèi)了(le)說明(míng)這(zhè)一(yī)點,我們來看(kàn)以下(xià)用例。
假設您已進入應用的注冊流程。在“出生日期”驗證屏幕中,如(rú)果用戶輸入某個日期,當用戶點按“繼續”按鈕時(shí),ViewModel 會驗證該日期。ViewModel 會将相應驗證邏輯委托給數(shù)據層。如(rú)果日期有效,用戶會進入下(xià)一(yī)個屏幕。作(zuò)為(wèi)一(yī)項額外(wài)功能(néng),用戶可(kě)以在不同的注冊屏幕之間(jiān)來回切換,以便在想要(yào)更改某些數(shù)據時(shí)能(néng)夠進行(xíng)所需的操作(zuò)。因此,注冊流程中的所有目的地(dì)都(dōu)保留在同一(yī)個返回堆棧中。
其他(tā)用例
如(rú)果您認為(wèi)界面事件用例無法通(tōng)過界面狀态更新得以解決,可(kě)能(néng)需要(yào)重新考慮數(shù)據在應用中的流動方式。請(qǐng)考慮以下(xià)原則:
每個類都(dōu)應各司其職,不能(néng)越界。界面負責屏幕專屬行(xíng)為(wèi)邏輯,例如(rú)導航調用、點擊事件以及獲取權限請(qǐng)求。ViewModel 包含業務邏輯,并将結果從(cóng)層次結構的較低(dī)層轉換為(wèi)界面狀态。
考慮事件的發起點。請(qǐng)遵循本指南開(kāi)頭介紹的決策樹(shù),并讓每個類各司其職。例如(rú),如(rú)果事件源自(zì)界面并導緻出現導航事件,則必須在界面中處理該事件。某些邏輯可(kě)能(néng)會委托給 ViewModel,但(dàn)事件的處理無法完全委托給 ViewModel。
如(rú)果事件有多個使用方,則當您對某個事件會被使用多次而感到擔憂時(shí),可(kě)能(néng)需要(yào)重新考慮您的應用架構。 同時(shí)有多個使用方會導緻“恰好交付一(yī)次”協定變得非常難以保證,因此複雜(zá)性和(hé)細微(wēi)行(xíng)為(wèi)的數(shù)量也會急劇增加。如(rú)果您遇到此問(wèn)題,不妨考慮提升這(zhè)些問(wèn)題在界面樹(shù)上(shàng)的層級;您可(kě)能(néng)需要(yào)在層次結構中較高層級設定其他(tā)實體。
考慮何時(shí)需要(yào)使用狀态。在某些情況下(xià),您可(kě)能(néng)不想在應用處于後台時(shí)保留使用狀态(例如(rú)顯示 Toast)。在這(zhè)些情況下(xià),請(qǐng)考慮在界面位于前台時(shí)使用狀态。
網站建設開(kāi)發|APP設計開(kāi)發|小程序建設開(kāi)發