Recomposition è un meccanismo di Jetpack Compose che ricostruisce automaticamente parti dell'interfaccia utente quando i dati cambiano, senza aggiornamento manuale degli elementi View. Quando una variabile di stato da cui dipende una funzione Composable cambia valore, Compose riavvia solo quella funzione, lasciando intatto il resto dell'albero UI. Secondo Google Android Developers, 2026, una corretta comprensione della Recomposition consente di ridurre i ridisegni non necessari del 40–60%.
Punti Chiave
Recomposition è la riesecuzione di funzioni Composable che hanno già partecipato alla Composition, con nuovi valori di parametri o stato. L'obiettivo principale della ricomposizione è sincronizzare l'albero UI con i dati correnti senza ricostruire l'intera interfaccia da zero. A differenza della Composition, che avviene una volta, la Recomposition può essere attivata centinaia di volte durante la vita di uno schermo.
La Recomposition funziona secondo il principio di smart invalidation: Compose traccia quali oggetti State ogni funzione Composable legge e segna per il riavvio solo quelle le cui dipendenze sono cambiate. Ciò è ottenuto tramite un sistema di snapshot che registra tutte le operazioni di lettura di State durante l'esecuzione e un Composer che mappa queste dipendenze a funzioni specifiche.
È importante capire: la ricomposizione non significa ridisegno immediato dello schermo. Compose lavora in tre fasi: Composition (costruzione della descrizione UI), Layout (calcolo di dimensioni e posizioni) e Drawing (rendering sulla tela). Se dopo la ricomposizione le dimensioni e le posizioni degli elementi non sono cambiate, la fase Layout può essere saltata. Se l'aspetto visivo non è cambiato — Drawing viene saltato. Questa architettura a tre fasi garantisce un costo minimo per ogni aggiornamento dell'interfaccia.
Esistono tre principali gatillatori di ricomposizione. Il primo è un cambiamento in un oggetto State letto all'interno del corpo di una funzione Composable. Quando mutableStateOf o derivedStateOf cambia il suo valore, tutte le funzioni che hanno registrato la lettura di questo State nella composizione precedente vengono segnate per il riavvio.
Il secondo gatillatore è un cambiamento di parametro di una funzione Composable quando viene chiamata da una funzione genitore. Se la funzione genitore passa un nuovo valore (ad esempio, il testo o il numero è cambiato), la funzione figlia verrà riavviata, anche se non legge State internamente. Compose confronta i valori dei parametri nuovi e vecchi tramite equals, e se sono uguali — la funzione può essere saltata.
Il terzo gatillatore è un cambiamento di CompositionLocal tramite CompositionLocalProvider. Tutte le funzioni che leggono CompositionLocal tramite .current vengono riavviate quando il provider cambia. Questo meccanismo è utilizzato da MaterialTheme: cambiare tema (chiaro/scuro) causa la ricomposizione di tutti i componenti che leggono MaterialTheme.colorScheme.
@Composable
fun RecompositionDemo() {
var counter by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("Hello") }
Column {
Text("Counter: $counter") // recomposition when counter changes
Text("Message: $text") // recomposition when text changes
Button(onClick = { counter++ }) {
Text("+1")
}
Button(onClick = { text = "World" }) {
Text("Change Text")
}
}
}
Cliccando sul pulsante +1, counter cambia, causando la ricomposizione solo della prima riga Text e della Column stessa. La seconda riga Text che mostra text non si riavvia. Questo isolamento è il risultato del sistema di snapshot: ogni funzione Composable conosce solo gli oggetti State che ha letto.
L'ottimizzazione della ricomposizione inizia con la scelta delle strutture dati corrette. Utilizzare collezioni immutabili (listOf, mapOf) invece di mutabili (mutableListOf). Compose confronta i parametri tramite equals, e se una collezione è cambiata ma equals ha restituito true — la funzione non si riavvierà. Per le collezioni mutabili, utilizzare SnapshotStateList, che implementa il tracciamento corretto delle modifiche a livello di elemento.
La seconda tecnica è estrarre le parti stabili dell'interfaccia in funzioni Composable separate. Se una parte dello schermo non dipende da uno stato che cambia frequentemente, estraila in una funzione separata con parametri. Quando si verifica la ricomposizione, la funzione stabile riceve gli stessi parametri, Compose li confronta e salta l'esecuzione. Questo è più efficiente che riavviare quella parte come parte di una grande funzione dove alcuni parametri sono cambiati.
La terza tecnica sono le chiavi in LazyColumn. Specificare sempre una chiave per gli elementi in LazyColumn, LazyGrid e altri contenitori lazy. La chiave permette a Compose di identificare gli elementi quando la lista cambia: aggiunta, rimozione o riordinamento. Senza chiave, Compose riavvia tutti gli elementi della lista a ogni cambiamento, il che su liste grandi causa un notevole degrado delle prestazioni.
// Optimized structure: stable parts extracted separately
@Composable
fun OptimizedScreen(items: List<Item>) {
Column {
Header() // does not depend on items — no recomposition
Spacer(modifier = Modifier.height(8.dp))
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item = item) // recomposition only for changed items
}
}
}
}
@Composable
fun Header() {
Text("Item list", style = MaterialTheme.typography.headlineMedium)
}
@Composable
fun ItemRow(item: Item) {
Text(item.title)
}
Skipping è un meccanismo in cui Compose salta l'esecuzione di una funzione Composable se tutti i suoi parametri non sono cambiati. Affinché lo skipping funzioni correttamente, i tipi dei parametri devono essere stabili. Il compilatore Kotlin segna come stabili: i tipi primitivi (Int, Float, Boolean), String, le funzioni lambda e le classi i cui campi sono tutti stabili e val.
Stability è l'annotazione @Stable o @Immutable che può essere aggiunta a classi di dati personalizzate. Se una classe contiene un campo mutabile (var), il compilatore la considera instabile e Compose non potrà saltare le funzioni con tali parametri. Per le classi con var, utilizzare @Stable se si garantisce che la notifica di modifica verrà inviata tramite il sistema di snapshot.
È possibile verificare la stabilità con il flag del compilatore -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports". Genera un report con un elenco di tutte le funzioni Composable e dei loro parametri indicando la stabilità. Se un parametro è instabile — lo skipping è impossibile per quella funzione e verrà riavviata a ogni ricomposizione del genitore.
| Tipo | Stabilità | Skipping |
|---|---|---|
| Int, Float, Boolean | Stabile | Sì |
| String | Stabile | Sì |
| Lambda | Stabile | Sì |
| data class con campi val | Stabile | Sì |
| data class con campi var | Instabile | No |
| List<String> | Instabile | No |
Nota: List<String> è considerata instabile perché è un'interfaccia, non un'implementazione concreta. Utilizzare immutableListOf() dalla libreria Kotlin Collections Immutable o avvolgere la lista in una classe @Stable. Lambda è sempre stabile perché il suo equals confronta solo i riferimenti e quando viene creata una nuova lambda nel sito di chiamata, anche la funzione genitore si riavvia.
Per monitorare la ricomposizione, Android Studio fornisce Layout Inspector con la modalità Compose Recomposition Counts. In questa modalità, ogni funzione Composable mostra il numero di ricomposizioni e le ragioni del riavvio. Ciò permette di trovare rapidamente le funzioni che si ricompongono troppo spesso e determinare la causa principale — parametri instabili o dipendenze di State non necessarie.
Strumenti aggiuntivi: Compose Metrics (raccolta di statistiche tramite test di strumentazione) e Recomposition Timer (misurazione del tempo di esecuzione di ogni funzione). Google raccomanda di attivare questi strumenti durante la profilazione e di disattivarli nelle build di rilascio, poiché aggiungono un overhead fino al 20% per ricomposizione.
Nell'analisi delle ricomposizioni, cercare pattern di ricomposizione non necessaria: una funzione si riavvia anche se la sua UI di output non dovrebbe cambiare. Una causa comune è l'uso di lambda senza remember, dove viene creato un nuovo oggetto lambda ogni volta e Compose considera il parametro cambiato. Soluzione: avvolgere le lambda in remember { } con catture fisse.
// Bad: new lambda on every parent recomposition
@Composable
fun Parent() {
Child(onClick = { doSomething() }) // new lambda every time
}
// Good: remember stabilizes the lambda
@Composable
fun Parent() {
val onClick = remember { { doSomething() } }
Child(onClick = onClick) // same reference
}
Domande Frequenti
No, la ricomposizione è solo la fase di Composition. Dopo di essa vengono eseguiti Layout e Drawing. Se dopo la ricomposizione le dimensioni e le posizioni degli elementi non sono cambiate, Layout e Drawing possono essere completamente saltati, risparmiando risorse GPU.
Durante le animazioni, la ricomposizione può essere eseguita fino a 120 volte al secondo (120fps). Per l'interazione normale — 10–60 volte al secondo. È importante che ogni ricomposizione rientri nel budget del fotogramma (8–16 ms), altrimenti l'applicazione rallenterà.
La ragione è un cambiamento di parametro dalla funzione genitore. Il genitore si riavvia (per la sua ragione) e passa un nuovo valore. Per evitarlo, verificare la stabilità dei parametri e utilizzare remember per stabilizzare lambda e valori calcolati.
Non esiste una disabilitazione diretta, ma esiste lo skipping forzato tramite readInComposition — lo State viene letto al di fuori del corpo della funzione, che non registra una dipendenza. Usare con cautela: la funzione non reagirà ai cambiamenti, il che può portare a un'interfaccia obsoleta.
Composition è più costosa perché crea tutti gli slot e i nodi dell'albero da zero. Recomposition riutilizza gli slot esistenti e aggiorna solo i loro valori. In pratica, la Composition di uno schermo richiede 2–10 ms, mentre la ricomposizione di un singolo elemento richiede 0.1–1 ms.
Riepilogo
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.
Leggi anche