MutableState はJetpack Composeにおけるインターフェイスで、変更可能な可視値のためのコンテナを表します。これはComposeの反応的システムの基礎です:MutableStateの値がセッタを通じて変更されるたびに、Compose Runtimeがすべての読み取りコンポーネントに通知し、再構成を発動します。Google Android Developers, 2026によると、宣言的UIで状態を正しく扱うにはMutableStateの理解が必須です。
ポイント
MutableState はaxandroidx.compose.runtimeパッケージのインターフェイスで、ひとつのプロパティを宣言します:override var value: T。getterは現在の値を返し、setterは新しい値を書き込み、Compose Runtimeに変更を通知します。このインターフェイスはState<T>を継承しており、valueは読み取り専用です。この二階層アーキテクチャによりアクセスを分離できます:値を読むことだけが必要なコンポーネントはState<T>を受け取り、所有者コンポーネントはMutableState<T>を受け取ります。
MutableStateのデフォルト実装は内部クラスSnapshotMutableStateImplで、変更を追跡するためにスナップショットメカニズムを使用します。valueのセッタが呼ばれると、現在のスナップショットが書き込みを記録し、すべての登録済みObservedScopeを無効としてマークします。これらのスコープ(通常はComposable関数)は次のフレームで再構成されます。Lock-free snapshotアーキテクチャにより、全ての処理が同期的かつロックフリーで行われます。
State vs MutableState:Stateは公開APIのための読み取り専用インターフェイスです。Composable関数パラメータをState<Int>として宣言すると、コンポーネントが状態を読むことができても変更はできないことを保証します。MutableStateは所有者コンポーネント内で使用されます。この分離は、不正な変更を防ぐComposeの基本的な実践のひとつです。
Composeの状態インターフェイスの階層にはいくつかのレベルがあります。最上位には読み取り専用のvalueを持つState<T>があります。その下には読み書き可能なvalueを持つMutableState<T>があります。さらにその下には専門化されたプリミティブバージョン(MutableIntState、MutableFloatState、MutableLongState、MutableBooleanStateなど)があり、プリミティブのボックス化を回避します。
MutableDoubleStateとMutableLongStateはあまり一般的ではありませんが、存在します。コレクションインターフェイス:MutableListState — リスト内の変更を追跡するため、MutableStateMap — マップのため。これらの各インターフェイスは特定のシナリオに向けて最適化され、基本のMutableStateを追加のコレクション操作メソッドで拡張します。
SnapshotStateListとSnapshotStateMapは、スナップショットと互換性のある変更可能なリストおよびマップの実装です。これらは単なる値の置き換えだけでなく、内部変更(リストへのアイテム追加、削除、既存アイテムの変更)を追跡できます。こうした構造に対しては、mutableStateListOf()とmutableStateMapOf()が対応する可視コレクションを作成します。
| インターフェイス | 目的 | 作成メソッド |
|---|---|---|
| State<T> | 読み取り専用コンテナ | — |
| MutableState<T> | 読み書き用コンテナ | mutableStateOf() |
| MutableIntState | ボックス化なしのプリミティブInt | mutableIntStateOf() |
| MutableFloatState | ボックス化なしのプリミティブFloat | mutableFloatStateOf() |
| SnapshotStateList | 可視リスト | mutableStateListOf() |
| SnapshotStateMap | 可視マップ | mutableStateMapOf() |
SnapshotMutationPolicy は、MutableStateの変更が重要とみなされる条件を決定するインターフェイスです。mutableStateOfは第2引数としてpolicyを受け取ります。標準実装:structuralEquality() (equals)、referentialEquality() (===)、neverEqualPolicy() (常に変更を重要とみなす)。カスタムロジックに対しては独自のポリシーを実装できます。
structuralEquality() — デフォルトの動作。Composeは新しい値と旧い値をequals()で比較します。結果がtrueなら、再構成は発動しません。これはプリミティブやdata classに便利で、同じフィールドを持つ2つのインスタンスが等しいとみなされます。問題点:data classがListを含む場合、equals()は深い比較を行い、大きなリストに対してコストが高くなる可能性があります。
referentialEquality() — ===で参照を比較します。再構成は、内容が同じでも、異なるオブジェクトが代入された場合にのみ発動します。これは、新しいインスタンスが変更を保証する不変データクラスに最適です。neverEqualPolicy() — 比較を行わずに常に変更を重要とみなします。セッタがめったに呼ばれ、equalsに時間をかける必要がない場合に便利です。
// Policy comparison in practice
data class User(val name: String, val age: Int)
@Composable
fun UserProfile() {
// structuralEquality: recomposition ONLY if data changed
var user1 by remember {
mutableStateOf(User("Alice", 30))
}
// referentialEquality: recomposition on ANY assignment
var user2 by remember {
mutableStateOf(User("Bob", 25),
SnapshotMutationPolicy.referentialEquality())
}
// user1: copy() with same fields does NOT trigger recomposition
// user2: even user2.copy() == user2 triggers recomposition (new ref)
}
プリミティブMutableIntStateなどは、ボックス化せずにプリミティブを保存する専門インターフェイスです。普通のMutableState<Int>はIntをIntegerとして保存し、書き込みごとにヒープ上にオブジェクトを作成します。MutableIntStateはint(プリミティブ)を保存し、ボックス化のオーバーヘッドを完全に解消します。これは高頻度の更新(カウンタ、スクロール位置、アニメーション値)に特に重要です。
mutableIntStateOf()、mutableFloatStateOf()、mutableLongStateOf() — プリミティブMutableStateを作成する関数です。インターフェイスはそれぞれMutableIntState、MutableFloatState、MutableLongStateと呼ばれます。これらはそれぞれMutableState<Int>、MutableState<Float>、MutableState<Long>を拡張し、プリミティブへの高速アクセスのためにintValueプロパティを追加します。内部実装はAtomicIntegerを使用してロックフリーの読み書きを実現しています。
使用例:カウンタ (Int)、スクロール位置 (Float offset)、タイムスタンプ (Long)。日常のほとんどのシナリオではパフォーマンス差は目にみえませんが、数千アイテムとトランジションアニメーションを持つLazyListでは、プリミティブStateは明らかなパフォーマンス向上を提供します。Googleは、一般的なmutableStateOfの代わりに、ティピカルなシナリオに対してプリミティブStateを使用することを推奨しています。
@Composable
fun ScrollCounter() {
// Bad: boxing on every update
var badCount by remember { mutableStateOf(0) }
// Good: no boxing, primitive storage
var goodCount by remember { mutableIntStateOf(0) }
// Usage is identical
Button(onClick = { goodCount++ }) {
Text("Count: $goodCount")
}
}
TodoListコンポーネントを考えます。ここではMutableStateが2つの形式で使用されます:入力状態のための独立した変数と、動的なタスクリストのためのSnapshotStateListです。どちらもコードを簡潔にするために委任を使用しています。
data class TodoItem(val id: Int, val text: String, val isDone: Boolean = false)
@Composable
fun TodoScreen() {
var inputText by remember { mutableStateOf("") }
val items = remember { mutableStateListOf() }
Column(modifier = Modifier.padding(16.dp)) {
Row {
TextField(
value = inputText,
onValueChange = { inputText = it }
)
Button(onClick = {
if (inputText.isNotBlank()) {
items.add(TodoItem(items.size, inputText))
inputText = ""
}
}) { Text("Add") }
}
LazyColumn {
items(items) { item ->
Row(modifier = Modifier.fillMaxWidth().clickable {
val idx = items.indexOf(item)
items[idx] = item.copy(isDone = !item.isDone)
}) {
Checkbox(checked = item.isDone, onCheckedChange = null)
Text(item.text)
}
}
}
}
}
mutableStateListOfはSnapshotStateListを作成します — 個々の要素の変更を追跡する変更可能なリストです。items.add()やitems[n] = newValueが呼ばれると、Composeが変更を検知し、変化したLazyColumnの要素だけを再構成します。inputTextは通常のMutableState<String>です。二つのMutableStateタイプ(単一およびコレクション)の組み合わせは、フォームとリストを含む画面のタイピカルなパターンです。
よくある質問
MutableStateをrememberなしで使用すると、再構成ごとに新しく作成されます。mutableStateOfを新たに呼ぶたびに新しいオブジェクトが作成され、旧い値は失われます。StateがComposableの外(ViewModelなど)で作成されない限り、再構成間でStateを保存するために常にrememberを使用してください。
snapshot { }を介してスナップショットの外で.valueを一度読み取ってください。ただし、これは反応性を無効にします — 変更が再構成を発動しなくなります。サブスクリプションなしの一回の読み取りには、読み取りなしのスナップショット内でcurrentValue()を使用してください。
mutableIntStateOfのほうが高速です。intをIntegerにボックスする必要がないからです。秒間に数千回の更新(アニメーション、スクロール)がある場合、割り当て時間で30-50%の差が出ることがあります。めったな更新(クリック、テキスト入力)の場合、差はほとんどありません。
可能ですが、推奨されません。MutableStateの代わりに、State(読み取り専用)+ onValueChangeラムダを渡してください。これはState Hoistingパターンを実装し、コンポーネントを再利用可能にします。MutableStateを受け入れるコンポーネントは、単方向データフローに違反します。
MutableStateインターフェイスを実装し、getterとsetterを持つoverride var valueを提供してください。setterには検証やロギングを追加できます。Compose Runtimeとの互換性のために、カスタム実装をsnapshotFlowでラップするか、snapshotIncrementを使用してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。