單向數(shù)據流 (UDF) 可(kě)作(zuò)為(wèi)為(wèi)界面層提供和(hé)管理界面狀态的方式,界面層指南介紹了(le)這(zhè)種方式。
此外(wài),該指南還重點介紹了(le)将 UDF 管理委托給名為(wèi)狀态容器的特殊類的好處。您可(kě)以通(tōng)過 ViewModel 或普通(tōng)類實現狀态容器。本文檔詳細介紹了(le)狀态容器及其在界面層中的作(zuò)用。
學完本文檔後,您應了(le)解如(rú)何在界面層中管理應用狀态;這(zhè)就是界面狀态生成流水線。您應該能(néng)夠了(le)解和(hé)掌握以下(xià)內(nèi)容:
了(le)解界面層中存在的界面狀态類型。
了(le)解在界面層中對這(zhè)些界面狀态執行(xíng)的邏輯類型。
知道(dào)如(rú)何選擇合适的狀态容器實現方式,例如(rú) ViewModel 或簡單類。
界面狀态生成流水線的元素
界面狀态以及生成該狀态的邏輯定義了(le)界面層。
界面狀态
界面狀态是描述界面的屬性。界面狀态有兩種類型:
屏幕界面狀态是需要(yào)在屏幕上(shàng)顯示的內(nèi)容。例如(rú),NewsUiState 類可(kě)以包含呈現界面所需的新聞報道(dào)和(hé)其他(tā)信息。由于該狀态包含應用數(shù)據,因此通(tōng)常會與層次結構中的其他(tā)層相關聯。
界面元素狀态是指界面元素的固有屬性,這(zhè)些屬性會影響界面元素的呈現方式。界面元素可(kě)能(néng)處于顯示或隐藏狀态,并且可(kě)能(néng)具有特定的字體、字體大小或字體顔色。在 Android View 中,View 會自(zì)行(xíng)管理此狀态(因為(wèi)它本身是有狀态的),并公開(kāi)用于修改或查詢其狀态的方法。例如(rú),TextView 類的 get 和(hé) set 方法用于顯示該類的文本。在 Jetpack Compose 中,狀态在可(kě)組合項之外(wài),您甚至可(kě)以将狀态從(cóng)可(kě)組合項附近提升到執行(xíng)調用的可(kě)組合函數(shù)或狀态容器中。例如(rú),Scaffold 可(kě)組合項的 ScaffoldState。
邏輯
界面狀态不是靜态屬性,因為(wèi)應用數(shù)據和(hé)用戶事件會導緻界面狀态随時(shí)間(jiān)而變化。邏輯決定了(le)變化的具體細節,包括界面狀态的哪些部分發生了(le)變化、為(wèi)什麽發生變化以及應該在何時(shí)發生變化。
應用中的邏輯可(kě)以是業務邏輯或界面邏輯:
業務邏輯決定着應用數(shù)據的産品要(yào)求的實現。例如(rú),在新聞閱讀器應用中,當用戶點按相應按鈕時(shí),就會為(wèi)報道(dào)添加書(shū)簽。這(zhè)種用于将書(shū)簽保存到文件或數(shù)據庫的邏輯通(tōng)常放置在領域或數(shù)據層中。狀态容器通(tōng)常通(tōng)過調用這(zhè)類層公開(kāi)的方法,将此邏輯委托給相應的層。
界面邏輯決定着如(rú)何在屏幕上(shàng)顯示界面狀态。例如(rú),在用戶選擇了(le)某個類别時(shí)獲取正确的搜索欄提示、滾動至列表中的特定項,或者在用戶點擊某按鈕時(shí)便進入特定屏幕的導航邏輯。
Android 生命周期以及界面狀态和(hé)邏輯的類型
界面層包含兩個部分:一(yī)部分依賴于界面生命周期,另一(yī)部分不依賴于界面生命周期。這(zhè)種分離決定了(le)每個部分可(kě)用的數(shù)據源,因此需要(yào)不同類型的界面狀态和(hé)邏輯。
不依賴于界面生命周期:界面層的這(zhè)一(yī)部分用于處理應用的數(shù)據生成層(數(shù)據層或網域層),由業務邏輯定義。界面中的生命周期、配置更改和(hé) Activity 重新創建可(kě)能(néng)會影響界面狀态生成流水線是否處于活動狀态,但(dàn)不會影響生成的數(shù)據的有效性。
依賴于界面生命周期:界面層的這(zhè)一(yī)部分用于處理界面邏輯,受生命周期或配置更改的直接影響。這(zhè)些更改會直接影響從(cóng)中讀取數(shù)據的來源的有效性,因此其狀态隻會在其生命周期處于活動狀态時(shí)發生變化。例如(rú)運行(xíng)時(shí)權限,以及獲取依賴于配置的資源(例如(rú)本地(dì)化字符串)。
界面狀态生成流水線
界面狀态生成流水線是指為(wèi)生成界面狀态而執行(xíng)的步驟。相關步驟包括應用上(shàng)文定義的各類邏輯,并且完全取決于界面的需求。有些界面可(kě)能(néng)會受益于流水線中不依賴于界面生命周期的部分和(hé)/或依賴于界面生命周期的部分,也可(kě)能(néng)不會受益于其中任一(yī)部分。
也就是說,界面層流水線的以下(xià)排列是有效的:
由界面本身生成和(hé)管理的界面狀态。例如(rú),一(yī)個簡單且可(kě)重複使用的基本計數(shù)器:
@Composable
fun Counter() {
// The UI state is managed by the UI itself
var count by remember { mutableStateOf(0) }
Row {
Button(onClick = { ++count }) {
Text(text = "Increment")
}
Button(onClick = { --count }) {
Text(text = "Decrement")
}
}
}
界面邏輯 → 界面。例如(rú),顯示或隐藏允許用戶跳(tiào)轉到列表頂部的按鈕。
@Composable
fun ContactsList(contacts: List
val listState = rememberLazyListState()
val isAtTopOfList by remember {
derivedStateOf {
listState.firstVisibleItemIndex < 3
}
}
// Create the LazyColumn with the lazyListState
...
// Show or hide the button (UI logic) based on the list scroll position
AnimatedVisibility(visible = !isAtTopOfList) {
ScrollToTopButton()
}
}
業務邏輯 → 界面。在屏幕上(shàng)展示當前用戶的照片的界面元素。
@Composable
fun UserProfileScreen(viewModel: UserProfileViewModel = hiltViewModel()) {
// Read screen UI state from the business logic state holder
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Call on the UserAvatar Composable to display the photo
UserAvatar(picture = uiState.profilePicture)
}
業務邏輯 → 界面邏輯 → 界面。會針對給定界面狀态在屏幕上(shàng)滾動以顯示正确信息的界面元素。
@Composable
fun ContactsList(viewModel: ContactsViewModel = hiltViewModel()) {
// Read screen UI state from the business logic state holder
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val contacts = uiState.contacts
val deepLinkedContact = uiState.deepLinkedContact
val listState = rememberLazyListState()
// Create the LazyColumn with the lazyListState
...
// Perform UI logic that depends on information from business logic
if (deepLinkedContact != null && contacts.isNotEmpty()) {
LaunchedEffect(listState, deepLinkedContact, contacts) {
val deepLinkedContactIndex = contacts.indexOf(deepLinkedContact)
if (deepLinkedContactIndex >= 0) {
// Scroll to deep linked item
listState.animateScrollToItem(deepLinkedContactIndex)
}
}
}
}
如(rú)果将這(zhè)兩種邏輯都(dōu)應用于界面狀态生成流水線,則必須始終先應用業務邏輯,然後再應用界面邏輯。如(rú)果嘗試先應用界面邏輯,再應用業務邏輯,則意味着業務邏輯依賴于界面邏輯。
狀态容器及其責任
狀态容器的責任是存儲狀态,以便應用讀取狀态。 在需要(yào)邏輯時(shí),它會充當中介,并提供對托管所需邏輯的數(shù)據源的訪問(wèn)權限。這(zhè)樣,狀态容器就會将邏輯委托給相應的數(shù)據源。
這(zhè)會帶來以下(xià)好處:
簡單的界面:界面僅綁定了(le)其狀态。
可(kě)維護性:可(kě)以對狀态容器中定義的邏輯進行(xíng)叠代,而無需更改界面本身。
可(kě)測試性:界面及其狀态生成邏輯可(kě)獨立進行(xíng)測試。
可(kě)讀性:代碼讀者可(kě)以清楚地(dì)看(kàn)出界面呈現代碼與界面狀态生成代碼之間(jiān)的差異。
無論大小或作(zuò)用域如(rú)何,每個界面元素都(dōu)與其對應的狀态容器具有 1 對 1 關系。此外(wài),狀态容器必須能(néng)夠接受和(hé)處理任何可(kě)能(néng)導緻界面狀态發生變化的用戶操作(zuò),并且必須生成随後的狀态變化。
注意:狀态容器并非絕對必要(yào)。簡單的界面可(kě)能(néng)會托管內(nèi)嵌到其呈現代碼中的邏輯。
狀态容器的類型
與界面狀态和(hé)邏輯的類型類似,界面層中有兩種類型的狀态容器,它們根據自(zì)身與界面生命周期的關系而定義:
業務邏輯狀态容器。
界面邏輯狀态容器。
以下(xià)幾個部分更詳細地(dì)介紹了(le)狀态容器的類型,首先講的就是業務邏輯狀态容器。
注意:如(rú)果界面邏輯狀态容器依賴于數(shù)據層或網域層中的信息,您應從(cóng)業務邏輯狀态容器向界面邏輯狀态容器傳遞該信息。這(zhè)是因為(wèi)與界面邏輯狀态容器相比,業務邏輯狀态容器的存在期更長(cháng),原因是後者不依賴于界面生命周期。
業務邏輯及其狀态容器
業務邏輯狀态容器會處理用戶事件,并将數(shù)據從(cóng)數(shù)據層或網域層轉換為(wèi)屏幕界面狀态。
将 ViewModel 用作(zuò)業務邏輯狀态容器
ViewModel 在 Android 開(kāi)發中的優勢使其适用于提供對業務邏輯的訪問(wèn)權限以及準備要(yào)在屏幕上(shàng)呈現的應用數(shù)據。這(zhè)些優勢包括如(rú)下(xià)各項:
ViewModel 觸發的操作(zuò)在配置發生變化後仍然有效。
與 Navigation 集成:
當屏幕位于返回堆棧中時(shí),Navigation 會緩存 ViewModel。這(zhè)對在返回目标位置時(shí)即時(shí)提供之前加載的數(shù)據非常重要(yào)。使用遵循可(kě)組合項屏幕的生命周期的狀态容器時(shí),這(zhè)種情況會更難處理。
當目标位置從(cóng)返回堆棧彈出後,ViewModel 也會被一(yī)并清除,以确保自(zì)動清理狀态。這(zhè)不同于監聽可(kě)組合項的處理,監聽的原因可(kě)能(néng)有多種,例如(rú)轉到新屏幕、配置發生變化等。
與其他(tā) Jetpack 庫(如(rú) Hilt)集成。
注意:如(rú)果 ViewModel 的優勢不适用于您的用例,或者您以其他(tā)方式執行(xíng)操作(zuò),則可(kě)以将 ViewModel 的責任轉移到對普通(tōng)狀态容器類中。
界面邏輯及其狀态容器
界面邏輯是對界面本身提供的數(shù)據執行(xíng)操作(zuò)的邏輯。它可(kě)能(néng)依賴于界面元素的狀态或界面數(shù)據源(如(rú)權限 API 或 Resources)。利用界面邏輯的狀态容器通(tōng)常具有以下(xià)屬性:
生成界面狀态并管理界面元素狀态。
在 Activity 重新創建後不再有效:托管在界面邏輯中的狀态容器通(tōng)常依賴于界面本身的數(shù)據源,并且在很(hěn)多情況下(xià),嘗試在配置發生變化後保留此信息會導緻內(nèi)存洩漏。如(rú)果狀态容器需要(yào)數(shù)據在配置發生變化後保持不變,則需要(yào)将其委托給更适合在 Activity 重新創建後繼續留存的其他(tā)組件。例如(rú),在 Jetpack Compose 中,使用 remembered 函數(shù)創建的可(kě)組合界面元素狀态通(tōng)常會委托給 rememberSaveable,以便在 Activity 重新創建後保留狀态。此類函數(shù)的示例包括 rememberScaffoldState() 和(hé) rememberLazyListState()。
引用了(le)界面範圍的數(shù)據源:生命周期 API 和(hé)資源等數(shù)據源可(kě)以安全地(dì)引用和(hé)讀取,因為(wèi)界面邏輯狀态容器與界面具有相同的生命周期。
可(kě)在多個不同的界面中重複使用:同一(yī)界面邏輯狀态容器的不同實例可(kě)以在應用的不同部分中重複使用。例如(rú),用于管理條狀标簽組的用戶輸入事件的狀态容器可(kě)用在過濾條件塊的搜索頁上(shàng),也可(kě)以用于表示電子郵件接收者的“收件人(rén)”字段。
界面邏輯狀态容器通(tōng)常使用普通(tōng)類實現。這(zhè)是因為(wèi)界面本身負責創建界面邏輯狀态容器,而界面邏輯狀态容器與界面本身具有相同的生命周期。例如(rú),在 Jetpack Compose 中,狀态容器是組合的一(yī)部分,并遵循組合的生命周期。
注意:當界面邏輯足夠複雜(zá),可(kě)以移出界面時(shí),會使用普通(tōng)類狀态容器。否則,界面邏輯可(kě)以在界面中以內(nèi)嵌方式實現。
Now in Android 示例會根據設備的屏幕大小來顯示用于導航的底部應用欄或導航欄。較小的屏幕使用底部應用欄,較大的屏幕則使用導航欄。
由于決定 NiaApp 可(kě)組合函數(shù)中使用的适當導航界面元素的邏輯不依賴于業務邏輯,因此可(kě)以通(tōng)過名稱為(wèi) NiaAppState 的普通(tōng)類狀态容器來管理:
@Stable
class NiaAppState(
val navController: NavHostController,
val windowSizeClass: WindowSizeClass
) {
// UI logic
val shouldShowBottomBar: Boolean
get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
// UI logic
val shouldShowNavRail: Boolean
get() = !shouldShowBottomBar
// UI State
val currentDestination: NavDestination?
@Composable get() = navController
.currentBackStackEntryAsState().value?.destination
// UI logic
fun navigate(destination: NiaNavigationDestination, route: String? = null) { /* ... */ }
/* ... */
}
在上(shàng)面的示例中,關于 NiaAppState 的以下(xià)詳細信息值得注意:
在 Activity 重新創建後不再有效:通(tōng)過使用遵循 Compose 命名慣例的可(kě)組合函數(shù) rememberNiaAppState 創建 NiaAppState,在組合中 remembered 了(le)該容器。重新創建 Activity 後,之前的實例會丢失,并會使用傳入的所有依賴項(适用于重新創建的 Activity 的新配置)創建一(yī)個新實例。這(zhè)些依賴項可(kě)能(néng)是新的,也可(kě)能(néng)是根據以前的配置恢複的。例如(rú),NiaAppState 構造函數(shù)中使用了(le) rememberNavController(),後者會委托給 rememberSaveable 以在重新創建 Activity 的過程中保留狀态。
引用了(le)界面範圍的數(shù)據源:對 navigationController、Resources 和(hé)其他(tā)類似生命周期範圍的類型的引用可(kě)以安全地(dì)保存在 NiaAppState 中,因為(wèi)它們具有相同的生命周期作(zuò)用域。
注意:建議(yì)為(wèi)可(kě)重用的界面部分(如(rú)搜索欄或條狀标簽組)使用普通(tōng)狀态容器類。在這(zhè)種情況下(xià),您不應使用 ViewModel,因為(wèi) ViewModel 最适合用于管理導航目的地(dì)的狀态和(hé)對業務邏輯的訪問(wèn)權限。
為(wèi)狀态容器選擇 ViewModel 和(hé)普通(tōng)類
在上(shàng)面幾部分中,選擇 ViewModel 還是普通(tōng)類狀态容器取決于對界面狀态應用的邏輯以及執行(xíng)該邏輯的數(shù)據源。
注意:大多數(shù)應用會選擇執行(xíng)內(nèi)嵌在界面本身中的界面邏輯,而這(zhè)些邏輯原本可(kě)以放在普通(tōng)類狀态容器中。這(zhè)适用于簡單的情況,但(dàn)在其他(tā)情況下(xià),您可(kě)以通(tōng)過将邏輯拉取到普通(tōng)類狀态容器中來提高可(kě)讀性
狀态容器可(kě)組合
狀态容器可(kě)以依賴于另一(yī)個狀态容器,前提是依賴項的生命周期與狀态容器相同或更短。示例如(rú)下(xià):
界面邏輯狀态容器可(kě)以依賴于另一(yī)個界面邏輯狀态容器。
屏幕級狀态容器可(kě)以依賴于界面邏輯狀态容器。
以下(xià)代碼段展示了(le) Compose 的 DrawerState 如(rú)何依賴于另一(yī)個內(nèi)部狀态容器,即 SwipeableState;還展示了(le)應用的界面邏輯狀态容器可(kě)以如(rú)何依賴于 DrawerState:
@Stable
class DrawerState(/* ... */) {
internal val swipeableState = SwipeableState(/* ... */)
// ...
}
@Stable
class MyAppState(
private val drawerState: DrawerState,
private val navController: NavHostController
) { /* ... */ }
@Composable
fun rememberMyAppState(
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
navController: NavHostController = rememberNavController()
): MyAppState = remember(drawerState, navController) {
MyAppState(drawerState, navController)
}
注意:鑒于屏幕級狀态容器管理部分或整個屏幕的業務邏輯複雜(zá)性,因此讓屏幕級狀态容器依賴于另一(yī)個屏幕級狀态容器的做(zuò)法并不合理。如(rú)果您遇到這(zhè)種情況,請(qǐng)重新考慮相關屏幕和(hé)狀态容器,确定您是否真的需要(yào)這(zhè)樣做(zuò)。
舉例來說,如(rú)果界面邏輯狀态容器依賴于屏幕級狀态容器,那(nà)麽依賴項的生命周期就比狀态容器更長(cháng)。這(zhè)會降低(dī)生命周期較短的狀态容器的可(kě)重用性,并使其能(néng)夠訪問(wèn)超出實際需要(yào)的邏輯和(hé)狀态。
如(rú)果生命周期較短的狀态容器需要(yào)來自(zì)較高層級範圍的狀态容器的某些信息,請(qǐng)僅将它需要(yào)的信息作(zuò)為(wèi)參數(shù)傳遞,而不是傳遞狀态容器實例。例如(rú),在以下(xià)代碼段中,界面邏輯狀态容器類僅從(cóng) ViewModel 接收所需信息,而不是将整個 ViewModel 實例作(zuò)為(wèi)依賴項傳遞。
網站建設開(kāi)發|APP設計開(kāi)發|小程序建設開(kāi)發