mutableStateOfはJetpack Composeの関数で、変更可能な監視可能な状態のコンテナを作成します。このコンテナ内の値が変更されると、Composeは自動的にこの状態を読み取っているすべてのコンポーネントの再コンポジションをトリガーします。mutableStateOfがなければ、データが変更されたときにUIをリアクティブに更新することはできません。Google Android Developers, 2026によると、mutableStateOfはComposeにおけるローカル状態の主要な構成要素です。
重要なポイント
mutableStateOfはcompose.runtimeパッケージの関数で、値を格納しCompose Runtimeに変更を通知できるMutableState<T>オブジェクトを作成します。シグネチャ: fun <T> mutableStateOf(value: T, policy: SnapshotMutationPolicy<T> = structuralEquality()): MutableState<T>。パラメータpolicyは、変更が重要とみなされるタイミングを決定します — 構造的等価性の場合、参照等価性の場合、または決してありません。
MutableStateは単一のプロパティvalueを持つインターフェースです: 読み取り用のgetterと書き込み用のsetterです。setterが呼び出されると、Compose Runtimeは変更をスナップショットに記録し、このState変数を読み取っているすべてのComposable関数を再コンポジションが必要としてマークします。このプロセスは単一のスナップショットサイクル内で同期的に発生し、カスケード変更中の中間状態を排除します。
パラメータpolicy — mutableStateOfの2番目の引数で、比較動作を定義します。structuralEquality()はequals()をチェックします — これがデフォルトの動作です。referentialEquality()は===(参照等価性)をチェックします。neverEqual()はすべての代入を変更とみなします。policyの選択は、同じ値を代入したときに再コンポジションがトリガーされるかどうかに影響します。
監視可能な状態を宣言する最も簡単な方法は、mutableStateOfをrememberとともに使用することです。rememberがないと、再コンポジションのたびに新しいStateが作成され、以前の変更はすべて失われます。rememberは、Composable関数がコンポジション内に留まる限り、同じMutableStateが一連の再コンポジションを生き残ることを保証します。
@Composable
fun Counter() {
// 委譲なし: .value経由で読み取り/書き込み
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("Count: ${count.value}")
}
}
@Composable
fun CounterDelegated() {
// 委譲あり: var + by = Property Delegation
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
2つのアプローチの違いは構文上のものです。Property Delegation(by)はKotlinの規約を使用します: コンパイラは読み取りと書き込みのためにgetValue()とsetValue()の呼び出しを生成します。これはcount.valueに直接アクセスするのと同等ですが、通常の変数を扱っているように見えます。どちらのアプローチも機能的には同一です: Composeは表記法に関係なく、getterでの読み取りとsetterでの書き込みを追跡します。
| 形式 | コード | 読み取り | 書き込み |
|---|---|---|---|
| 委譲なし | val count = mutableStateOf(0) | count.value | count.value = n |
| 委譲あり | var count by mutableStateOf(0) | count | count = n |
Kotlinの委譲プロパティメカニズムはComposeの機能ではなく、言語の組み込み機能です。任意のクラスがgetValue(thisRef, property)およびsetValue(thisRef, property, value)演算子を実装でき、その後そのインスタンスをbyキーワードで使用できます。MutableStateはまさにこのように動作します: getValueは現在の値を返し、setValueは新しい値を代入します。
重要な違い: val vs var。mutableStateOfはvalとvarの両方に代入できます。val(val count = mutableStateOf(0))の場合、MutableStateオブジェクト自体は不変ですが、そのvalueプロパティは変更できます。var(var count by mutableStateOf(0))の場合、委譲はプリミティブを扱っているかのような錯覚を作り出しますが、setterは実際にはMutableStateでsetValueを呼び出します。valとvarの選択は、.valueへの明示的アクセスと暗黙的アクセスの選択です。
State委譲はコードを簡素化する構文糖ですが、メカニズムは変わりません。Kotlinコンパイラはvar x by mutableStateOf(0)を、mutableStateOf.getValue()とmutableStateOf.setValue()を呼び出すgetter/setterに変換します。生成されたバイトコードでは、byを使用したvalとvarの間に違いはありません — どちらも同じMutableStateコンテナを介して動作します。
// Compose Stateのカスタムデリゲート
class ValidatedState<T>(initialValue: T) {
private val state = mutableStateOf(initialValue)
operator fun getValue(thisRef: Any?, property: KProperty<*>) = state.value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (value != state.value) {
state.value = value
}
}
}
@Composable
fun Test() {
var text by remember { ValidatedState("") }
}
Snapshotは、並行変更中のState読み取りの一貫性を保証するCompose Runtimeのメカニズムです。Composable関数がmutableStateOfを読み取ると、スナップショットは現在の値を記録します。コンポジション中に別の変更が同じStateに書き込まれた場合、スナップショットは書き込みを認識しますが、不整合なデータの読み取りは許可しません — 読み取りは常にスナップショット開始時に有効な値を返します。
setter mutableStateOf.value = newValueが呼び出されると、Compose Runtimeはすぐに再コンポジションをトリガーしません。代わりに、変更は現在のスナップショットに登録されます。スナップショットが適用される(フレーム境界で)と、Composeは変更されたStateのリストをスキャンし、読み取りコンポーネントをInvalidとしてマークします。次のフレームでのみ再コンポジションが開始されます。これにより、カスケード変更中にUIが何十回も再描画されないことが保証されます。
グローバルスナップショットとローカルスナップショット: デフォルトでは、mutableStateOfは自動的に適用されるグローバルスナップショットで動作します。SnapshottakeSnapshot()を介してローカルスナップショットを作成し、副作用なしで分離された読み取りを行うことができます。これは、変更を購読せずにStateを読み取る必要がある場合にModifier内で使用されます。このアプローチはパフォーマンスを最適化し、予期しない再コンポジションを防ぎます。
実際のシナリオを考えてみましょう — 3つのフィールド(メール、パスワード、ロード状態)を持つログインフォームです。3つのフィールドすべてがmutableStateOfを使用していますが、異なるpolicyと異なるネストレベルで使用しています。emailは委譲を使用し、passwordは直接アクセスを使用します。
data class LoginState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
// フォームの単一State, policy = referentialEquality
var formState by remember {
mutableStateOf(LoginState(), SnapshotMutationPolicy.referentialEquality())
}
val isValid = remember(formState) {
formState.email.contains("@") && formState.password.length() >= 6
}
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = formState.email,
onValueChange = { formState = formState.copy(email = it) },
label = { Text("メール") }
)
OutlinedTextField(
value = formState.password,
onValueChange = { formState = formState.copy(password = it) },
label = { Text("パスワード") },
visualTransformation = PasswordVisualTransformation()
)
Button(
onClick = { onLogin(formState.email, formState.password) },
enabled = isValid
) {
Text("ログイン")
}
}
}
この例では、mutableStateOfはカスタムデータクラスLoginStateとpolicy referentialEqualityで使用されています。これは、copy()を介して新しいLoginStateインスタンスが代入された場合にのみ再コンポジションがトリガーされることを意味します。isValidはformStateに基づいて計算され、それが変更された場合にのみ再計算されます。このアプローチは再コンポジションを明確に制御します: 各フォームフィールドは新しいコピーを作成することによってのみ変更されます。
よくある質問
mutableStateOfはスナップショット内で動作するCompose固有のコンテナです。StateFlowはkotlinx.coroutines.flowのもので、Composeに結びついていません。mutableStateOfは自動的に再コンポジションをトリガーしますが、StateFlowはcollectAsState()を必要とします。Composable内のUI状態には、mutableStateOfが推奨されます。
はい、mutableStateOfはComposable関数の外部で呼び出すことができますが、追跡されません。UIでリアクティビティを機能させるには、StateをComposable内で読み取る必要があります。多くのViewModelは、StateFlowを介してUIに状態を渡すためにMutableStateField(mutableStateOfのラッパー)を使用します。
Snapshotシステムが一貫性を保証します: 各再コンポジションはスナップショット開始時の一貫した状態を認識します。異なるスレッドからの変更はフレーム境界でアトミックに適用され、単一のコンポジション内での読み取り中のレースコンディションを排除します。
新しい値を代入します: count.value = 0(または委譲を使用する場合はcount = 0)。Stateを完全に再作成する必要がある場合は、キー付きのrememberを使用します: remember(key) { mutableStateOf(initial) } — キーが変更されると、Stateは新たに作成されます。
Composerは変更をグループ化するスナップショットを使用します: 1つのフレームで何百もの代入があっても、再コンポジションは1回だけ実行されます。非常に頻繁な更新(アニメーション)には、Animatableまたはanimate*AsStateを使用してください — これらはフレームごとの更新に最適化されています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。