0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

在Compose中使用狀態(tài)(State)

谷歌開發(fā)者 ? 來源:Android 開發(fā)者 ? 作者:Android ? 2022-04-21 11:05 ? 次閱讀
對于開發(fā)者來說,狀態(tài) (State) 指應(yīng)用中可以隨時間變化的任何值。我們的應(yīng)用天然便是擁有狀態(tài)的,無論您將數(shù)據(jù)存儲在本地還是服務(wù)器中,狀態(tài)都可以使這些數(shù)據(jù)更有價值。接下來,我們就以應(yīng)用 Jetsnack 作為示例,談一談 Compose 中的狀態(tài)。aff35282-c114-11ec-bce3-dac502259ad0.png

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),這些都是搜索界面的組成部分:

b0141404-c114-11ec-bce3-dac502259ad0.png

△組合呈現(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ù):

b02b3d1e-c114-11ec-bce3-dac502259ad0.png

△組合呈現(xiàn)購物車界面的過程

在購物車界面中,我們重點關(guān)注單獨的購物車商品項。該元素用于顯示購物車中的商品,并允許您更改數(shù)量:

b03e3a4a-c114-11ec-bce3-dac502259ad0.gif

△單獨的購物車商品

我們可以使用包含兩個 Button 和一個 Text 的 Row 來構(gòu)建該界面,但是要如何記錄購物車中商品的當(dāng)前數(shù)量呢?

@Composablefun CartItem() {    var quantity = ... ˉ\_(ッ)_/ˉ ... ?     Row {        Button(onClick = { quantity++ }) {            Text("+")        }        Text(quantity.toString())        Button(onClick = { quantity-- }) {            Text("-")        }    }}
我們可以簡單地在可組合函數(shù)中添加一個可變變量 quantity,但接下來您會發(fā)現(xiàn),在我們通過點按增加和減少數(shù)量的按鈕來修改此變量的值時,界面中顯示的數(shù)量沒有發(fā)生任何變化——當(dāng)狀態(tài)更改時,該函數(shù)沒有重新組合。這是因為 Compose 不會跟蹤 quantity 變量。
@Composablefun CartItem() {    var quantity: Int = 1    ...}
b281b07a-c114-11ec-bce3-dac502259ad0.gif△數(shù)量的顯示沒有發(fā)生改變

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ā)重新組合。

// Stateinterface State<out T> {    val value: T} // MutableStateinterface MutableState<T> : State<T> {    override var value: T}

您可以使用 mutableStateOf 函數(shù)創(chuàng)建 MutableState,該函數(shù)需要接收一個初始值,并且它的 value 是可變的。相應(yīng)的,我們需要改用 value 屬性來讀取和寫入 quantity 狀態(tài):

@Composablefun 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ù)將對象存儲在組合中:

@Composablefun 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 屬性的話,這不失為一種好方法。

@Composablefun 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)地更新這些信息的邏輯:b3595f84-c114-11ec-bce3-dac502259ad0.png△ 假設(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 向上傳遞事件:

b36cddfc-c114-11ec-bce3-dac502259ad0.png

△可組合項應(yīng)接收狀態(tài) (State)

并使用 lambda 向上傳遞事件 (Events)

使可組合項趨于無狀態(tài),不但可以使其符合單一可信來源原則,而且可以提高它的可重用性和可測試性。因為在這種情況下,可組合項沒有與任何特定的數(shù)據(jù)處理方式耦合在一起,而我們還可以共享和攔截以這種方式提升的狀態(tài)。下面是無狀態(tài)版的 CartItem 的示例代碼,它接收 quantity 并做為狀態(tài)顯示,同時將用戶交互公開為事件:

@Composablefun 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ù)的持有者:

@Composablefun 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):

@Composablefun Scaffold(    scaffoldState: ScaffoldState = rememberScaffoldState(),    ...) { ... } @Composablepublic 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ù):
@Composablefun Cart(    orderLines: List<OrderLine>,    removeSnack: (Long) -> Unit,    increaseItemCount: (Long) -> Unit,    decreaseItemCount: (Long) -> Unit,    inspiredByCart: SnackCollection,    modifier: Modifier = Modifier) {    ...}

這樣的 Cart 可組合項更易于預(yù)覽和測試,同時符合單一可信來源原則。這樣做的可重用性也更高,比如,如果我們需要,就可以在窗口尺寸足夠大的情況下,與另一個界面并排顯示購物車。不僅如此,我們還提供了有狀態(tài)版本,使其也可以通過特定的方式處理狀態(tài)和事件:

