△Jetsnack 應(yīng)用屏幕截圖
在 Compose 中使用 State
Jetsnack 是一款使用 Compose 構(gòu)建的小吃訂購示例應(yīng)用,狀態(tài)對它來說非常重要。比如,在一屏中顯示哪些商品、顯示用戶篩選的小吃以及記錄購物車等操作,都需要狀態(tài)的支持。我們將 Compose 構(gòu)建的界面稱之為組合 (Composition),它會在屏幕中呈現(xiàn)應(yīng)用的當(dāng)前狀態(tài)。下圖直觀地展示了組合在視覺上呈現(xiàn)搜索頁的過程,您可以在其中找到搜索欄 (SearchBar)、分隔線 (JetsnackDivider) 和搜索建議 (SearchSuggestions),這些都是搜索界面的組成部分:
△組合呈現(xiàn)搜索界面的過程
在像 Compose 這樣的聲明式框架中,您只需描述應(yīng)用的當(dāng)前狀態(tài),Compose 會負(fù)責(zé)在狀態(tài)發(fā)生更改時更新界面。因此,當(dāng)我們導(dǎo)航到購物車屏幕時,Compose 也會重新執(zhí)行受狀態(tài)更改影響的部分界面。下圖中,NavHost 更新為顯示購物車界面。由于界面的每個部分都是一個可組合項,當(dāng)狀態(tài)更改時,這些函數(shù)會進行重組,以便在屏幕上顯示新數(shù)據(jù):
△組合呈現(xiàn)購物車界面的過程
在購物車界面中,我們重點關(guān)注單獨的購物車商品項。該元素用于顯示購物車中的商品,并允許您更改數(shù)量:
△單獨的購物車商品
我們可以使用包含兩個 Button 和一個 Text 的 Row 來構(gòu)建該界面,但是要如何記錄購物車中商品的當(dāng)前數(shù)量呢?
我們可以簡單地在可組合函數(shù)中添加一個可變變量 quantity,但接下來您會發(fā)現(xiàn),在我們通過點按增加和減少數(shù)量的按鈕來修改此變量的值時,界面中顯示的數(shù)量沒有發(fā)生任何變化——當(dāng)狀態(tài)更改時,該函數(shù)沒有重新組合。這是因為 Compose 不會跟蹤 quantity 變量。fun CartItem() {
var quantity = ... ˉ\_(ッ)_/ˉ ... ?
Row {
Button(onClick = { quantity++ }) {
Text("+")
}
Text(quantity.toString())
Button(onClick = { quantity-- }) {
Text("-")
}
}
}
△數(shù)量的顯示沒有發(fā)生改變fun CartItem() {
var quantity: Int = 1
...
}
Compose 具有特殊的狀態(tài)跟蹤系統(tǒng),可以在某個狀態(tài)改變時,重組讀取該狀態(tài)的所有可組合項。這種機制使得 Compose 可以對界面進行精細(xì)控制,在狀態(tài)發(fā)生改變時不用修改整個界面,只需重組需要更改的可組合函數(shù)即可。
這一功能是通過跟蹤狀態(tài)寫入 (即狀態(tài)更改) 以及狀態(tài)讀取來實現(xiàn)的,我們可以使用 Compose 的 State 和 MutableState 類型使?fàn)顟B(tài)可被觀察。Compose 會跟蹤讀取 State 中 value 屬性的可組合項,并在 value 發(fā)生更改時觸發(fā)重新組合。
// State
interface State<out T> {
val value: T
}
// MutableState
interface MutableState<T> : State<T> {
override var value: T
}
您可以使用 mutableStateOf 函數(shù)創(chuàng)建 MutableState,該函數(shù)需要接收一個初始值,并且它的 value 是可變的。相應(yīng)的,我們需要改用 value 屬性來讀取和寫入 quantity 狀態(tài):
fun CartItem() {
val quantity: MutableState<Int> = mutableStateOf(1)
Row {
Button(onClick = { quantity.value++ }) {
Text("+")
}
Text(quantity.value.toString())
Button(onClick = { quantity.value-- }) {
Text("-")
}
}
}
但是,即使 Compose 已經(jīng)跟蹤了 quantity 變量,并觸發(fā)了重組,您會發(fā)現(xiàn)界面依然沒有顯示狀態(tài)的更改。問題在于,雖然該函數(shù)已經(jīng)重組,但 quantity 的值 value 總是會被初始化為 1。這是一個常見的錯誤,因此您在嘗試編寫這段代碼時也會產(chǎn)生編譯錯誤。為了在重組中重用 quantity 狀態(tài),我們需要使其成為組合的一部分。要做到這一點,可以使用 remember 可組合函數(shù)將對象存儲在組合中:
fun CartItem() {
val quantity = remember { mutableStateOf(1) }
...
}
Remember 可用于存儲可變對象和不可變對象,您必須對在組合中創(chuàng)建的 State,也就是可組合函數(shù)中的 State 執(zhí)行 remember 操作。在被記住后,狀態(tài)將成為組合的一部分,并在函數(shù)重組時被重用,這樣一來,購物車商品也可以按照我們的預(yù)期工作了。
由于重新組合期間會保留 quantity,因此屏幕上將顯示改變后的新值。此外,Compose 還提供了 rememberSaveable,其行為與 remember 類似,但存儲的值可在 Activity 和進程重建后保留下來,這是在配置變更時保留界面數(shù)據(jù)的好方法。rememberSaveable 適用于界面狀態(tài),如商品數(shù)量或選定的標(biāo)簽,但不適用諸如過渡動畫狀態(tài)一類的用例。
此外,您還可以將委托屬性與 State API 結(jié)合使用。在下面的代碼中可以看到,在實際應(yīng)用中,我們可以使用 by 關(guān)鍵字來實現(xiàn)這一點。如果您不想每次都訪問 value 屬性的話,這不失為一種好方法。
fun CartItem() {
var quantity: Int by rememberSaveable { mutableStateOf(1) }
Row {
Button(onClick = { quantity++ }) {
Text("+")
}
Text(quantity.toString())
Button(onClick = { quantity-- }) {
Text("-")
}
}
}
注意,您只應(yīng)在可組合函數(shù)的作用域之外操作狀態(tài),因為可組合項可能會頻繁地、以任何順序執(zhí)行。上面的代碼中,在 onClick 監(jiān)聽器中修改 quantity 的操作是安全的,因為 onClick 不是可組合函數(shù)。您可以根據(jù)特定的用戶輸入觸發(fā)狀態(tài)更改,例如點擊按鈕或使用附帶效應(yīng):
https://developer.android.google.cn/jetpack/compose/side-effects
狀態(tài)提升
Compose 相比起 View 系統(tǒng)的一大優(yōu)勢,便是更為良好的可復(fù)用性。然而在當(dāng)前的形式下,您無法復(fù)用 CartItem 可組合函數(shù),因為它總是會將私有的 quantity 初始化為 1。在真實的使用環(huán)境中,購物車中的商品并不總是都從 1 開始計數(shù),而且用戶的購物車中也可能會有之前會話中的商品。與依賴注入的邏輯類似,為了使 CartItem 可重用,我們需要將 quantity 作為參數(shù)傳遞給該函數(shù);不僅如此,為了遵循單一可信來源原則,傳遞給 CartItem 的 quantity 應(yīng)該不可變。 單一可信來源原則鼓勵結(jié)構(gòu)化的代碼,以便只在一個位置修改數(shù)據(jù)。在本例中,如果 CartItem 不負(fù)責(zé)特定的狀態(tài) (即 quantity),就不應(yīng)該對它進行更新。因此 CartItem 需要在用戶與按鈕交互并觸發(fā)狀態(tài)更新時通知調(diào)用方。但是這樣一來,我們就需要考慮應(yīng)該由誰負(fù)責(zé)更新 quantity 等狀態(tài)的操作。我們可以先假設(shè) Cart 可組合項應(yīng)該擁有所有 CartItem 的信息,以及相應(yīng)地更新這些信息的邏輯:△ 假設(shè) Cart 可組合項負(fù)責(zé)更新每個購物車的商品數(shù)量為了使 CartItem 可被重用,我們將 quantity 狀態(tài)從 CartItem 提升至 Cart 中,這一過程被稱為狀態(tài)提升:
https://developer.android.google.cn/jetpack/compose/state
狀態(tài)提升是一種將私有狀態(tài)移出可組合項的模式,這可以使可組合項更趨于無狀態(tài),從而提高在應(yīng)用中的可重用性。無狀態(tài)可組合項是指不保存任何私有狀態(tài)的可組合項。理想情況下,可組合項應(yīng)接收狀態(tài)作為參數(shù),并使用 lambda 向上傳遞事件:
△可組合項應(yīng)接收狀態(tài) (State)
并使用 lambda 向上傳遞事件 (Events)使可組合項趨于無狀態(tài),不但可以使其符合單一可信來源原則,而且可以提高它的可重用性和可測試性。因為在這種情況下,可組合項沒有與任何特定的數(shù)據(jù)處理方式耦合在一起,而我們還可以共享和攔截以這種方式提升的狀態(tài)。下面是無狀態(tài)版的 CartItem 的示例代碼,它接收 quantity 并做為狀態(tài)顯示,同時將用戶交互公開為事件:
fun CartItem(
quantity: Int, // 狀態(tài)
incrementQuantity: () -> Unit, // 事件
decrementQuantity: () -> Unit // 事件
) {
Row {
Button(onClick = incrementQuantity) {
Text("+")
}
Text(quantity.toString())
Button(onClick = decrementQuantity) {
Text("-")
}
}
}
接下來我們來看 Cart 可組合函數(shù)的實現(xiàn)。Cart 界面會在 LazyColumn 中顯示不同的 CartItem,同時負(fù)責(zé)使用正確的信息調(diào)用 CartItem。Cart 中的項目實際上是從 CartViewModel 取得的應(yīng)用數(shù)據(jù)。我們對于每個 CartItem 都傳入特定的 quantity,增加或減少數(shù)量的邏輯被委托給 ViewModel,ViewModel 則作為 Cart 數(shù)據(jù)的持有者:
fun Cart(viewModel: CartViewModel = viewModel()) {
val cartItems by viewModel.cartItems
LazyColumn {
items(cartItems) { item ->
CartItem(
quantity = item.quantity,
incrementQuantity = {
viewModel.inrementQuantity(item)
},
decrementQuantity = {
viewModel.decrementQuantity(item)
}
)
}
}
}
狀態(tài)提升是一種在 Compose 中廣泛使用的模式。作為一種攔截和控制界面元素內(nèi)部使用狀態(tài)的方式,您可以在大多數(shù) Compose API 中看到它。我們也可以將攔截狀態(tài)設(shè)計為可選操作,從而可以利用強大的默認(rèn)參數(shù)特性。以下面的代碼為例,如果需要控制或共享 scaffoldState,您可以傳入該狀態(tài);而如果您沒有傳入,該函數(shù)也會創(chuàng)建一個默認(rèn)狀態(tài):
fun Scaffold(
scaffoldState: ScaffoldState = rememberScaffoldState(),
...
) { ... }
public fun NavHost(
navController: NavHostController,
...
){...}
在上面的例子中,我們只是假設(shè)應(yīng)該由 Cart 可組合函數(shù)負(fù)責(zé) CartItem 的狀態(tài)更新。那么在我們實際去應(yīng)用時,應(yīng)該將狀態(tài)提升到多高的層級呢?這其實是一個數(shù)據(jù)所有權(quán)的問題,如果您不能確定,則至少應(yīng)將狀態(tài)提升到需要訪問該狀態(tài)的所有可組合項的最低公共父級。在本例中,CartItem 的最低公共父級是 Cart,也就是負(fù)責(zé)使用正確的信息調(diào)用 CartItem 的層級。狀態(tài)提升另一個原則是,可組合項應(yīng)該只接受所需的參數(shù)。在 Jetsnack 中,我們使用了無狀態(tài)的 Cart 可組合項,它只接受需要的參數(shù):
fun Cart(
orderLines: List<OrderLine>,
removeSnack: (Long) -> Unit,
increaseItemCount: (Long) -> Unit,
decreaseItemCount: (Long) -> Unit,
inspiredByCart: SnackCollection,
modifier: Modifier = Modifier
) {
...
}
這樣的 Cart 可組合項更易于預(yù)覽和測試,同時符合單一可信來源原則。這樣做的可重用性也更高,比如,如果我們需要,就可以在窗口尺寸足夠大的情況下,與另一個界面并排顯示購物車。不僅如此,我們還提供了有狀態(tài)版本,使其也可以通過特定的方式處理狀態(tài)和事件:
fun Cart(
modifier: Modifier = Modifier,
viewModel: CartViewMo = viewModel()
) {
val orderLines by viewModel.orderLines.collectAsState()
Cart(
orderLines = orderLines,
removeSnack = viewModel::removeSnack,
increaseItemCount = viewModel::increaseSnackCount,
decreaseItemCount = viewModel::decreaseSnackCount,
inspiredByCart = viewModel.inspiredByCart,
modifier = modifier
)
}
我們可以看到,這個版本的 Cart 通過處理業(yè)務(wù)邏輯和狀態(tài)的 CartViewModel 來調(diào)用無狀態(tài)版的 Cart 可組合項。這種同時提供有狀態(tài)、無狀態(tài),或趨于無狀態(tài)組合項的模式,可以很好的兼顧各種使用場景。您既可以在需要時重用可組合項,又可以在應(yīng)用中以特定的方式使用它。
狀態(tài)管理
狀態(tài)應(yīng)至少提升到最低公共父級,但是否應(yīng)該總是將狀態(tài)置于可組合項中?在前面的例子中我們可以看到,Jetsnack Cart 使用的是與 Compose Navigation 集成度很好的 ViewModel。在下面的表格中,列出了幾種管理和定義可信來源的方式,以及它們所對應(yīng)的狀態(tài)類型,在下面的文章中將對它們進行詳細(xì)的介紹:注意: 如果對應(yīng)用例不能應(yīng)用 ViewModel 的優(yōu)勢,那么就可以用一般的狀態(tài)持有者代替 ViewModel
在開始之前,我們要定義文中所涉及特定術(shù)語的含義:
- 界面元素狀態(tài): 是指被提升的界面元素狀態(tài)。例如,ScaffoldState。
- 屏幕或界面的狀態(tài):是指需要在屏幕上顯示的內(nèi)容。例如,CartUiState 可以包含購物車商品、向用戶顯示的消息或加載標(biāo)記。此類狀態(tài)通常會與層次結(jié)構(gòu)中的其他層級相關(guān)聯(lián),因為其包含應(yīng)用數(shù)據(jù)。
- 界面的行為邏輯:與如何在界面上顯示狀態(tài)更改相關(guān)。例如,導(dǎo)航邏輯或顯示信息提示控件。界面行為邏輯應(yīng)始終位于組合中。
- 業(yè)務(wù)邏輯:決定如何處理狀態(tài)更改。比如,進行支付或存儲用戶偏好設(shè)置。這類邏輯通常應(yīng)置于業(yè)務(wù)層或數(shù)據(jù)層,絕不應(yīng)置于界面層。
但是,實際情況往往更加復(fù)雜。JetsnackApp 除了會發(fā)送界面元素外,還負(fù)責(zé)顯示信息提示控件、導(dǎo)航到正確的屏幕、設(shè)置底部操作欄等操作。將所有這些內(nèi)容都放在可組合項中,會使它難以閱讀和理解。我們可以遵循分離關(guān)注點原則,將屏幕邏輯和界面元素狀態(tài)委托給名為 "狀態(tài)容器"的類,從而讓可組合函數(shù)只負(fù)責(zé)生成界面元素。 一般類型作為狀態(tài)容器fun JetsnackApp() {
JetsnackTheme {
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope()
JetsnackScaffold(scaffoldState = scaffoldState) {
Content(
showSnackbar = { message ->
coroutineScope.launch {
scaffoldState.snackbarHostState
.showSnackbar(message)
}
}
)
}
}
}
我們使用 JetsnackAppState 類作為狀態(tài)容器,它將會是 JetsnackApp 的界面元素狀態(tài)的可信來源,因此所有狀態(tài)寫入都應(yīng)在該類中進行。狀態(tài)容器是在組合中創(chuàng)建和記住的普通類,因此,該類的作用域限定于創(chuàng)建它的可組合項。JetsnackAppState 只是一個普通類,而且由于它遵循可組合項的生命周期,因此可以使用 Compose 的依賴項,而不必?fù)?dān)心內(nèi)存泄漏:
class JetsnackAppState(
// 一般的類可以接收 Compose 依賴
val scaffoldState: ScaffoldState,
val navController: NavHostController,
...
) {
val shouldShowBottomBar: Boolean
// 在讀取的值發(fā)生改變時會進行重組
get() = navController.currentBackStackEntryAsState().value
?.destination?.route in bottomBarRoutes
// 與界面相關(guān)的邏輯
fun navigateToBottomBarRoute(route: String) {
if (route != currentRoute) {
navController.navigate(route) {
launchSingleTop = true
restoreState = true
popUpTo(findStartDestination(navController.graph).id) {
saveState = true
}
}
}
}
}
狀態(tài)容器還可以包含可組合項屬性,更改此類屬性將會觸發(fā)重組,上面的代碼即為是否顯示底部操作欄的屬性。該狀態(tài)容器還包含界面相關(guān)的邏輯,比如導(dǎo)航邏輯。就像前面說過的,您必須使用 remember 記住數(shù)據(jù),以便在重新組合期間重用數(shù)據(jù),如果狀態(tài)容器使用了 State 依賴項,那么應(yīng)該提供方法來記住狀態(tài)容器。在下面的代碼中,我們將依賴項傳入 remember,以便在任何依賴項發(fā)生更改時獲取 JetsnackAppState 的新實例:
fun rememberJetsnackAppState(
scaffoldState: ScaffoldState = rememberScaffoldState(),
navController: NavHostController = rememberNavController(),
...
) = remember(scaffoldState, navController, ...) {
JetsnackAppState(scaffoldState, navController, ...)
}
現(xiàn)在,我們在 JetsnackApp 中獲取了 appState 的新實例。我們使用該實例將被提升的狀態(tài)傳遞給可組合項,并在需要顯示界面元素時檢查該狀態(tài);同時調(diào)用函數(shù)來觸發(fā)與界面相關(guān)的操作:
fun JetsnackApp() {
JetsnackTheme {
val appState = rememberJetsnackAppState()
JetsnackScaffold(
scaffoldState = appState.scaffoldState,
bottomBar = {
if (appState.shouldShowBottomBar) {
JetsnackBottomBar(
tabs = appState.bottomBarTabs,
navigateToRoute = {
appState.navigateToBottomBarRoute(it)
}
)
}
}
) {
NavHost(navController=appState.navController,...){
簡單來說,狀態(tài)容器是一個普通類,用于提升界面元素的狀態(tài)并包含界面相關(guān)的邏輯。狀態(tài)容器可以降低可組合項的復(fù)雜性,并提高其可測試性,從而有助于關(guān)注點分離。它還可以使?fàn)顟B(tài)提升變得更為容易,因為只需提升一個狀態(tài)而不是多個狀態(tài)。狀態(tài)容器可以非常簡單并且只用于特定用途,例如,只用于搜索界面的 SearchState 類,其中僅包含 activeFilters 和 searchResults List。當(dāng)您需要跟蹤狀態(tài)或界面邏輯時可以使用狀態(tài)容器來幫助控制復(fù)雜度。
ViewModel 作為狀態(tài)容器class SearchState {
var searchResults: List
by mutableStateOf(listOf()) private set
var activeFilters: List
by mutableStateOf(listOf()) private set
...
}
除了一般的狀態(tài)容器外,我們還可以使用 ViewModel,這是一種繼承架構(gòu)組件 ViewModel 類的類。ViewModel 可用作由業(yè)務(wù)邏輯確定狀態(tài)的狀態(tài)容器。ViewModel 有兩項責(zé)任: 首先,提供對應(yīng)用業(yè)務(wù)邏輯的訪問,這些業(yè)務(wù)邏輯通常位于層次結(jié)構(gòu)的其他層級中,如存儲區(qū)或用例中;其次,準(zhǔn)備要在特定屏幕上呈現(xiàn)的應(yīng)用數(shù)據(jù),通常是用可觀察類型呈現(xiàn)屏幕的界面狀態(tài)。
在完全使用 Compose 構(gòu)建的應(yīng)用中,我們可以使用 Compose 的 State 類型。但在混合應(yīng)用中,您還可能會用到其他的可觀察類型,如 StateFlow:
class CartViewModel(
private val repository: SnackRepository,
private val savedState: SavedStateHandle
) : ViewModel() {
val uiState: State
= ...
fun increaseSnackCount(snackId: Long) { ... }
fun decreaseSnackCount(snackId: Long) { ... }
fun removeSnack(snackId: Long) { ... }
fun completeOrder() { ... }
}
ViewModel 在配置變更后仍然有效,因此其生命周期比組合更長。ViewModel 不屬于組合的一部分,因此不能接受組合作用域內(nèi)的狀態(tài),比如使用記住的值,您需要謹(jǐn)慎對待此類操作,因為這可能會導(dǎo)致內(nèi)存泄漏。ViewModel 依賴于層次結(jié)構(gòu)的其他層級,例如存儲區(qū)或用例。另外,如果您希望界面在狀態(tài)發(fā)生更改時重組,您依然需要使用 Compose State API。
class CartViewModel(
private val repository: SnackRepository,
private val savedState: SavedStateHandle
) : ViewModel() {
// 在 ViewModel 中,仍要使用 State 類型來使?fàn)顟B(tài)可被 Compose 觀察
var uiState by mutableStateOf
(EmptyCart) private set
...
}
不過在本例中,由于 uiState 位于組合之外,因此您不需要記住它,而只需使用它即可??山M合函數(shù)將在 uiState 更改時重新執(zhí)行:
fun Cart(viewModel: CartViewModel = viewModel()) {
val uiState by viewModel.uiState
}
層次結(jié)構(gòu)的其他層通常使用流式數(shù)據(jù)來傳播更改,您可能已經(jīng)開始在 ViewModel 中使用它們了。Flow 也可以很好地與 Compose 結(jié)合使用,我們提供了工具函數(shù),可以將數(shù)據(jù)流轉(zhuǎn)換為 Compose 的可觀察 State API。例如,您可以使用 collectAsState 從數(shù)據(jù)流中收集值,并將它們呈現(xiàn)為 State。這樣一來,每當(dāng)數(shù)據(jù)流發(fā)出新值時,就會觸發(fā)重組。
fun Cart(viewModel: CartViewModel = viewModel()) {
// 通過將 Flow 轉(zhuǎn)換為 State 來跟蹤 snacks 狀態(tài)的改變
val snacks by viewModel.snacks.collectAsState()
...
}
總的來說,ViewModel 可以在組合之外提升組合的狀態(tài),同時具有更長的生命周期。ViewModel 負(fù)責(zé)屏幕的業(yè)務(wù)邏輯并決定要顯示哪些數(shù)據(jù),它會從其他層級獲取數(shù)據(jù),并準(zhǔn)備這些要呈現(xiàn)的數(shù)據(jù)。因此,建議在屏幕級的可組合項中使用 ViewModel。
與普通狀態(tài)容器相比 ViewModel 具有一些優(yōu)勢,其中包括,ViewModel 觸發(fā)的操作在配置變更后仍然有效,并且 ViewModel 可以與 Hilt、Navigation 等 Jetpack 庫很好地集成在一起。在使用 Hilt 時,僅使用一行代碼,就能通過 Hilt 提供的依賴項獲取 ViewModel。
當(dāng)屏幕位于返回棧中時,Navigation 會緩存 ViewModel,這意味著當(dāng)返回到目標(biāo)時,數(shù)據(jù)已經(jīng)處于可用狀態(tài);而當(dāng)目標(biāo)離開返回棧后 ViewModel 又會被清除,從而確保狀態(tài)可以被自動清理。
使用遵循可組合項界面生命周期的狀態(tài)容器 (即使用一般的類作為狀態(tài)容器),將會難以做到前述操作。盡管如此,如果 ViewModel 的優(yōu)勢不適用于您的用例或者您以不同的方式操作,您可以使用其他最適合您的狀態(tài)容器,而不一定是 ViewModel 來完成相應(yīng)的工作。
同時使用 ViewModel 和其他狀態(tài)容器界面級可組合項也可以同時使用 ViewModel 和其他狀態(tài)容器。由于 ViewModel 的生命周期更長,普通的狀態(tài)容器可以將 ViewModel 作為依賴項。
我們來看一下實際應(yīng)用。除了在 Cart 可組合項中使用 CartViewModel 之外,我們還可以另外使用包含界面元素狀態(tài)和界面邏輯的 CartState。在 CartState 中,我們使用 lazyListState 來記錄大型購物車界面的滾動位置;使用 resources 來格式化信息和價格;如果允許展開商品以顯示更多信息,還可以了解每個商品的狀態(tài):
class CartState(
lazyListState: LazyListState,
resources: Resources,
expandedItems: List
- = emptyList()
) {
...
fun formatPrice(...) { ... }
}
Cart 中同時使用了 ViewModel 和其他狀態(tài)容器,它們具有不同的用途,并可以協(xié)同工作。我們來仔細(xì)看一下它們的生命周期: CartState 會遵循 Cart 可組合項的生命周期,當(dāng) Cart 從組合中移除后 CartState 也會一同移除;而 CartViewModel 具有不同的生命周期,即導(dǎo)航目的地、導(dǎo)航圖、Fragment 或 Activity 的生命周期:
△CartState 遵循 Cart 的生命周期
而 CartViewModel 則位于組合之外從全局來看,每個實體的作用都有明確的定義,從包含界面元素的界面層到包含業(yè)務(wù)邏輯的數(shù)據(jù)層,每個實體都有特定的用途。在下圖中,您可以看到扮演著不同角色的實體,以及它們之間潛在的依賴關(guān)系:
△實體間的關(guān)系及它們對應(yīng)的用途
總結(jié)
對于我們的應(yīng)用來說,狀態(tài)是十分重要的一部分。我們可以在 Compose 中使用 State API 做到簡單的狀態(tài)響應(yīng),也可以使用一般的類或者 ViewModel 作為狀態(tài)容器,以便對可組合項進行狀態(tài)提升,并使其符合單一可信來源原則。我們還可以組合不同的狀態(tài)容器,從而利用它們各自不同的生命周期。
下面是我們在文章中列出的表格,請記住它,以便您在未來做決策時可以為您的應(yīng)用提供明確的狀態(tài)管理架構(gòu):
△希望現(xiàn)在您能更加理解這個表格的意義
希望本文能幫助您實現(xiàn) "理想的 Compose 狀態(tài)",祝您擁有愉快的 Compose 使用體驗。
原文標(biāo)題:實踐 | Jetpack Compose 中的狀態(tài)管理
文章出處:【微信公眾號:谷歌開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
-
狀態(tài)
+關(guān)注
關(guān)注
0文章
16瀏覽量
11959 -
State
+關(guān)注
關(guān)注
0文章
5瀏覽量
7672 -
開發(fā)者
+關(guān)注
關(guān)注
1文章
577瀏覽量
17029
原文標(biāo)題:實踐 | Jetpack Compose 中的狀態(tài)管理
文章出處:【微信號:Google_Developers,微信公眾號:谷歌開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論