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 — 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 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ție | Metodă de creare |
|---|---|---|
| State<T> | Container doar pentru citire | — |
| MutableState<T> | Container pentru citire și scriere | mutableStateOf() |
| MutableIntState | Int primitiv fără boxing | mutableIntStateOf() |
| MutableFloatState | Float primitiv fără boxing | mutableFloatStateOf() |
| SnapshotStateList | Listă observabilă | mutableStateListOf() |
| SnapshotStateMap | Mapare observabilă | mutableStateMapOf() |
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.
// 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ă)
}
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.
@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")
}
}
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.
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
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).
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.
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, 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.
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
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