Composition è il processo centrale in Jetpack Compose, durante il quale un albero UI vivo visualizzato sullo schermo viene costruito da funzioni Composable descrittive. A differenza del sistema View di Android, dove i layout venivano caricati da XML e convertiti in oggetti immutabili, Composition funziona come un sistema dinamico: le funzioni vengono eseguite, creano slot in memoria, formano una gerarchia di nodi e la legano allo stato. Secondo Google Android Developers, 2026, comprendere Composition è fondamentale per ottimizzare le prestazioni delle applicazioni Compose.
Punti chiave
Composition è il processo di esecuzione delle funzioni Composable, che risulta in una rappresentazione interna dell'interfaccia utente come albero di nodi. Ogni nodo di questo albero corrisponde a un componente integrato (Text, Button, Image) o a una chiamata a una funzione Composable definita dall'utente. Composition non crea direttamente oggetti View Android — costruisce una descrizione astratta che viene poi elaborata dalle fasi Layout e Drawing.
La caratteristica principale di Composition è la sua riavviabilità. Ogni funzione Composable all'interno della composizione può essere riavviata in qualsiasi momento se i suoi parametri di input o gli oggetti di stato che legge sono cambiati. Il sistema non riavvia l'intero albero — solo quelle funzioni che dipendono effettivamente dai dati modificati.
Tecnicamente, Composition è gestita tramite Composer — un motore interno che il compilatore Kotlin incorpora in ogni funzione Composable. Composer scrive in slot (gruppi di posizione) informazioni su quali funzioni sono state chiamate, con quali parametri e in quale ordine. Nelle chiamate successive, Composer confronta i nuovi dati con i dati memorizzati e decide se riavviare.
Il processo di costruzione dell'albero UI inizia con la chiamata del metodo setContent all'interno di un'Activity o Fragment. Questo metodo crea la Composition iniziale e avvia l'esecuzione della funzione Composable radice. Quindi ogni funzione Composable annidata aggiunge i propri nodi all'albero, formando una gerarchia: Row contiene Text e Button, Column contiene Image e Card, e così via.
Ogni nodo dell'albero riceve una chiave di posizione unica, basata sulla sua posizione nel codice sorgente. Questa chiave viene utilizzata per identificare il nodo durante le esecuzioni successive. La chiave di posizione è il motivo per cui l'ordine di chiamata delle funzioni Composable non deve dipendere da condizioni: se in un'esecuzione viene chiamato A -> B, e nella successiva B -> A, Compose non sarà in grado di far corrispondere i nodi vecchi e nuovi.
@Composable
fun AppScreen() {
Column { // Nodo Column (posizione 1)
HeaderSection() // Nodo HeaderSection (posizione 2)
ContentSection() // Nodo ContentSection (posizione 3)
FooterSection() // Nodo FooterSection (posizione 4)
}
}
@Composable
fun HeaderSection() {
Row { // Nodo Row (posizione 2.1)
Text("Titolo") // Nodo Text (posizione 2.2)
Icon(...) // Nodo Icon (posizione 2.3)
}
}
In questo esempio, ogni chiamata riceve una posizione basata sull'ordine nel codice. Column (posizione 1) contiene tre nodi figli (posizioni 2, 3, 4). HeaderSection aggiunge altri due nodi figli (2.1, 2.2, 2.3). Se nella prossima ricomposizione ContentSection viene chiamato prima di HeaderSection, Composer non sarà in grado di far corrispondere correttamente i nodi — da qui la regola: l'ordine delle chiamate alle funzioni Composable deve essere stabile.
Lo stato in Composition è gestito tramite oggetti di tipo State<T>. Quando una funzione Composable legge un valore da State tramite una proprietà delegata (by), registra una dipendenza da quello State. Quando il valore cambia, tutte le funzioni che hanno letto questo State vengono contrassegnate per il riavvio nella fase di composizione successiva.
Il meccanismo di registrazione delle dipendenze si chiama sistema di snapshot. Ogni volta che State cambia, uno snapshot registra tutte le modifiche e notifica a Composer quali funzioni dipendono da quello State. È importante capire: leggere State all'interno di codice non-Composable (ad esempio, in una lambda onClick) non registra una dipendenza — solo la lettura all'interno di una funzione Composable o in lambda eseguite nel contesto di composizione.
Il sistema di snapshot funziona in modo transazionale: più modifiche di State all'interno di un singolo evento vengono combinate in un'unica transazione, prevenendo molteplici ricomposizioni. Questo è particolarmente importante nella gestione dei gesti: un movimento modifica diversi oggetti State, ma Compose esegue una sola ricomposizione.
@Composable
fun StateExample() {
var text by remember { mutableStateOf("Hello") }
var isVisible by remember { mutableStateOf(true) }
Column {
Text(text) // registra dipendenza da text
if (isVisible) { // registra dipendenza da isVisible
TextField(value = text, onValueChange = { text = it })
}
Button(onClick = { isVisible = !isVisible }) {
Text(if (isVisible) "Nascondi" else "Mostra")
}
}
}
Cambiare text attiva la ricomposizione solo di Column, Text e TextField. Column, Button e la condizione isVisible rimangono invariati. Questo isolamento della ricomposizione è un vantaggio chiave di Compose rispetto ai sistemi che ridisegnano l'intero schermo. Ogni funzione Composable traccia solo gli oggetti State che legge direttamente.
Composition e Recomposition sono due diverse modalità di esecuzione delle funzioni Composable. Composition avviene una volta quando lo schermo viene creato: il sistema esegue tutte le funzioni Composable con valori iniziali e costruisce l'albero UI iniziale. Recomposition avviene più volte quando i dati cambiano: il sistema riavvia solo le funzioni che dipendono dallo stato modificato.
Modalità Composition attiva tutti i nodi dell'albero, alloca slot per ogni funzione e registra tutti i discendenti. Recomposition funziona selettivamente: Compose confronta i nuovi e vecchi valori dei parametri di ogni funzione, e se non sono cambiati — la funzione non viene eseguita (skipping).
Composition e Recomposition differiscono in costo. La prima Composition è più costosa perché richiede la costruzione completa dell'albero e l'allocazione degli slot. Recomposition è più economica, specialmente se la maggior parte delle funzioni è stabile — i loro parametri vengono confrontati con equals, e Compose salta la loro invocazione. Per le massime prestazioni, bisogna cercare di far sì che la maggior parte delle ricomposizioni interessi il minor numero possibile di funzioni.
| Caratteristica | Composition | Recomposition |
|---|---|---|
| Quando avviene | Una volta, al primo display | Più volte, al cambio di dati |
| Ambito | Intero albero | Solo funzioni modificate |
| Confronto parametri | Non eseguito | Eseguito per skipping |
| Creazione slot | Sì, tutti gli slot creati | Solo per nuovi nodi |
CompositionLocal è un meccanismo per passare dati implicitamente attraverso l'albero di composizione. Risolve il problema quando un parametro deve essere passato attraverso decine di funzioni Composable annidate che non lo usano direttamente. Invece di una catena esplicita di parametri, i dati vengono impostati al livello superiore e letti in qualsiasi funzione annidata tramite CompositionLocal.current.
MaterialTheme è l'esempio più noto di CompositionLocal. Tutti i componenti Compose leggono colori, tipografia e forme tramite MaterialTheme.colorScheme, MaterialTheme.typography, MaterialTheme.shapes, senza riceverli tramite parametri. Gli sviluppatori possono creare propri CompositionLocal per dati come l'utente corrente, le impostazioni di localizzazione o la configurazione dello schermo.
Un'importante limitazione: CompositionLocal non dovrebbe essere usato per dati che cambiano frequentemente (posizione di scorrimento, testo in un campo di input). Un componente che legge CompositionLocal si riavvia ogni volta che il valore cambia, quindi per dati dinamici è meglio usare parametri espliciti o State. CompositionLocal è ottimale per dati di configurazione che cambiano raramente o mai.
val LocalUser = compositionLocalOf<User?> { null }
@Composable
fun AppRoot(user: User, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalUser.provides(user)) {
content()
}
}
@Composable
fun UserAvatar() {
val user = LocalUser.current // lettura senza parametro esplicito
AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}
CompositionLocalProvider crea un ambito all'interno del quale LocalUser.current restituisce il valore specificato. UserAvatar legge l'utente senza passare esplicitamente il parametro attraverso funzioni intermedie. Questo è particolarmente prezioso in gerarchie profonde dove i dati sono necessari solo in pochi nodi foglia.
Domande frequenti
Modificare State durante Composition pianifica una nuova ricomposizione, che verrà eseguita dopo il completamento di quella corrente. Non si verifica un ciclo infinito: Compose garantisce che ogni ricomposizione venga eseguita in una transazione separata del sistema di snapshot.
Sui dispositivi moderni, la Composition di uno schermo con 50–100 funzioni Composable richiede 1–5 ms. Google raccomanda di rimanere entro 16 ms per un fotogramma a 60fps. Se Composition supera questo limite, utilizzare LazyColumn o suddividere lo schermo in funzioni più piccole.
L'avvio manuale diretto di Composition non è possibile — è gestito da Composer automaticamente. Tuttavia, è possibile forzare una ricomposizione modificando State o chiamando invalidate() sul composable radice se si ha accesso a CompositionContext.
La gerarchia View è un albero immutabile di oggetti Java che viene creato una volta. Composition è un albero virtuale che viene ricostruito ogni volta che i dati cambiano. View memorizza il suo stato in variabili d'istanza, Composition — in slot legati alla posizione della chiamata della funzione.
Se una funzione Composable non viene più chiamata (ad esempio, una condizione if diventa false), Composition rimuove il suo nodo e attiva la pulizia di DisposableEffect. Quando riappare (if diventa di nuovo true), viene creato un nuovo nodo — quello vecchio non viene ripristinato.
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