Jetpack Compose 筆記 (二) - 狀態管理
學習 Android APP 開發,這次採用的是 Jetpack Compose 的聲明式 UI 框架,這篇文章記錄著狀態管理的一些介紹

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(
value = "",
onValueChange = {},
label = { Text("Name") }
)
}
}
上面這一個段程式目前執行起來,將沒有任何的作用,輸入欄位也無法輸入,但其實這樣的狀況並不是無法輸入,而是 value 的數值並不會自動的更改,它會在 value 的數值變更後自動的重組 UI。所以我們這邊就會需要建立一個表示該狀態的值,然後再 onValueChange 事件當中去更新我們的值。
// by 委派需要 import 以下
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
var name: String by remember { mutableStateOf("") }
Text(
text = "Hello",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
}
}
這裡使用 MutableStateOf
建立一個可變狀態,它是 Compose 中的一種可觀察類型,對於該值做任何的修改,都將對於讀取該值的 Composable 進行重組作業,而 remember
的作用在於重組時保留該值狀態,若沒有 remember
每次重組時,都將初始化成空字串狀態。
雖然remember
可協助您在各次重組間保留狀態,但只要設定有所變更,狀態就無法保留。針對這種情況,您必須使用rememberSaveable
。rememberSaveable
會自動儲存可儲存在Bundle
中的任何值。其他值可在自訂儲存器物件中傳送。
狀態提升
在說明狀態提升前,先談談有狀態及無狀態,上面的 HelloContent 這個 Composable 是屬於有狀態的組合項,利用了 remember
在內部來保留 name 的狀態。這樣的優點是當呼叫端不需要管理狀態也可以使用,但缺點是不易重覆使用,以及難以測試。
而無狀態的組合項指的是不含任何狀態的 Composable,達成無狀態的方式就是使用狀態提升。
開發可重複使用的可組合項時,通常會想同時提供有狀態和無狀態的版本。有狀態版本對於不考慮狀態的呼叫端來說很方便,而對於需要控制或提升狀態的呼叫端來說,則一定要使用無狀態版本。
Jetpack Compose 中最常使用的狀態提升是將狀態變數替換成兩個參數
value: T
:目前顯示的數值onValueChange: (T) -> Unit
:要求變更值的事件,其中T
是提議的新值
以下是官方說明這種方式的重要屬性
- 單一真實資訊來源:採用移動而非複製的方式處理狀態,以確保真實資訊來源只有一個。這有助於避免錯誤。
- 封裝:必須是「有狀態」的可組合項才能修改狀態。這完全屬於內部。
- 可共用:提升過的狀態可讓多個可組合項共用。使用提升即可在其他可組合項中讀取
name
。 - 可攔截:無狀態可組合項的呼叫端可在變更狀態前決定忽略或修改事件。
- 已分離:無狀態
ExpandingCard
的狀態可以儲存在任何位置。舉例來說,現在可以將name
移至ViewModel
中。
@Composable
fun HelloScreen() {
var name by rememberSaveable { mutableStateOf("") }
HelloContent(name = name, onNameChange = { name = it })
}
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello, $name",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChange = onNameChange,
label = { Text("Name") }
)
}
}
將狀態從 HelloContent 分離出來可以更輕易的重覆使用。

重點:提升狀態時,以下三項規則可協助釐清狀態的移動方向:
- 狀態「至少」該提升至使用該狀態的所有可組合項的最低共同父項 (讀取)。
- 狀態「至少」該提升至該狀態可以變更的最高層級 (寫入)。
- 如果兩個狀態為了回應同一事件而變更,則應一併提升。
您可以將狀態提升到比規則要求更高的層級,但降低狀態會導致難以或無法跟隨單向資料流。