L'annotazione @Composable è un elemento fondamentale di Jetpack Compose che trasforma una normale funzione Kotlin in un blocco costruttivo dichiarativo dell'interfaccia utente. Senza questa annotazione, è impossibile creare qualsiasi schermata nello sviluppo Android moderno. Secondo Google Android Developers, 2026, oltre l'80% dei nuovi progetti Kotlin utilizza Compose per costruire l'UI e @Composable è l'annotazione più frequentemente utilizzata nell'ecosistema.
Punti Chiave
@Composable è un'annotazione del linguaggio Kotlin che contrassegna una funzione come destinata a descrivere l'interfaccia utente nel framework Jetpack Compose. Quando il compilatore Kotlin incontra questa annotazione, genera codice aggiuntivo che consente alla funzione di operare nel contesto di composizione — il sistema di gestione dell'albero UI.
L'annotazione @Composable è stata introdotta da Google nel 2021 insieme alla prima versione stabile di Jetpack Compose 1.0. Prima della sua comparsa, lo sviluppo di interfacce Android avveniva esclusivamente tramite markup XML e il sistema View. @Composable ha cambiato radicalmente l'approccio: invece di descrivere l'UI in un file di markup separato, lo sviluppatore scrive l'interfaccia direttamente in Kotlin.
La differenza principale tra @Composable e le funzioni Kotlin ordinarie è la capacità di leggere e reagire ai cambiamenti di stato. Quando una variabile letta da una funzione Composable cambia valore, il sistema pianifica automaticamente un riavvio (ricomposizione) di quella funzione. Questo libera lo sviluppatore dall'aggiornamento manuale dell'UI tramite findViewById e setText.
La meccanica interna di @Composable si basa sul concetto di slot — un'area di memoria speciale allocata per ogni funzione all'interno della composizione. Questo slot memorizza i valori passati alla funzione, nonché le informazioni di servizio necessarie per il confronto nelle chiamate successive.
Per dichiarare una funzione Composable, è sufficiente aggiungere l'annotazione @Composable prima della parola chiave fun. La funzione deve trovarsi in un pacchetto che importa l'annotazione da androidx.compose.runtime. Si raccomanda di scrivere il nome della funzione con la lettera maiuscola — questa è una convenzione ampiamente accettata nella community di Compose che distingue visivamente i componenti UI dalle funzioni ordinarie.
import androidx.compose.runtime.Composable
@Composable
fun Greeting(name: String) {
var count by remember { mutableStateOf(0) }
Column {
Text("Ciao, $name!")
Button(onClick = { count++ }) {
Text("Cliccato $count volte")
}
}
}
I parametri di una funzione Composable possono essere qualsiasi cosa — tipi primitivi, stringhe, lambda e persino altre funzioni Composable passate tramite Slot API. Si raccomanda di rendere i parametri immutabili (val) per evitare effetti collaterali durante la ricomposizione. Tutti i dati mutabili devono essere gestiti attraverso i meccanismi di stato di Compose.
Le funzioni Composable non possono restituire valori arbitrari come le funzioni regolari — il loro unico compito è costruire o aggiornare un frammento dell'albero UI. Tuttavia, esistono pattern speciali come State Hoisting in cui una funzione Compose accetta stato e callback tramite parametri, rimanendo pura e riutilizzabile.
Il sistema Compose impone diverse restrizioni rigorose su come le funzioni Composable devono apparire e comportarsi. Prima regola: una funzione Composable può chiamare solo altre funzioni Composable o funzioni regolari senza effetti collaterali. Ciò garantisce la prevedibilità della composizione e il corretto funzionamento delle ottimizzazioni di Compose.
La seconda regola riguarda l'ordine di esecuzione. Compose ha il diritto di chiamare le funzioni Composable in qualsiasi ordine, quindi il codice nel corpo di tale funzione non deve dipendere dalla sequenza di chiamata delle funzioni vicine. Ogni funzione Composable deve essere autosufficiente a livello della sua posizione nell'albero UI.
La terza regola — divieto di effetti collaterali all'interno del corpo di una funzione Composable. Operazioni come scrittura nel database, invio di richieste di rete o modifica di variabili esterne devono essere eseguite solo all'interno di effetti speciali: LaunchedEffect, DisposableEffect o SideEffect. Violare questa regola porta a comportamenti imprevedibili durante le ricomposizioni.
La quarta regola: le funzioni Composable devono essere idempotenti. Chiamarle di nuovo con gli stessi argomenti deve produrre la stessa UI. Questo requisito è necessario per il corretto funzionamento dell'ottimizzazione skipping, dove Compose salta il ridisegno delle funzioni i cui dati di input non sono cambiati.
// Corretto: funzione Composable pura senza effetti collaterali
@Composable
fun UserCard(user: User, onClick: () -> Unit) {
Card(modifier = Modifier.clickable { onClick() }) {
Text(text = user.name)
}
}
// Sbagliato: effetto collaterale all'interno del corpo
@Composable
fun WrongCard(userId: String) {
// val result = viewModel.loadUser(userId) // NON CONSENTITO
Text("Caricamento...")
}
Diamo un'occhiata a un esempio pratico di creazione di una schermata del profilo utilizzando l'annotazione @Composable. Qui dimostriamo la combinazione di più funzioni Composable, il lavoro con lo stato e i modificatori — elementi chiave di qualsiasi layout Compose.
@Composable
fun ProfileScreen(userId: String) {
var isFollowed by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
ProfileHeader(userId = userId)
Spacer(modifier = Modifier.height(16.dp))
StatsRow(posts = 42, followers = 1280)
Spacer(modifier = Modifier.height(24.dp))
FollowButton(
isFollowed = isFollowed,
onToggle = { isFollowed = !isFollowed }
)
}
}
@Composable
fun ProfileHeader(userId: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
AsyncImage(model = "https://example.com/avatars/$userId",
contentDescription = "User avatar")
Spacer(modifier = Modifier.width(12.dp))
Text(text = "Utente #$userId", style = MaterialTheme.typography.headlineMedium)
}
}
@Composable
fun StatsRow(posts: Int, followers: Int) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
StatItem("Posts", posts)
StatItem("Followers", followers)
}
}
@Composable
fun StatItem(label: String, value: Int) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "$value", style = MaterialTheme.typography.headlineSmall)
Text(text = label, style = MaterialTheme.typography.bodySmall)
}
}
Nell'esempio, ogni funzione Composable è responsabile della propria parte della schermata: ProfileScreen gestisce lo stato generale e la composizione delle funzioni figlie, ProfileHeader visualizza l'avatar e il nome, e StatsRow mostra un blocco di statistiche. Questo approccio segue il principio di responsabilità unica e semplifica il riutilizzo dei componenti.
In Jetpack Compose, esistono tre tipi principali di funzioni Composable. Il primo tipo — contenitori (Row, Column, Box, LazyColumn) — determinano la disposizione degli elementi figli. Il secondo tipo — elementi di visualizzazione (Text, Image, Icon, Button) — renderizzano componenti UI specifici. Il terzo tipo — funzioni Composable personalizzate — combinano componenti integrati in blocchi riutilizzabili.
I contenitori differiscono dagli elementi regolari in quanto accettano un lambda content — l'ultimo parametro di tipo @Composable () -> Unit. Questo meccanismo consente di costruire alberi UI annidati: ogni contenitore genera una composizione figlia con il proprio contesto e area di memoria.
Le funzioni Composable personalizzate si dividono in due sottotipi: intelligenti (smart) e semplici (dumb). Le funzioni intelligenti gestiscono stato e logica — contengono chiamate a remember, LaunchedEffect e altre API di Compose. Le funzioni semplici ricevono tutti i dati tramite parametri e li visualizzano soltanto. La separazione in componenti intelligenti e semplici migliora la testabilità e il riutilizzo del codice.
| Tipo | Esempio | Scopo |
|---|---|---|
| Contenitore | Column, Row, Box | Gestione della disposizione degli elementi figli |
| Elemento | Text, Image, Button | Visualizzazione del contenuto e gestione dell'input |
| Personalizzato | ProfileCard, UserList | Combinazione di componenti standard |
Il principale vantaggio dell'annotazione @Composable è la capacità di creare componenti UI riutilizzabili senza ereditarietà e gerarchie di classi complesse. A differenza del sistema View, dove ogni elemento personalizzato richiedeva la creazione di una classe Java con costruttori, un componente Composable è semplicemente una funzione Kotlin con parametri.
Per garantire il riutilizzo, si utilizza il pattern Slot API, in cui una funzione Composable accetta lambda content per diverse aree del suo layout. Ad esempio, un componente Card può accettare contenuti separati per intestazione, corpo e piè di pagina, rendendolo universale per qualsiasi schermata dell'applicazione.
I modificatori (Modifier) giocano un ruolo chiave nel riutilizzo: consentono di configurare padding, dimensioni, clic e animazioni senza modificare il componente stesso. Si raccomanda di passare sempre Modifier come parametro di una funzione Composable con un valore predefinito: Modifier = Modifier — questa è una pratica standard adottata nelle librerie ufficiali di Google.
@Composable
fun SectionCard(
modifier: Modifier = Modifier,
title: String,
content: @Composable () -> Unit
) {
Card(modifier = modifier) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(8.dp))
content()
}
}
}
Grazie a Slot API, il componente SectionCard può essere utilizzato su schermate diverse con contenuti diversi — moduli, elenchi, blocchi di testo. La combinazione di modificatori e Slot API rende i componenti Compose estremamente flessibili senza perdere la sicurezza dei tipi offerta da Kotlin.
Domande Frequenti
Una funzione @Composable viene eseguita nel contesto di composizione e può leggere lo stato, riavviandosi automaticamente quando cambia. Le funzioni Kotlin normali non hanno accesso ai meccanismi di tracciamento dello stato e non partecipano alla costruzione dell'albero UI.
No, le funzioni Composable possono essere chiamate solo da altre funzioni Composable, poiché è richiesto un contesto di composizione speciale. Per integrare il codice Compose con Kotlin normale, si utilizza il metodo setContent { } in Activity o ComposeView nel sistema View.
È una convenzione di denominazione adottata nella community di Compose. La lettera maiuscola distingue visivamente i componenti UI dalle funzioni ordinarie, seguendo le regole di denominazione delle classi. Non è un requisito del compilatore, ma una pratica consigliata nella documentazione di Google.
Non ci sono limiti sul numero. In pratica, una schermata grande può contenere 50–100 funzioni Composable, inclusi componenti integrati (Text, Button) e personalizzati. Compose ottimizza l'albero delle funzioni ed esegue solo quelle i cui dati di input sono cambiati.
Di solito le funzioni Composable restituiscono Unit, poiché il loro compito è costruire l'UI. Tuttavia, esistono funzioni specializzate come remember e derivedStateOf che sono contrassegnate con @Composable e restituiscono valori. Questa è un'eccezione, non la regola.
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