MutableState — adalah antarmuka di Jetpack Compose yang mewakili wadah untuk nilai yang dapat diamati dan berubah. Ini adalah dasar dari sistem reaktif Compose: setiap kali nilai MutableState berubah melalui setter, Compose Runtime memberi tahu semua komponen pembaca dan memicu rekomposisi. Menurut Google Android Developers, 2026, memahami MutableState adalah wajib untuk bekerja dengan benar dengan status di UI deklaratif.
Poin utama
MutableState — adalah antarmuka dari paket androidx.compose.runtime yang mendeklarasikan satu properti: override var value: T. Getter mengembalikan nilai saat ini, setter menulis yang baru dan memberi tahu Compose Runtime tentang perubahan. Antarmuka ini mewarisi dari State<T>, di mana value hanya dapat dibaca. Arsitektur dua tingkat ini memungkinkan pemisahan akses: komponen yang hanya perlu membaca nilai mendapatkan State<T>, dan komponen pemilik — MutableState<T>.
Implementasi default MutableState adalah kelas internal SnapshotMutableStateImpl, yang menggunakan mekanisme snapshot untuk melacak perubahan. Ketika setter value dipanggil, snapshot saat ini mencatat penulisan dan menandai semua ObservedScope (area observasi) yang terdaftar sebagai tidak valid. Area-area ini (biasanya fungsi Composable) akan direkomposisi pada frame berikutnya. Seluruh proses terjadi secara sinkron dan tanpa penguncian berkat arsitektur Lock-free snapshot.
State vs MutableState: State — adalah antarmuka hanya-baca, digunakan untuk API publik komponen. Ketika Anda mendeklarasikan parameter fungsi Composable sebagai State<Int>, Anda menjamin bahwa komponen dapat membaca tetapi tidak dapat mengubah status. MutableState digunakan di dalam komponen pemilik. Pemisahan semacam ini — salah satu praktik dasar Compose — mencegah perubahan yang tidak sah.
Hierarki antarmuka status di Compose memiliki beberapa tingkat. Di puncak — State<T> dengan value hanya-baca. Di bawah — MutableState<T> dengan value baca-tulis. Selanjutnya adalah versi primitif khusus: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState dan lainnya, yang menghindari auto-boxing primitif.
MutableDoubleState dan MutableLongState — tipe yang kurang umum tetapi tetap ada. Antarmuka koleksi: MutableListState — untuk melacak perubahan di dalam daftar, MutableStateMap — untuk peta. Masing-masing antarmuka ini dioptimalkan untuk skenario tertentu dan memperluas MutableState dasar dengan metode tambahan untuk bekerja dengan koleksi.
SnapshotStateList dan SnapshotStateMap — adalah implementasi dari daftar dan peta yang dapat diubah, kompatibel dengan snapshot. Mereka memungkinkan pelacakan tidak hanya penggantian nilai, tetapi juga perubahan internal: penambahan elemen ke daftar, penghapusan, modifikasi elemen yang ada. Untuk struktur seperti itu, mutableStateListOf() dan mutableStateMapOf() membuat koleksi yang dapat diamati sesuai.
| Antarmuka | Tujuan | Metode pembuatan |
|---|---|---|
| State<T> | Wadah hanya-baca | — |
| MutableState<T> | Wadah baca-tulis | mutableStateOf() |
| MutableIntState | Int primitif tanpa boxing | mutableIntStateOf() |
| MutableFloatState | Float primitif tanpa boxing | mutableFloatStateOf() |
| SnapshotStateList | Daftar yang dapat diamati | mutableStateListOf() |
| SnapshotStateMap | Peta yang dapat diamati | mutableStateMapOf() |
SnapshotMutationPolicy — adalah antarmuka yang menentukan kapan perubahan MutableState dianggap signifikan. mutableStateOf menerima policy sebagai argumen kedua. Implementasi standar: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (selalu menganggap perubahan signifikan). Untuk logika kustom, Anda dapat mengimplementasikan policy Anda sendiri.
structuralEquality() — perilaku default. Compose membandingkan nilai baru dengan yang lama melalui equals(). Jika hasilnya true — rekomposisi TIDAK dipicu. Ini nyaman untuk primitif dan data class, di mana dua instance dengan bidang yang sama dianggap sama. Masalah: jika data class berisi List, equals() melakukan perbandingan mendalam, yang bisa mahal untuk daftar besar.
referentialEquality() — membandingkan referensi melalui ===. Rekomposisi hanya dipicu saat menetapkan objek lain, bahkan jika kontennya identik. Ini optimal untuk data class yang tidak dapat diubah, di mana setiap instance baru menjamin perubahan. neverEqualPolicy() — selalu menganggap perubahan signifikan tanpa melakukan perbandingan. Berguna ketika setter jarang dipanggil dan tidak perlu membuang waktu untuk equals.
// Perbandingan kebijakan dalam praktik
data class User(val name: String, val age: Int)
@Composable
fun UserProfile() {
// structuralEquality: rekomposisi HANYA jika data berubah
var user1 by remember {
mutableStateOf(User("Alice", 30))
}
// referentialEquality: rekomposisi pada SETIAP penugasan
var user2 by remember {
mutableStateOf(User("Bob", 25),
SnapshotMutationPolicy.referentialEquality())
}
// user1: copy() dengan bidang yang sama TIDAK memicu rekomposisi
// user2: bahkan user2.copy() == user2 memicu rekomposisi (ref baru)
}
MutableIntState primitif dan sejenisnya — adalah antarmuka khusus yang menyimpan primitif tanpa auto-boxing. MutableState<Int> biasa menyimpan Int sebagai Integer, yang pada setiap penulisan membuat objek di heap. MutableIntState menyimpan int (primitif), sepenuhnya menghilangkan overhead pembungkusan. Ini sangat penting pada pembaruan frekuensi tinggi — penghitung, posisi scroll, nilai animasi.
mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — fungsi yang membuat MutableState primitif. Antarmuka tersebut bernama MutableIntState, MutableFloatState, MutableLongState. Mereka memperluas MutableState<Int>, MutableState<Float> dan MutableState<Long> masing-masing, menambahkan properti intValue sebagai akses cepat ke primitif. Dalam implementasi internal mereka, AtomicInteger digunakan untuk baca/tulis tanpa kunci.
Penerapan: penghitung (Int), posisi scroll (Float offset), stempel waktu (Long). Di sebagian besar skenario sehari-hari, perbedaan kinerja tidak terlihat, tetapi di LazyList dengan ribuan elemen dan animasi transisi, State primitif memberikan peningkatan yang nyata. Google merekomendasikan penggunaan State primitif untuk skenario tipikal daripada mutableStateOf universal.
@Composable
fun ScrollCounter() {
// Buruk: pembungkusan pada setiap pembaruan
var badCount by remember { mutableStateOf(0) }
// Baik: tanpa pembungkusan, penyimpanan primitif
var goodCount by remember { mutableIntStateOf(0) }
// Penggunaan identik
Button(onClick = { goodCount++ }) {
Text("Count: $goodCount")
}
}
Mari kita lihat komponen TodoList, di mana MutableState digunakan dalam dua bentuk: sebagai variabel terpisah untuk status input dan sebagai SnapshotStateList untuk daftar tugas dinamis. Keduanya menggunakan delegasi untuk keringkasan kode.
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("Tambah") }
}
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 membuat SnapshotStateList — daftar yang dapat diubah yang melacak perubahan elemen individual. Saat items.add() dan items[n] = newValue dipanggil, Compose melihat mutasi dan merekomposisi hanya elemen LazyColumn yang berubah. inputText — MutableState<String> biasa. Kombinasi dua tipe MutableState (tunggal dan koleksi) — pola umum untuk layar dengan formulir dan daftar.
Pertanyaan yang Sering Diajukan
MutableState tanpa remember akan dibuat ulang pada setiap rekomposisi. Setiap panggilan baru mutableStateOf membuat objek baru, dan nilai lama hilang. Selalu gunakan remember untuk mempertahankan State antar rekomposisi, kecuali jika State dibuat di luar Composable (misalnya di ViewModel).
Baca .value sekali di luar snapshot melalui snapshot { }. Tetapi ini menonaktifkan reaktivitas — perubahan tidak lagi memicu rekomposisi. Untuk pembacaan satu kali tanpa langganan, gunakan currentValue() di dalam snapshot tanpa membaca.
mutableIntStateOf lebih cepat karena tidak memerlukan pembungkusan int ke Integer. Pada ribuan pembaruan per detik (animasi, scroll) perbedaannya bisa mencapai 30-50% dari waktu alokasi. Pada pembaruan jarang (klik, input teks) perbedaannya tidak signifikan.
Bisa, tetapi tidak disarankan. Alih-alih MutableState, kirimkan State (hanya-baca) + lambda onValueChange. Ini mengimplementasikan pola State Hoisting dan membuat komponen dapat digunakan kembali. Komponen yang menerima MutableState melanggar aliran data searah.
Implementasikan antarmuka MutableState dan berikan override var value dengan getter dan setter. Di setter Anda dapat menambahkan validasi atau pencatatan. Untuk kompatibilitas mundur dengan Compose Runtime, bungkus implementasi Anda dalam snapshotFlow atau gunakan snapshotIncrement.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga