MutableState — stare observabilă și mecanism de actualizare în Compose

Autor: IT Sectr Publicat: 2026-06-28 Timp de citire: 7 min

MutableState — este o interfață în Jetpack Compose care reprezintă un container pentru o valoare observabilă mutabilă. Este baza sistemului reactiv Compose: de fiecare dată când valoarea MutableState se modifică prin setter, Compose Runtime notifică toate componentele cititoare și declanșează recompoziția. Conform Google Android Developers, 2026, înțelegerea MutableState este obligatorie pentru lucrul corect cu starea în UI declarativ.

Principalele puncte

  • MutableState — interfață compose.runtime cu o singură proprietate value (getter + setter)
  • State — interfața părinte doar pentru citire, MutableState adaugă posibilitatea de scriere
  • Recompoziția se declanșează la apelarea setterului value în interiorul unui ciclu snapshot activ
  • SnapshotMutationPolicy determină când o modificare este considerată semnificativă pentru recompoziție
  • MutableIntState și analogii — versiuni primitive optimizate ale MutableState

Ce este MutableState în Jetpack Compose

MutableState — este o interfață din pachetul androidx.compose.runtime, care declară o singură proprietate: override var value: T. Getter returnează valoarea curentă, setter scrie una nouă și notifică Compose Runtime despre modificare. Interfața moștenește de la State<T>, unde value este accesibil doar pentru citire. Această arhitectură pe două niveluri permite separarea accesului: componentul care trebuie doar să citească valoarea primește State<T>, iar componentul-proprietar — MutableState<T>.

Implementarea implicită a MutableState este clasa internă SnapshotMutableStateImpl, care utilizează mecanismul de snapshot-uri pentru urmărirea modificărilor. Când setterul value este apelat, snapshot-ul curent înregistrează scrierea și marchează toate ObservedScope-urile înregistrate (zone de observare) ca invalide. Aceste zone (de obicei funcții Composable) vor fi recompoziționate în următorul cadru. Întregul proces are loc sincron și fără blocări datorită arhitecturii Lock-free snapshot.

State vs MutableState: State — este o interfață doar pentru citire, utilizată pentru API-urile publice ale componentelor. Când declarați un parametru al funcției Composable ca State<Int>, garantați că componentul poate citi, dar nu poate modifica starea. MutableState este utilizat în interiorul componentului-proprietar. O astfel de separare — una dintre practicile de bază ale Compose, prevenind modificările neautorizate.

Ierarhia State, MutableState și interfețelor derivate

Ierarhia interfețelor de stare în Compose are mai multe niveluri. În vârf — State<T> cu value doar pentru citire. Mai jos — MutableState<T> cu value pentru citire și scriere. Urmează versiunile primitive specializate: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState și altele, care evită autoambalarea (boxing) primitivelor.

MutableDoubleState și MutableLongState — tipuri mai puțin răspândite, dar existente. Interfețe de colecții: MutableListState — pentru urmărirea modificărilor în interiorul listei, MutableStateMap — pentru mapări. Fiecare dintre aceste interfețe este optimizată pentru un scenariu specific și extinde MutableState de bază cu metode suplimentare de lucru cu colecția.

SnapshotStateList și SnapshotStateMap — sunt implementări ale listelor și mapărilor mutabile, compatibile cu snapshot-urile. Ele permit urmărirea nu doar a înlocuirii valorii, ci și a modificărilor interne: adăugarea unui element în listă, ștergerea, modificarea unui element existent. Pentru astfel de structuri, mutableStateListOf() și mutableStateMapOf() creează colecții observabile corespunzătoare.

InterfațăDestinațieMetodă de creare
State<T>Container doar pentru citire
MutableState<T>Container pentru citire și scrieremutableStateOf()
MutableIntStateInt primitiv fără boxingmutableIntStateOf()
MutableFloatStateFloat primitiv fără boxingmutableFloatStateOf()
SnapshotStateListListă observabilămutableStateListOf()
SnapshotStateMapMapare observabilămutableStateMapOf()

SnapshotMutationPolicy: când să declanșăm recompoziția

SnapshotMutationPolicy — este o interfață care determină când o modificare a MutableState este considerată semnificativă. mutableStateOf acceptă policy ca al doilea argument. Implementări standard: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (consideră întotdeauna modificarea semnificativă). Pentru logică personalizată se poate implementa propriul policy.

structuralEquality() — comportament implicit. Compose compară noua valoare cu cea veche prin equals(). Dacă rezultatul este true — recompoziția NU se declanșează. Este convenabil pentru primitive și data class-uri, unde două instanțe cu aceleași câmpuri sunt considerate egale. Problemă: dacă data class conține List, equals() efectuează o comparație profundă, ceea ce poate fi costisitor pentru liste mari.

referentialEquality() — compară referințele prin ===. Recompoziția se declanșează doar la atribuirea unui alt obiect, chiar dacă conținutul este identic. Este optim pentru data class-uri imutabile, unde fiecare nouă instanță garantează o modificare. neverEqualPolicy() — consideră întotdeauna modificarea semnificativă, fără a efectua comparația. Util când setterul este apelat rar și nu este necesar să se piardă timp pe equals.

kotlin
    // Compararea politicilor în practică
data class User(val name: String, val age: Int)

