MutableState — stato osservabile e meccanismo di aggiornamento in Compose

Autore: IT Sectr Pubblicato: 2026-06-28 Tempo di lettura: 7 min

MutableState è un'interfaccia in Jetpack Compose che rappresenta un contenitore per un valore mutabile osservabile. È il fondamento del sistema reattivo di Compose: ogni volta che il valore di MutableState cambia tramite il setter, Compose Runtime notifica tutti i componenti lettori e attiva la ricomposizione. Secondo Google Android Developers, 2026, comprendere MutableState è essenziale per lavorare correttamente con lo stato in una UI dichiarativa.

Punti chiave

  • MutableState — interfaccia compose.runtime con una singola proprietà value (getter + setter)
  • State — interfaccia padre di sola lettura, MutableState aggiunge capacità di scrittura
  • Ricomposizione attivata quando il setter di value viene chiamato all'interno di un ciclo snapshot attivo
  • SnapshotMutationPolicy determina quando una modifica è considerata significativa per la ricomposizione
  • MutableIntState e simili — versioni primitive ottimizzate di MutableState

Cos'è MutableState in Jetpack Compose

MutableState è un'interfaccia del pacchetto androidx.compose.runtime che dichiara una singola proprietà: override var value: T. Il getter restituisce il valore corrente, il setter ne scrive uno nuovo e notifica Compose Runtime della modifica. L'interfaccia eredita da State<T>, dove value è in sola lettura. Questa architettura a due livelli consente di separare l'accesso: un componente che deve solo leggere il valore riceve State<T>, mentre il componente proprietario riceve MutableState<T>.

L'implementazione predefinita di MutableState è la classe interna SnapshotMutableStateImpl, che utilizza un meccanismo di snapshot per tracciare le modifiche. Quando viene chiamato il setter di value, lo snapshot corrente registra la scrittura e segna tutti gli ObservedScope registrati come non validi. Questi ambiti (di solito funzioni Composable) verranno ricomposti nel fotogramma successivo. L'intero processo avviene in modo sincrono e senza blocchi grazie all'architettura Lock-free snapshot.

State vs MutableState: State è un'interfaccia di sola lettura utilizzata per le API pubbliche dei componenti. Quando dichiari un parametro di funzione Composable come State<Int>, garantisci che il componente possa leggere ma non modificare lo stato. MutableState viene utilizzato all'interno del componente proprietario. Questa separazione è una delle pratiche di base di Compose che previene modifiche non autorizzate.

Gerarchia di State, MutableState e interfacce derivate

La gerarchia delle interfacce di stato in Compose ha diversi livelli. Al vertice c'è State<T> con value in sola lettura. Sotto c'è MutableState<T> con value in lettura-scrittura. Più in basso vengono le versioni primitive specializzate: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState e altre, che evitano il boxing dei primitivi.

MutableDoubleState e MutableLongState sono meno comuni ma esistono anch'essi. Interfacce di collezioni: MutableListState — per tracciare modifiche all'interno di una lista, MutableStateMap — per mappe. Ognuna di queste interfacce è ottimizzata per uno scenario specifico ed estende la MutableState di base con metodi aggiuntivi di manipolazione delle collezioni.

SnapshotStateList e SnapshotStateMap sono implementazioni di liste e mappe mutabili compatibili con snapshot. Consentono di tracciare non solo la sostituzione del valore, ma le modifiche interne: aggiunta di un elemento a una lista, rimozione, modifica di un elemento esistente. Per tali strutture, mutableStateListOf() e mutableStateMapOf() creano le corrispondenti collezioni osservabili.

InterfacciaScopoMetodo di creazione
State<T>Contenitore di sola lettura
MutableState<T>Contenitore lettura-scritturamutableStateOf()
MutableIntStateInt primitivo senza boxingmutableIntStateOf()
MutableFloatStateFloat primitivo senza boxingmutableFloatStateOf()
SnapshotStateListLista osservabilemutableStateListOf()
SnapshotStateMapMappa osservabilemutableStateMapOf()

SnapshotMutationPolicy: quando attivare la ricomposizione

SnapshotMutationPolicy è un'interfaccia che determina quando una modifica in MutableState è considerata significativa. mutableStateOf accetta policy come secondo argomento. Implementazioni standard: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (considera sempre la modifica significativa). È possibile implementare una politica personalizzata per logica propria.

structuralEquality() — comportamento predefinito. Compose confronta il nuovo valore con il vecchio tramite equals(). Se il risultato è true, la ricomposizione NON viene attivata. Ciò è comodo per primitivi e data class, dove due istanze con gli stessi campi sono considerate uguali. Problema: se una data class contiene una List, equals() esegue un confronto profondo, che può essere costoso per liste grandi.

referentialEquality() — confronta i riferimenti tramite ===. La ricomposizione viene attivata solo quando viene assegnato un oggetto diverso, anche se il contenuto è identico. È ottimale per data class immutabili dove ogni nuova istanza garantisce una modifica. neverEqualPolicy() — considera sempre la modifica significativa senza eseguire il confronto. Utile quando il setter viene chiamato raramente e non c'è bisogno di spendere tempo in equals.

kotlin
    // Policy comparison in practice
data class User(val name: String, val age: Int)

