狀態以及 Jetpack Compose 如何使用和操作狀態。
在我們深入研究之前,定義狀態到底是什么很有用。 從本質上講,應用程序中的狀態是任何可以隨時間變化的值。 這是一個非常廣泛的定義,包括從 Room 數據庫到類上的變量的所有內容。
目標:
什么是單向數據流
如何在 UI 中考慮狀態和事件
如何在 Compose 中使用 Architecture Component 的 ViewModel 和 LiveData 來管理狀態
Compose 如何使用狀態來繪制屏幕
何時將狀態移動到調用者
如何在 Compose 中使用內部狀態
如何使用 State<T> 將狀態與 Compose 集成
使用單向數據流
為了幫助解決非結構化狀態的這些問題,我們引入了包含 ViewModel 和 LiveData 的 Android 架構組件。
ViewModel 允許您從 UI 中提取狀態并定義 UI 可以調用以更新該狀態的事件
官方的例子理解原理:
ViewModel 還公開了一個事件:onNameChanged。 此事件由 UI 調用以響應用戶事件,例如每當 EditText 的文本更改時此處會發生什么。
回到我們之前討論過的 UI 更新循環,我們可以看到這個 ViewModel 如何與事件和狀態結合在一起。
事件 – onNameChanged 在文本輸入更改時由 UI 調用
更新狀態 - onNameChanged 進行處理,然后設置 _name 的狀態
顯示狀態 - 調用名稱的觀察者,通知 UI 狀態變化
通過以這種方式構建我們的代碼,我們可以認為事件“向上”流向 ViewModel。 然后,為了響應事件,ViewModel 將進行一些處理并可能更新狀態。 當狀態更新時,它會“向下”流向
這種模式稱為單向數據流。 單向數據流是一種狀態向下流動而事件向上流動的設計。 通過以這種方式構建我們的代碼,我們獲得了一些優勢:
- 可測試性——通過將狀態與顯示它的 UI 分離,可以更輕松地測試 ViewModel 和 Activity
- 狀態封裝——因為狀態只能在一個地方(ViewModel)更新,隨著 UI 的增長,你不太可能引入部分狀態更新錯誤
- UI 一致性——所有狀態更新都通過使用可觀察狀態持有者立即反映在 UI 中
因此,雖然這種方法確實添加了更多代碼,但使用單向數據流處理復雜的狀態和事件往往更容易、更可靠。
單向數據流是一種事件向上流動而狀態向下流動的設計。
例如,在 ViewModel 中,事件通過來自 UI 的方法調用傳遞,而狀態使用 LiveData 向下流動。
它不僅僅是描述 ViewModel 的術語——任何事件向上流動和狀態下降的設計都是單向的。
如何使用 ViewModel 在 Compose 中使用單向數據流。
上面理論,使用 ViewModel 和 LiveData 探索了 Android View 系統中的單向數據流。
StateCodeLab 項目 解讀
TodoScreen.kt – 這些可組合項直接與狀態交互,我們將在探索 compose 狀態時編輯此文件。
TodoComponents.kt – 這些可組合定義了我們將用于構建 TodoScreen 的可重用 UI 位。 您無需編輯這些可組合項即可完成此 Codelab。
這種文件劃分有點隨意,將 TodoScreen.kt 中的代碼集中在狀態上。 在實踐中,這些組合項可能位于同一個文件中,或者分布在多個文件中,具體取決于您在項目中如何使用它們。
-
TodoScreen
函數
這個可組合顯示一個可編輯的 TODO 列表,但它沒有任何自己的狀態。 請記住,狀態是任何可以更改的值——但 TodoScreen 的任何參數都不能修改。
items – 要顯示在屏幕上的不可變項目列表
onAddItem – 用戶請求添加項目時的事件
onRemoveItem – 用戶請求刪除項目時的事件
事實上,這個可組合是無狀態的。 它只顯示傳入的項目列表,無法直接編輯列表。 相反,它傳遞了兩個可以請求更改的事件 onRemoveItem 和 onAddItem。
這就提出了一個問題:如果它是無狀態的,它如何顯示可編輯列表? 它通過使用一種稱為狀態提升的技術來做到這一點。 狀態提升是向上移動狀態以使組件無狀態的模式。 無狀態組件更容易測試,往往有更少的錯誤,并提供更多的重用機會。
事件 – 當用戶請求添加或刪除項目時 TodoScreen 調用 onAddItem 或 onRemoveItem
更新狀態——TodoScreen 的調用者可以通過更新狀態來響應這些事件
顯示狀態 - 當狀態更新時,TodoScreen 將使用新項目再次調用,并且可以在屏幕上顯示它們
調用者負責確定在何處以及如何保持此狀態。 它可以存儲項目但有意義,例如在內存中或從 Room 數據庫中讀取它們。 TodoScreen 與狀態的管理方式完全分離。
使用這個 ViewModel 從 TodoScreen 提升狀態。 完成后,我們將創建一個單向數據流設計
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 在啟動新 Compose 項目時創建的默認主題。
Surface 為應用程序添加背景,并配置文本顏色。
Flow the events up 事件向上流動
Kotlin 提示
您還可以使用方法引用語法生成一個調用單個方法的 lambda。 這將從方法調用中創建一個 lambda。 使用方法引用語法,上面的 onAddItem 也可以表示為 onAddItem = todoViewModel::addItem。
Pass the state down 向下傳遞狀態
現在我們已經探索了如何使用 compose 和 ViewModels 來構建單向數據流,讓我們來探索 compose 如何在內部與狀態交互。
有狀態的可組合是一種可以隨時間改變的狀態組合。
重新組合是再次運行相同的組合以在其數據發生變化時更新樹的過程
Compose 生成一棵樹,但它與您可能熟悉的 Android 視圖系統中的 UI 樹有點不同。 compose 生成了一棵可組合的樹,而不是一棵 UI 小部件樹。
我們不希望每次 重新組合時都會改變。 為此,我們需要一個地方來記住我們在上一個構圖中使用的元素。 Compose 允許我們將值存儲在組合樹中,因此我們可以更新相應的操作 以將【值或者狀態】 存儲在組合樹中。
每次 重構時一些元素都會更新的原因是 有一個隱藏的副作用。 副作用是在可組合函數的執行之外可見的任何更改。
Remember
remember 給出了一個可組合的函數內存。
由記住計算的值將存儲在組合樹中,并且只有在要記住的鍵發生變化時才會重新計算。
您可以將 memory 視為將單個對象的存儲空間分配給函數,就像私有 val 屬性在對象中所做的那樣。
可組合函數可以使用 remember
可組合項記住單個對象。系統會在初始組合期間將由 remember
計算的值存儲在組合中,并在重組期間返回存儲的值。remember
既可用于存儲可變對象,又可用于存儲不可變對象。