@Composable
fun UserProfile() {
    // structuralEquality: recompoziție DOAR dacă datele s-au schimbat
    var user1 by remember {
        mutableStateOf(User("Alice", 30))
    }

    // referentialEquality: recompoziție la ORICE atribuire
    var user2 by remember {
        mutableStateOf(User("Bob", 25),
            SnapshotMutationPolicy.referentialEquality())
    }

    // user1: copy() cu aceleași câmpuri NU declanșează recompoziția
    // user2: chiar user2.copy() == user2 declanșează recompoziția (ref nouă)
}

State primitive: MutableIntState, MutableFloatState, MutableLongState

MutableIntState primitiv și analogii — sunt interfețe specializate care stochează primitive fără autoambalare (boxing). MutableState<Int> obișnuit stochează Int ca Integer, ceea ce la fiecare scriere creează un obiect în heap. MutableIntState stochează int (primitiv), eliminând complet overhead-ul de ambalare. Acest lucru este deosebit de important la actualizări de înaltă frecvență — contoare, poziții de scroll, valori de animație.

mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — funcții care creează MutableState primitive. Interfețele se numesc MutableIntState, MutableFloatState, MutableLongState. Ele extind MutableState<Int>, MutableState<Float> și MutableState<Long> respectiv, adăugând proprietatea intValue ca acces rapid la primitiv. În implementarea lor internă se utilizează AtomicInteger pentru citire/scriere fără blocări.

Aplicație: contoare (Int), poziții de scroll (Float offset), marcaje temporale (Long). În majoritatea scenariilor zilnice diferența de performanță este imperceptibilă, dar în LazyList cu mii de elemente și animații de tranziție, State-urile primitive oferă un avantaj sesizabil. Google recomandă utilizarea State-urilor primitive pentru scenarii tipice în locul mutableStateOf universal.

kotlin
@Composable
fun ScrollCounter() {
    // Rău: ambalare la fiecare actualizare
    var badCount by remember { mutableStateOf(0) }

    // Bine: fără ambalare, stocare primitivă
    var goodCount by remember { mutableIntStateOf(0) }

    // Utilizarea este identică
    Button(onClick = { goodCount++ }) {
        Text("Count: $goodCount")
    }
}

Exemple practice de lucru cu MutableState

Să examinăm componentul TodoList, unde MutableState este utilizat în două forme: ca variabile separate pentru starea de intrare și ca SnapshotStateList pentru lista dinamică de sarcini. Ambele utilizează delegarea pentru concizia codului.

kotlin
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("Adaugă") }
        }

        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 creează un SnapshotStateList — o listă mutabilă care urmărește modificările elementelor individuale. La apelarea items.add() și items[n] = newValue, Compose vede mutația și recompoziționează doar acele elemente LazyColumn care s-au modificat. inputText — un MutableState<String> obișnuit. Combinația a două tipuri de MutableState (individual și colecție) — un model tipic pentru ecrane cu formulare și liste.

Întrebări frecvente

Se poate folosi MutableState fără remember?

MutableState fără remember va fi creat din nou la fiecare recompoziție. Fiecare apel nou mutableStateOf creează un obiect nou, iar valoarea veche se pierde. Folosiți întotdeauna remember pentru a păstra State între recompoziții, cu excepția cazului în care State este creat în afara Composable (de exemplu, în ViewModel).

Cum se transformă MutableState într-o variabilă obișnuită?

Citiți .value o dată în afara snapshot-ului prin snapshot { }. Dar aceasta dezactivează reactivitatea — modificările nu vor mai declanșa recompoziția. Pentru citirea unică fără abonare, utilizați currentValue() în interiorul snapshot fără citire.

Ce este mai rapid: mutableStateOf sau mutableIntStateOf?

mutableIntStateOf este mai rapid deoarece nu necesită ambalarea int în Integer. La mii de actualizări pe secundă (animație, scroll) diferența poate ajunge la 30-50% din timpul de alocare. La actualizări rare (clicuri, introducere text) diferența este nesemnificativă.

Se poate folosi MutableState ca argument al unei funcții Composable?

Se poate, dar nu este recomandat. În loc de MutableState, transmiteți State (doar pentru citire) + lambda onValueChange. Aceasta implementează modelul State Hoisting și face componentul reutilizabil. Componentele care acceptă MutableState încalcă fluxul unidirecțional de date.

Cum se creează o implementare personalizată a MutableState?

Implementați interfața MutableState și furnizați override var value cu getter și setter. În setter puteți adăuga validare sau logging. Pentru compatibilitate inversă cu Compose Runtime, înfășurați implementarea în snapshotFlow sau utilizați snapshotIncrement.

Concluzii

  • MutableState — interfața de bază pentru starea observabilă mutabilă în Compose
  • State — versiune doar pentru citire pentru transmiterea datelor fără drept de modificare
  • SnapshotMutationPolicy gestionează condițiile de declanșare a recompoziției la modificare
  • State-uri primitive (MutableIntState etc.) elimină overhead-ul autoambalării
  • SnapshotStateList și SnapshotStateMap urmăresc modificările interne ale colecțiilor
  • Recompoziția se declanșează automat la orice apel al setterului value
  • Recomandare: utilizați MutableState pentru posesia stării și State pentru transmiterea în jos

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.

Discutați proiectul

Citiți și