@Composablefun 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ì)的介紹:

b3884736-c114-11ec-bce3-dac502259ad0.png

注意: 如果對應(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)置于界面層。
在大致了解了這些概念后,讓我們來看看處理狀態(tài)的不同方式。首先,如果您要處理的界面元素狀態(tài)比較簡單,就可以放在可組合項中。在本例中,JetsnackApp 可組合項中持有 scaffoldState。由于 scaffoldState 包含可變屬性,因此,與之相關(guān)的所有交互都應(yīng)在該可組合項中進行。如果將 scaffoldState 傳遞給其他可組合項,這些可組合項可能會改變其狀態(tài)。這不符合單一可信來源原則,而且會使對錯誤的跟蹤變得更加困難。
@Composablefun JetsnackApp() {    JetsnackTheme {        val scaffoldState = rememberScaffoldState()        val coroutineScope = rememberCoroutineScope()         JetsnackScaffold(scaffoldState = scaffoldState) {            Content(                    showSnackbar = { message ->                        coroutineScope.launch {                            scaffoldState.snackbarHostState                                    .showSnackbar(message)                        }                    }            )        }    }}
但是,實際情況往往更加復(fù)雜。JetsnackApp 除了會發(fā)送界面元素外,還負(fù)責(zé)顯示信息提示控件、導(dǎo)航到正確的屏幕、設(shè)置底部操作欄等操作。將所有這些內(nèi)容都放在可組合項中,會使它難以閱讀和理解。我們可以遵循分離關(guān)注點原則,將屏幕邏輯和界面元素狀態(tài)委托給名為 "狀態(tài)容器"的類,從而讓可組合函數(shù)只負(fù)責(zé)生成界面元素。 一般類型作為狀態(tài)容器

我們使用 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ā)生改變時會進行重組        @Composable 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 的新實例:

@Composablefun 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)的操作:

@Composablefun 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ù)雜度。

class SearchState {    var searchResults: List by mutableStateOf(listOf())        private set     var activeFilters: List by mutableStateOf(listOf())        private set     ...}
ViewModel 作為狀態(tài)容器

除了一般的狀態(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í)行:

@Composablefun 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ā)重組。

@Composablefun 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 的生命周期:

b39ca6b8-c114-11ec-bce3-dac502259ad0.png

CartState 遵循 Cart 的生命周期

而 CartViewModel 則位于組合之外

從全局來看,每個實體的作用都有明確的定義,從包含界面元素的界面層到包含業(yè)務(wù)邏輯的數(shù)據(jù)層,每個實體都有特定的用途。在下圖中,您可以看到扮演著不同角色的實體,以及它們之間潛在的依賴關(guān)系:

b3b52de6-c114-11ec-bce3-dac502259ad0.png實體間的關(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):

b3884736-c114-11ec-bce3-dac502259ad0.png希望現(xiàn)在您能更加理解這個表格的意義

希望本文能幫助您實現(xiàn) "理想的 Compose 狀態(tài)",祝您擁有愉快的 Compose 使用體驗。

原文標(biāo)題:實踐 | Jetpack Compose 中的狀態(tài)管理

文章出處:【微信公眾號:谷歌開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(liá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)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    關(guān)于狀態(tài)中使用事件情況的問題

    如圖1,2.問題是,我現(xiàn)在想要實現(xiàn)第二種情況,如圖流程(situation 2), state 1判斷信號input是否等于3,如果等于3,進入狀態(tài)3,如果5秒內(nèi)沒有信號發(fā)生,或信號不等于3,就進入
    發(fā)表于 05-16 19:22

    Coke Machine State Machine

    `Coke Machine State Machine 源碼:、官方給的例程中加以修改,關(guān)于狀態(tài)機是個很好的學(xué)習(xí)范例。PDF資料截圖如下:`
    發(fā)表于 08-06 19:42

    QT中使用按鍵事件檢測按鍵狀態(tài)

    嵌入式Linux系統(tǒng)中,用QT做的應(yīng)用層程序,需要檢測自定義的按鍵狀態(tài)。使用的QT的按鍵事件,驅(qū)動層使用的Linux的input子系統(tǒng)。環(huán)境如下:硬件:Imx6ullQT版本:5.5QT中使用按鍵
    發(fā)表于 10-27 10:31

    compose的使用技巧是什么?

    compose的使用技巧是什么?
    發(fā)表于 11-15 07:27

    LCA(邏輯單元陣列)器件中使狀態(tài)

    State-machine methodology defines the contents of everyflip-flop in a design under every
    發(fā)表于 05-15 13:21 ?10次下載

    State Machine Coding Styles for Synthesis

    本文論述了狀態(tài)機的verilog編碼風(fēng)格,以及不同編碼風(fēng)格的優(yōu)缺點, Steve Golsons 1994 paper, State Machine Design Techniques
    發(fā)表于 01-17 11:22 ?0次下載
    <b class='flag-5'>State</b> Machine Coding Styles for Synthesis

    如何使用 Compose 進行構(gòu)建

    適用于 Wear OS 的 Compose 已推出了開發(fā)者預(yù)覽版,使用 Compose 構(gòu)建 Wear OS 應(yīng)用,不僅可以輕松遵循 Material You 指南,同時可以將 Compose 的優(yōu)點發(fā)揮出來。
    的頭像 發(fā)表于 03-17 13:44 ?1781次閱讀

    Jetpack Compose基礎(chǔ)知識科普

    Jetpack Compose 是用于構(gòu)建原生 Android 界面的新工具包。它可簡化并加快 Android 上的界面開發(fā),使用更少的代碼、強大的工具和直觀的 Kotlin API,快速讓應(yīng)用生動
    的頭像 發(fā)表于 04-02 13:38 ?2980次閱讀

    Docker Compose Docker應(yīng)用構(gòu)建管理工具

    ./oschina_soft/compose.zip
    發(fā)表于 05-12 15:58 ?1次下載
    Docker <b class='flag-5'>Compose</b> Docker應(yīng)用構(gòu)建管理工具

    Compose社區(qū)中的反響

    我們很高興地看到這些團隊大規(guī)模和復(fù)雜的生產(chǎn)環(huán)境中仔細(xì)地評估并使用了 Compose,所帶來的結(jié)果也不僅是讓界面開發(fā)更清晰有趣,也帶來了更多工程上的收益。這只是其中幾個案例,因為 Play 商店排名前 1,000 的應(yīng)用中有超過 100 個正在使用
    的頭像 發(fā)表于 05-18 10:15 ?1056次閱讀

    Compose Material 3 穩(wěn)定版現(xiàn)已發(fā)布 | 2022 Android 開發(fā)者峰會

    (新一代 Material Design ) 構(gòu)建 Jetpack Compose 界面。立即開始應(yīng)用中使用 Material Design 3 吧! Compose Materi
    的頭像 發(fā)表于 11-21 18:10 ?1195次閱讀

    Jetpack Compose 更新一覽 | 2022 Android 開發(fā)者峰會

    作者 /?Android 開發(fā)者關(guān)系工程師 Jolanda Verhoef 去年我們發(fā)布了 Jetpack Compose ,此后一直進行優(yōu)化。我們已添加了新的功能并創(chuàng)造出功能更強大的工具,幫助
    的頭像 發(fā)表于 11-23 17:55 ?1133次閱讀

    Kotlin聲明式UI框架Compose Multiplatform支持iOS

    JetBrains ?KotlinConf’23 大會上宣布,Compose Multiplatform 已支持 iOS,目前處于 alpha 階段。至此,Compose
    的頭像 發(fā)表于 04-24 09:12 ?1314次閱讀
    Kotlin聲明式UI框架<b class='flag-5'>Compose</b> Multiplatform支持iOS

    PiSquare中使用PiHut狀態(tài)

    電子發(fā)燒友網(wǎng)站提供《PiSquare中使用PiHut狀態(tài)板.zip》資料免費下載
    發(fā)表于 06-13 15:38 ?0次下載
    <b class='flag-5'>在</b>PiSquare<b class='flag-5'>中使</b>用PiHut<b class='flag-5'>狀態(tài)</b>板

    docker-compose配置文件內(nèi)容詳解以及常用命令介紹

    。 使用 docker-compose.yml 定義構(gòu)成應(yīng)用程序的服務(wù),這樣它們可以隔離環(huán)境中一起運行。 最后,執(zhí)行 docker-compose up 命令來啟動并運行整個應(yīng)用程序。 二、docker-
    的頭像 發(fā)表于 12-02 09:29 ?547次閱讀
    docker-<b class='flag-5'>compose</b>配置文件內(nèi)容詳解以及常用命令介紹