State adalah jantung Compose: tanpa state, UI tidak akan pernah berubah. State yang dikelola dengan baik membuat UI tetap responsif dan bebas bug; state yang dikelola buruk mengakibatkan recomposition berlebihan, kebocoran memori, atau kehilangan data saat rotasi. Bab ini membahas tiga konsep sentral: State<T> / MutableState<T>, remember, rememberSaveable, serta pola state hoisting yang memisahkan composable stateless dari stateful.

State<T> dan MutableState<T> #

State<T> adalah observable holder yang dapat dibaca di dalam composable. Setiap kali nilainya berubah, Compose menjadwalkan recomposition untuk semua composable yang membacanya. MutableState<T> adalah versi read-write; kita biasanya membuatnya lewat mutableStateOf(initial).

kotlin Contoh counter Compose
@Composable
fun Counter() {
    val count = remember { mutableStateOf(0) }
    Button(onClick = { count.value++ }) {
        Text("Klik: ${count.value}")
    }
}

Saat count.value diubah, Compose memanggil ulang Counter untuk menghasilkan UI baru dengan label tombol diperbarui. Tanpa remember, setiap recomposition akan membuat instance MutableState baru — counter tidak akan pernah naik.

remember dan rememberSaveable #

remember mempertahankan nilai selama Activity hidup. Saat Activity direcreate (mis. rotasi), instance remember ikut hilang. Untuk state yang harus bertahan lintas rotasi, kita memakai rememberSaveable:

kotlin rememberSaveable untuk bertahan rotasi
@Composable
fun FormLogin() {
    var email by rememberSaveable { mutableStateOf("") }
    var sandi by rememberSaveable { mutableStateOf("") }
    TextField(value = email, onValueChange = { email = it })
    TextField(value = sandi, onValueChange = { sandi = it }, visualTransformation = PasswordVisualTransformation())
}

rememberSaveable otomatis menyimpan ke Bundle dan memulihkan saat Activity direcreate. Untuk tipe kompleks (mis. data class), kita perlu menyediakan Saver atau menggunakan @Parcelize. Tanpa rememberSaveable, pengguna yang memutar layar saat mengetik form akan kehilangan semua input.

Simulasi State Lintas Recreate #

Cuplikan berikut memodelkan MutableState sebagai property delegate dengan listener dan SaveableHolder sebagai Bundle yang menyimpan nilai saat Activity recreate:

kotlin
// state_recomposition.kt
// Mensimulasikan state hoisting, mutableStateOf, dan rememberSaveable sebagai
// kelas ringkas yang mempertahankan nilai lintas "rotasi layar" (recreate).

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

// State yang memicu listener saat nilai diubah
class MutableState<T>(initial: T) : ReadWriteProperty<Any?, T> {
    private var value: T = initial
    private val listeners = mutableListOf<(T) -> Unit>()

    fun onChange(listener: (T) -> Unit) { listeners.add(listener) }

    override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        if (this.value != value) {
            this.value = value
            listeners.forEach { it(value) }
        }
    }
}

// Simulasi rememberSaveable: nilai disimpan ke "Bundle" dan dipulihkan saat
// "Activity" direcreate
class SaveableHolder {
    private val bundle = mutableMapOf<String, Any?>()

    fun save(key: String, value: Any?) { bundle[key] = value }
    fun <T> restore(key: String, default: T): T {
        @Suppress("UNCHECKED_CAST")
        return (bundle[key] as? T) ?: default
    }
}

// Composable yang state-nya di-hoist ke pemanggil
class CounterState(initial: Int) {
    private val state = MutableState(initial)
    init { state.onChange { println("  [State] count berubah -> $it") } }

    var count by state
    fun increment() { count++ }
    fun reset() { count = 0 }
}

// Simulasi: Activity recreate (rotasi), state dipulihkan dari SaveableHolder
fun main() {
    val saveable = SaveableHolder()
    println("=== Render pertama ===")
    val state1 = CounterState(0)
    state1.increment()
    state1.increment()
    state1.increment()
    println("Sebelum recreate: count=${state1.count}")
    saveable.save("count", state1.count)

    println("\n=== Activity direcreate (rotasi) ===")
    val restored = saveable.restore("count", 0)
    val state2 = CounterState(restored)
    println("Setelah recreate: count=${state2.count}")

    println("\n=== Tanpa SaveableHolder: state hilang ===")
    val state3 = CounterState(0)
    println("Default baru: count=${state3.count}")
}

Output yang Dihasilkan:

text
=== Render pertama ===
  [State] count berubah -> 1
  [State] count berubah -> 2
  [State] count berubah -> 3
Sebelum recreate: count=3

=== Activity direcreate (rotasi) ===
Setelah recreate: count=3

=== Tanpa SaveableHolder: state hilang ===
Default baru: count=0

Pada output, counter yang disimpan ke SaveableHolder sebelum Activity recreate tetap memiliki nilai 3 setelah dipulihkan. Tanpa SaveableHolder, instance baru dimulai dari 0 — inilah mengapa rememberSaveable penting untuk form pengguna.

State Hoisting #

Pola state hoisting memindahkan kepemilikan state dari composable ke pemanggilnya. Composable yang menerima value dan onChange menjadi stateless — dapat dipreview, diuji, dan dipakai ulang dengan mudah.

kotlin Composable stateless (state di-hoist)
@Composable
fun NameField(nama: String, onNamaChange: (String) -> Unit) {
    TextField(value = nama, onValueChange = onNamaChange)
}

Composable stateless tidak peduli darimana state berasal — bisa dari rememberSaveable, ViewModel, atau state global. Pemanggillah yang menentukan. Pemisahan ini juga membuat composable dapat di-reuse di berbagai context tanpa duplikasi logika state.

PolaContohKapan dipakai
Stateful (internal)remember { mutableStateOf(0) }UI sederhana yang state tidak dibagikan
Hoisted (stateless)value, onChange dari parameterKomponen reusable, form, dialog
ViewModel-drivenstateFlow.collectAsState()State lintas layar atau hidup lama

Referensi Lanjutan #

  • Jetpack Compose — rujukan Jetpack Compose untuk state management dan mekanisme recomposition

Selanjutnya #

State sudah tersimpan dengan benar, namun bagaimana menampilkan daftar panjang atau grid yang efisien? Chapter Compose Layouts akan membahas LazyColumn, LazyRow, dan rantai Modifier.