狀態(tài)以及 Jetpack Compose 如何使用和操作狀態(tài)饰及。
在我們深入研究之前,定義狀態(tài)到底是什么很有用。 從本質(zhì)上講幻馁,應(yīng)用程序中的狀態(tài)是任何可以隨時(shí)間變化的值洗鸵。 這是一個(gè)非常廣泛的定義,包括從 Room 數(shù)據(jù)庫到類上的變量的所有內(nèi)容仗嗦。
目標(biāo):
什么是單向數(shù)據(jù)流
如何在 UI 中考慮狀態(tài)和事件
如何在 Compose 中使用 Architecture Component 的 ViewModel 和 LiveData 來管理狀態(tài)
Compose 如何使用狀態(tài)來繪制屏幕
何時(shí)將狀態(tài)移動(dòng)到調(diào)用者
如何在 Compose 中使用內(nèi)部狀態(tài)
如何使用 State<T> 將狀態(tài)與 Compose 集成
使用單向數(shù)據(jù)流
為了幫助解決非結(jié)構(gòu)化狀態(tài)的這些問題膘滨,我們引入了包含 ViewModel 和 LiveData 的 Android 架構(gòu)組件。
ViewModel 允許您從 UI 中提取狀態(tài)并定義 UI 可以調(diào)用以更新該狀態(tài)的事件
官方的例子理解原理:
ViewModel 還公開了一個(gè)事件:onNameChanged稀拐。 此事件由 UI 調(diào)用以響應(yīng)用戶事件火邓,例如每當(dāng) EditText 的文本更改時(shí)此處會(huì)發(fā)生什么。
回到我們之前討論過的 UI 更新循環(huán)钩蚊,我們可以看到這個(gè) ViewModel 如何與事件和狀態(tài)結(jié)合在一起贡翘。
事件 – onNameChanged 在文本輸入更改時(shí)由 UI 調(diào)用
更新狀態(tài) - onNameChanged 進(jìn)行處理蹈矮,然后設(shè)置 _name 的狀態(tài)
顯示狀態(tài) - 調(diào)用名稱的觀察者砰逻,通知 UI 狀態(tài)變化
通過以這種方式構(gòu)建我們的代碼,我們可以認(rèn)為事件“向上”流向 ViewModel泛鸟。 然后蝠咆,為了響應(yīng)事件,ViewModel 將進(jìn)行一些處理并可能更新狀態(tài)北滥。 當(dāng)狀態(tài)更新時(shí)刚操,它會(huì)“向下”流向
這種模式稱為單向數(shù)據(jù)流。 單向數(shù)據(jù)流是一種狀態(tài)向下流動(dòng)而事件向上流動(dòng)的設(shè)計(jì)再芋。 通過以這種方式構(gòu)建我們的代碼菊霜,我們獲得了一些優(yōu)勢:
- 可測試性——通過將狀態(tài)與顯示它的 UI 分離,可以更輕松地測試 ViewModel 和 Activity
- 狀態(tài)封裝——因?yàn)闋顟B(tài)只能在一個(gè)地方(ViewModel)更新济赎,隨著 UI 的增長鉴逞,你不太可能引入部分狀態(tài)更新錯(cuò)誤
- UI 一致性——所有狀態(tài)更新都通過使用可觀察狀態(tài)持有者立即反映在 UI 中
因此,雖然這種方法確實(shí)添加了更多代碼司训,但使用單向數(shù)據(jù)流處理復(fù)雜的狀態(tài)和事件往往更容易构捡、更可靠。
單向數(shù)據(jù)流是一種事件向上流動(dòng)而狀態(tài)向下流動(dòng)的設(shè)計(jì)壳猜。
例如勾徽,在 ViewModel 中,事件通過來自 UI 的方法調(diào)用傳遞统扳,而狀態(tài)使用 LiveData 向下流動(dòng)喘帚。
它不僅僅是描述 ViewModel 的術(shù)語——任何事件向上流動(dòng)和狀態(tài)下降的設(shè)計(jì)都是單向的。
如何使用 ViewModel 在 Compose 中使用單向數(shù)據(jù)流咒钟。
上面理論吹由,使用 ViewModel 和 LiveData 探索了 Android View 系統(tǒng)中的單向數(shù)據(jù)流。
StateCodeLab 項(xiàng)目 解讀
TodoScreen.kt – 這些可組合項(xiàng)直接與狀態(tài)交互盯腌,我們將在探索 compose 狀態(tài)時(shí)編輯此文件溉知。
TodoComponents.kt – 這些可組合定義了我們將用于構(gòu)建 TodoScreen 的可重用 UI 位。 您無需編輯這些可組合項(xiàng)即可完成此 Codelab。
這種文件劃分有點(diǎn)隨意,將 TodoScreen.kt 中的代碼集中在狀態(tài)上埃疫。 在實(shí)踐中研叫,這些組合項(xiàng)可能位于同一個(gè)文件中,或者分布在多個(gè)文件中甚淡,具體取決于您在項(xiàng)目中如何使用它們。
-
TodoScreen
函數(shù)
這個(gè)可組合顯示一個(gè)可編輯的 TODO 列表捅厂,但它沒有任何自己的狀態(tài)贯卦。 請記住,狀態(tài)是任何可以更改的值——但 TodoScreen 的任何參數(shù)都不能修改焙贷。
items – 要顯示在屏幕上的不可變項(xiàng)目列表
onAddItem – 用戶請求添加項(xiàng)目時(shí)的事件
onRemoveItem – 用戶請求刪除項(xiàng)目時(shí)的事件
事實(shí)上撵割,這個(gè)可組合是無狀態(tài)的。 它只顯示傳入的項(xiàng)目列表辙芍,無法直接編輯列表啡彬。 相反,它傳遞了兩個(gè)可以請求更改的事件 onRemoveItem 和 onAddItem故硅。
這就提出了一個(gè)問題:如果它是無狀態(tài)的庶灿,它如何顯示可編輯列表? 它通過使用一種稱為狀態(tài)提升的技術(shù)來做到這一點(diǎn)吃衅。 狀態(tài)提升是向上移動(dòng)狀態(tài)以使組件無狀態(tài)的模式往踢。 無狀態(tài)組件更容易測試,往往有更少的錯(cuò)誤徘层,并提供更多的重用機(jī)會(huì)峻呕。
事件 – 當(dāng)用戶請求添加或刪除項(xiàng)目時(shí) TodoScreen 調(diào)用 onAddItem 或 onRemoveItem
更新狀態(tài)——TodoScreen 的調(diào)用者可以通過更新狀態(tài)來響應(yīng)這些事件
顯示狀態(tài) - 當(dāng)狀態(tài)更新時(shí),TodoScreen 將使用新項(xiàng)目再次調(diào)用惑灵,并且可以在屏幕上顯示它們
調(diào)用者負(fù)責(zé)確定在何處以及如何保持此狀態(tài)山上。 它可以存儲項(xiàng)目但有意義,例如在內(nèi)存中或從 Room 數(shù)據(jù)庫中讀取它們英支。 TodoScreen 與狀態(tài)的管理方式完全分離佩憾。
使用這個(gè) ViewModel 從 TodoScreen 提升狀態(tài)。 完成后干花,我們將創(chuàng)建一個(gè)單向數(shù)據(jù)流設(shè)計(jì)
TodoScreen 集成到 TodoActivity.kt
class TodoActivity : AppCompatActivity() {
val todoViewModel by viewModels<TodoViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
StateCodelabTheme {
Surface {
TodoActivityScreen(todoViewModel)
}
}
}
}
}
@Composable
private fun TodoActivityScreen(todoViewModel: TodoViewModel) {
TodoScreen(
items = todoViewModel.todoItems,
currentlyEditing = todoViewModel.currentEditItem,
onAddItem = todoViewModel::addItem,
onRemoveItem = todoViewModel::removeItem,
onStartEdit = todoViewModel::onEditItemSelected,
onEditItemChange = todoViewModel::onEditItemChange,
onEditDone = todoViewModel::onEditDone
)
}
Android Studio 在啟動(dòng)新 Compose 項(xiàng)目時(shí)創(chuàng)建的默認(rèn)主題妄帘。
Surface 為應(yīng)用程序添加背景,并配置文本顏色池凄。
Flow the events up 事件向上流動(dòng)
Kotlin 提示
您還可以使用方法引用語法生成一個(gè)調(diào)用單個(gè)方法的 lambda抡驼。 這將從方法調(diào)用中創(chuàng)建一個(gè) lambda。 使用方法引用語法肿仑,上面的 onAddItem 也可以表示為 onAddItem = todoViewModel::addItem致盟。
Pass the state down 向下傳遞狀態(tài)
現(xiàn)在我們已經(jīng)探索了如何使用 compose 和 ViewModels 來構(gòu)建單向數(shù)據(jù)流碎税,讓我們來探索 compose 如何在內(nèi)部與狀態(tài)交互。
有狀態(tài)的可組合是一種可以隨時(shí)間改變的狀態(tài)組合馏锡。
重新組合是再次運(yùn)行相同的組合以在其數(shù)據(jù)發(fā)生變化時(shí)更新樹的過程
Compose 生成一棵樹雷蹂,但它與您可能熟悉的 Android 視圖系統(tǒng)中的 UI 樹有點(diǎn)不同。 compose 生成了一棵可組合的樹杯道,而不是一棵 UI 小部件樹匪煌。
我們不希望每次 重新組合時(shí)都會(huì)改變。 為此党巾,我們需要一個(gè)地方來記住我們在上一個(gè)構(gòu)圖中使用的元素萎庭。 Compose 允許我們將值存儲在組合樹中,因此我們可以更新相應(yīng)的操作 以將【值或者狀態(tài)】 存儲在組合樹中齿拂。
每次 重構(gòu)時(shí)一些元素都會(huì)更新的原因是 有一個(gè)隱藏的副作用驳规。 副作用是在可組合函數(shù)的執(zhí)行之外可見的任何更改。
Remember
remember 給出了一個(gè)可組合的函數(shù)內(nèi)存创肥。
由記住計(jì)算的值將存儲在組合樹中达舒,并且只有在要記住的鍵發(fā)生變化時(shí)才會(huì)重新計(jì)算值朋。
您可以將 memory 視為將單個(gè)對象的存儲空間分配給函數(shù)叹侄,就像私有 val 屬性在對象中所做的那樣。
可組合函數(shù)可以使用 remember
可組合項(xiàng)記住單個(gè)對象昨登。系統(tǒng)會(huì)在初始組合期間將由 remember
計(jì)算的值存儲在組合中趾代,并在重組期間返回存儲的值。remember
既可用于存儲可變對象丰辣,又可用于存儲不可變對象撒强。