@Composable
fun UserProfile() {
    // structuralEquality: recomposition ONLY if data changed
    var user1 by remember {
        mutableStateOf(User("Alice", 30))
    }

    // referentialEquality: recomposition on ANY assignment
    var user2 by remember {
        mutableStateOf(User("Bob", 25),
            SnapshotMutationPolicy.referentialEquality())
    }

    // user1: copy() with same fields does NOT trigger recomposition
    // user2: even user2.copy() == user2 triggers recomposition (new ref)
}

State primitivi: MutableIntState, MutableFloatState, MutableLongState

MutableIntState primitivo e simili sono interfacce specializzate che memorizzano primitivi senza boxing. Un normale MutableState<Int> memorizza Int come Integer, che crea un oggetto sull'heap a ogni scrittura. MutableIntState memorizza int (primitivo), eliminando completamente l'overhead del boxing. Ciò è particolarmente importante per aggiornamenti ad alta frequenza — contatori, posizioni di scorrimento, valori di animazione.

mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — funzioni che creano MutableState primitivi. Le interfacce si chiamano MutableIntState, MutableFloatState, MutableLongState. Estendono rispettivamente MutableState<Int>, MutableState<Float> e MutableState<Long>, aggiungendo la proprietà intValue per l'accesso rapido al primitivo. La loro implementazione interna utilizza AtomicInteger per lettura/scrittura senza blocco.

Utilizzo: contatori (Int), posizioni di scorrimento (Float offset), timestamp (Long). Nella maggior parte degli scenari quotidiani la differenza di prestazioni è impercettibile, ma in LazyList con migliaia di elementi e animazioni di transizione, gli State primitivi forniscono un miglioramento notevole. Google raccomanda di utilizzare State primitivi per scenari tipici invece del mutableStateOf universale.

kotlin
@Composable
fun ScrollCounter() {
    // Bad: boxing on every update
    var badCount by remember { mutableStateOf(0) }

    // Good: no boxing, primitive storage
    var goodCount by remember { mutableIntStateOf(0) }

    // Usage is identical
    Button(onClick = { goodCount++ }) {
        Text("Count: $goodCount")
    }
}

Esempi pratici di lavoro con MutableState

Consideriamo un componente TodoList, dove MutableState viene utilizzato in due forme: come variabili separate per lo stato di input e come SnapshotStateList per una lista dinamica di attività. Entrambi utilizzano la delega per la brevità del codice.

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("Add") }
        }

        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 crea un SnapshotStateList — una lista mutabile che traccia le modifiche ai singoli elementi. Quando vengono chiamati items.add() e items[n] = newValue, Compose vede la mutazione e ricompone solo gli elementi di LazyColumn che sono cambiati. inputText è un normale MutableState<String>. La combinazione di due tipi di MutableState (singolo e collezione) è un pattern tipico per schermate con moduli e liste.

Domande frequenti

Posso usare MutableState senza remember?

MutableState senza remember verrà creato nuovamente a ogni ricomposizione. Ogni nuova chiamata a mutableStateOf crea un nuovo oggetto e il vecchio valore viene perso. Usa sempre remember per preservare lo State tra le ricomposizioni, a meno che lo State non venga creato all'esterno di un Composable (ad esempio, in un ViewModel).

Come convertire MutableState in una variabile normale?

Leggi .value una volta al di fuori di uno snapshot tramite snapshot { }. Ma questo disabilita la reattività — le modifiche non attiveranno più la ricomposizione. Per una lettura singola senza sottoscrizione, usa currentValue() all'interno di uno snapshot senza lettura.

Qual è più veloce: mutableStateOf o mutableIntStateOf?

mutableIntStateOf è più veloce perché non richiede il boxing di int in Integer. Con migliaia di aggiornamenti al secondo (animazione, scorrimento), la differenza può raggiungere il 30-50% nel tempo di allocazione. Per aggiornamenti rari (clic, immissione di testo), la differenza è trascurabile.

Si può usare MutableState come argomento di una funzione Composable?

È possibile ma non raccomandato. Invece di MutableState, passa State (sola lettura) + un lambda onValueChange. Questo implementa il pattern State Hoisting e rende il componente riutilizzabile. I componenti che accettano MutableState violano il flusso di dati unidirezionale.

Come creare un'implementazione personalizzata di MutableState?

Implementa l'interfaccia MutableState e fornisci override var value con un getter e setter. Nel setter puoi aggiungere validazione o registrazione. Per la retrocompatibilità con Compose Runtime, avvolgi la tua implementazione personalizzata in snapshotFlow o usa snapshotIncrement.

Riepilogo

  • MutableState — interfaccia di base per stato mutabile osservabile in Compose
  • State — versione di sola lettura per passare dati senza diritto di modifica
  • SnapshotMutationPolicy gestisce le condizioni per attivare la ricomposizione al cambiamento
  • State primitivi (MutableIntState e altri) eliminano l'overhead del boxing
  • SnapshotStateList e SnapshotStateMap tracciano le modifiche interne delle collezioni
  • Ricomposizione attivata automaticamente a qualsiasi chiamata del setter di value
  • Raccomandazione: usa MutableState per possedere lo stato e State per passarlo verso il basso

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche