mutableStateOf este o funcție în Jetpack Compose care creează un container de stare observabilă mutabilă. Când valoarea din acest container se schimbă, Compose declanșează automat recompoziția tuturor componentelor care citesc această stare. Fără mutableStateOf, UI nu s-ar putea actualiza reactiv la modificările datelor. Conform Google Android Developers, 2026, mutableStateOf este blocul de construcție principal pentru starea locală în Compose.
Principalele
mutableStateOf este o funcție din pachetul compose.runtime care creează un obiect MutableState<T> ce stochează o valoare și poate notifica Compose Runtime despre modificări. Semnătura: fun <T> mutableStateOf(value: T, policy: SnapshotMutationPolicy<T> = structuralEquality()): MutableState<T>. Parametrul policy determină când o modificare este considerată semnificativă — la egalitate structurală, la egalitate referențială sau niciodată.
MutableState este o interfață cu o singură proprietate value: getter pentru citire și setter pentru scriere. La apelarea setter-ului, Compose Runtime înregistrează modificarea într-un snapshot și marchează toate funcțiile Composable care citesc această variabilă State ca necesitând recompoziție. Acest proces are loc sincron într-un singur ciclu de snapshot, ceea ce elimină stările intermediare la modificări în cascadă.
Parameter policy — al doilea argument al mutableStateOf, care determină comportamentul la comparare. structuralEquality() verifică equals() — acesta este comportamentul implicit. referentialEquality() verifică === (egalitate referențială). neverEqual() consideră fiecare atribuire ca o modificare. Alegerea policy influențează dacă recompoziția va fi declanșată la atribuirea aceleiași valori.
Cel mai simplu mod de a declara stare observabilă este utilizarea mutableStateOf cu remember. Fără remember, la fiecare recompoziție s-ar crea un nou State și toate modificările anterioare s-ar pierde. remember garantează că același MutableState supraviețuiește unei serii de recompoziții atâta timp cât funcția Composable rămâne în compoziție.
@Composable
fun Counter() {
// Fără delegare: citire/scriere prin .value
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("Count: ${count.value}")
}
}
@Composable
fun CounterDelegated() {
// Cu delegare: var + by = Property Delegation
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
Diferența dintre cele două variante este sintactică. Property Delegation (by) folosește convenția Kotlin: compilatorul generează apeluri getValue() și setValue() pentru citire și scriere. Aceasta este echivalentă cu accesarea directă a count.value, dar arată ca lucrul cu o variabilă obișnuită. Ambele variante sunt funcțional identice: Compose urmărește citirea în getter și scrierea în setter, indiferent de forma de scriere.
| Formă | Cod | Citire | Scriere |
|---|---|---|---|
| Fără delegare | val count = mutableStateOf(0) | count.value | count.value = n |
| Cu delegare | var count by mutableStateOf(0) | count | count = n |
Mecanismul proprietăților delegate din Kotlin nu este o caracteristică a Compose, ci o capacitate încorporată a limbajului. Orice clasă poate implementa operatorii getValue(thisRef, property) și setValue(thisRef, property, value), după care instanța sa poate fi folosită cu cuvântul cheie by. MutableState exact așa funcționează: getValue returnează valoarea curentă, iar setValue atribuie una nouă.
Diferența importantă: val și var. mutableStateOf poate fi atribuit atât la val, cât și la var. În cazul val (val count = mutableStateOf(0)), obiectul MutableState în sine este imutabil, dar proprietatea sa value poate fi modificată. În cazul var (var count by mutableStateOf(0)), delegarea creează iluzia lucrului cu un primitiv, dar de fapt setter-ul apelează setValue pe MutableState. Alegerea între val și var este o alegere între accesul explicit și implicit la .value.
State Delegation este zahăr sintactic care simplifică codul, dar nu schimbă mecanica. Compilatorul Kotlin transpune var x by mutableStateOf(0) în getter/setter care apelează mutableStateOf.getValue() și mutableStateOf.setValue(). În bytecode-ul generat, nu există diferență între val și var cu by — ambele funcționează prin același container MutableState.
// Delegare personalizată pentru State în Compose
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 este un mecanism al Compose Runtime care asigură consistența citirii State la modificări paralele. Când o funcție Composable citește mutableStateOf, snapshot-ul înregistrează valoarea curentă. Dacă în timpul compoziției o altă modificare scrie în același State, snapshot-ul vede scrierea, dar nu permite citirea datelor inconsistente — citirea returnează întotdeauna valoarea actuală la momentul începerii snapshot-ului.
La apelarea setter-ului mutableStateOf.value = newValue, Compose Runtime nu declanșează imediat recompoziția. În schimb, modificarea este înregistrată în snapshot-ul curent. Când snapshot-ul este aplicat (la granița frame-ului), Compose parcurge lista de State-uri modificate și marchează componentele cititoare ca Invalid. Abia în următorul frame se declanșează recompoziția. Aceasta garantează că UI nu este redesenat de zeci de ori la modificări în cascadă.
Snapshot-uri globale și locale: în mod implicit, mutableStateOf funcționează într-un snapshot global care se aplică automat. Se poate crea un snapshot local prin Snapshot.takeSnapshot() pentru citire izolată fără efecte secundare. Aceasta este utilizat în interiorul Modifier, unde trebuie citit State, dar nu trebuie să te abonezi la modificări. Această abordare optimizează performanța și previne recompozițiile neașteptate.
Să analizăm un scenariu real — un formular de autentificare cu trei câmpuri: email, parolă și stare de încărcare. Toate cele trei câmpuri folosesc mutableStateOf, dar cu policy diferite și grad diferit de imbricare. email folosește delegare, parola — acces direct.
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 unic pentru formular, 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("Email") }
)
OutlinedTextField(
value = formState.password,
onValueChange = { formState = formState.copy(password = it) },
label = { Text("Parolă") },
visualTransformation = PasswordVisualTransformation()
)
Button(
onClick = { onLogin(formState.email, formState.password) },
enabled = isValid
) {
Text("Autentificare")
}
}
}
În acest exemplu, mutableStateOf este folosit cu o clasă de date personalizată LoginState și policy referentialEquality. Aceasta înseamnă că recompoziția se va declanșa doar la atribuirea unei noi instanțe LoginState prin copy(). isValid este calculat pe baza formState și se recalculează doar la modificarea acestuia. Această abordare oferă un control clar asupra recompozițiilor: fiecare câmp al formularului se modifică doar prin crearea unei noi copii.
Întrebări frecvente
mutableStateOf este un container specific Compose care funcționează în interiorul snapshot-urilor. StateFlow provine din kotlinx.coroutines.flow și nu este legat de Compose. mutableStateOf declanșează automat recompoziția, StateFlow necesită collectAsState(). Pentru starea UI în interiorul Composable, mutableStateOf este preferat.
Da, mutableStateOf poate fi apelat în afara funcției Composable, dar nu va fi urmărit. Pentru funcționarea reactivității în UI, trebuie citit State în interiorul Composable. Multe ViewModel-uri folosesc MutableStateField (o înfășurare peste mutableStateOf) pentru a transmite stare către UI prin StateFlow.
Snapshot sistemul garantează consistența: fiecare recompoziție vede starea coerentă la momentul începerii snapshot-ului. Modificările din thread-uri diferite se aplică atomic la granița frame-ului, ceea ce elimină race condition la citirea în cadrul unei compoziții.
Atribuiți o nouă valoare: count.value = 0 (sau count = 0 la delegare). Dacă este necesară recrearea completă a State — folosiți remember cu cheie: remember(key) { mutableStateOf(initial) } — la schimbarea cheii, State va fi creat din nou.
Composer folosește snapshot-uri care grupează modificările: chiar și cu o sută de atribuiri într-un singur frame, recompoziția se execută o singură dată. Pentru actualizări foarte frecvente (animații), folosiți Animatable sau animate*AsState — sunt optimizate pentru actualizări pe cadre.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și