【Jetpack Compose/Android】Stateの使い方!状態管理とremember
この記事からわかること
- Kotlin/Android Jetpack Composeの使い方
- 状態管理の方法
- State/mutableStateOfの違い
- remember/rememberSaveableの使い方
index
[open]
\ アプリをリリースしました /
環境
- Android Studio:Narwhal Feature Drop
- Kotlin:2.1.10
- Material3
- AGP:8.9.2
- Gradle:8.11.1
- Mac M1:Sequoia 15.6.1
Jetpack Compose自体の基本的な使用方法に関しては以下の記事を参考にしてください。
Composeの状態管理
Jetpack Composeでは「Composable関数」でUIが構築されています。Composable関数では従来のXMLベースのUI構築とは大きく異なり、宣言的にUIを記述できるようになっています。
Composable関数は状態(State)に基づいてUIが描画される仕組みになっています。TextFieldなどがイメージがつきやすいと思いますが、テキスト入力欄の文字列(=状態)の変化イベントを駆動としてComposableが自動的に再コンポーズする流れ(Recomposition)になっています。
状態はUIの引数に紐づけられます。Recompositionの条件は新しい引数でComposable関数を呼び出すことです。例えば以下のようにvalueが常に空になるようなTextFieldの定義では、値を入力してもRecompositionされないのでUIに変化が発生しません。
TextField(
value = "",
onValueChange = { },
modifier = Modifier.fillMaxWidth()
)
変化するようにするためにはStateやrememberなどの使い方を理解しておく必要があります。
State / MutableState
状態を保持し管理するためにはState / MutableStateクラスを活用します。実際には保持するためにはrememberとの併用が必要にはなります。State / MutableStateで状態を保持することで変化するたびにRecompositionされるようになります。逆に通常の変数を指定しても変化に応じてUIが更新されないので注意してください。
var text = ""
TextField(
value = text,
onValueChange = {
text = it
},
modifier = Modifier.fillMaxWidth()
)
期待通りに動作させるためには以下のように実装します。rememberの使い方は後述しますがmutableStateOfメソッドを使って初期化していることに注目してください。
// mutableStateOf型で状態を保持する
val nameState = remember { mutableStateOf("") }
TextField(
value = nameState.value,
onValueChange = {
nameState.value = it
},
modifier = Modifier.fillMaxWidth()
)
実際の値にはvalueを経由する必要があります。これをもう少し直感的に実装できる方法としてby(プロパティデリゲート)を使用した方法があります。byを使用することでMutableState<T>の.valueを自動的に委譲してくれるので、変数には中身の値(以下ならString型)になります。使用するためには明示的にimportを追加してください。
// 自動で追加されないので明示的に追加する
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
var name by rememberSaveable { mutableStateOf("") }
MutableStateオブジェクトを生成する記法は上記で紹介したものも含めて全部で3つあるようです。
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
その他の状態管理の種類
Composable関数からState / MutableStateを使用したいところですがViewModelではStateFlowやLiveDataなど従来のアプリ開発では一般的だったオブザーバブルな型を使用していることも多いと思います。その場合でも簡単に変換できるようにcollectAsState()やobserveAsState()などが用意されています。
// State に変換して保持することでRecompositionされる
// items: StateFlow
val itemsState = viewModel.items.collectAsState()
mutableStateListOf(SnapshotStateList)
Composeでコレクション型を扱っている場合に中身の追加・削除などの更新でUIを更新したい場合はMutableStateでは期待通りにUIが更新されません。MutableStateは中身の参照が変化したときに再コンポーズが発生するのでコレクション自体が変化しない限りトリガーにならないようです。
コレクション型の中身の変化で再コンポーズを実行させたい場合はSnapshotStateList型を使用します。mutableStateListOfでSnapshotStateList型でコレクションを管理することが可能です。
val items: SnapshotStateList<String> = mutableStateListOf<String>()
mutableStateOfをどうしても使用したい場合は以下のようにコレクション自体を上書きするようにしてください。またその場合は警告が発生するので@SuppressLint("MutableCollectionMutableState")を付与しておく必要があります。
var items by mutableStateOf(listOf<<String>())
fun addItem(item: String) {
items = items + item // 新しいリストを生成して上書き
}
また逆にSnapshotStateList型では参照を入れ替えたタイミングでは再Composeされないので明示的に空にして追加することで再Composeされるようになります。
// items = newList.toMutableStateList()
// SnapshotStateListなので上記では再Composeされないので明示的に空にして追加する
items.clear()
items.addAll(newList)
remember / rememberSaveable
Composable関数の状態(State)をReCompositionされても保持するための仕組みがrememberです。ReCompositionされるとComposable関数は再実行されますが、その際にローカル変数などはリセットされてしまいます。ただrememberを使用していたものは同じインスタンスを再利用してくれるので値が失われずに済みます。
// rememberを使用することでReCompositionされてもリセットされない
val nameState = remember { mutableStateOf("") }
TextField(
value = nameState.value,
onValueChange = {
nameState.value = it
},
modifier = Modifier.fillMaxWidth()
)
rememberはCompositionが破棄されるとリセットされます。そのため画面回転やダークモード変更などActivityが再生成されるようなタイミングで同様にリセットされてしまいます。これを防ぎたい場合はrememberSaveableを使用します。
val nameState = rememberSaveable { mutableStateOf("") }
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。






