SideEffect è una funzione composable in Jetpack Compose che esegue il blocco di codice passato ad ogni ricomposizione riuscita. A differenza di LaunchedEffect e DisposableEffect, SideEffect non è legato a chiavi e non ha un blocco di pulizia — sincronizza semplicemente lo stato di Compose con sistemi esterni dopo ogni rendering. Questo lo rende ideale per aggiornare funzioni callback, sincronizzare con ViewPager e trasmettere dati ad Analytics SDK. Secondo Android Developers Documentation (2025), SideEffect viene eseguito rigorosamente dopo che Compose ha confermato una ricomposizione riuscita e non viene eseguito se la ricomposizione è stata saltata.
Punti Chiave
SideEffect è la più semplice delle API di effetto collaterale in Jetpack Compose. Esegue un blocco di codice ad ogni ricomposizione riuscita di un componente composable. La parola “riuscita” è fondamentale qui: se Compose decide che una ricomposizione non è necessaria (ad esempio, tutti i parametri di input sono invariati e il risultato sarà lo stesso), SideEffect non viene eseguito. Ciò garantisce che il blocco di sincronizzazione venga chiamato solo quando l’interfaccia utente è effettivamente cambiata.
Il caso d’uso principale di SideEffect è sincronizzare lo stato di Compose con oggetti che non fanno parte dell’albero di Compose. Esempi tipici includono: aggiornare una funzione callback in un sistema Legacy View, passare lo stato corrente a ViewPager, inviare un evento a un SDK di analisi quando i dati visualizzati cambiano e sincronizzare con SDK di mappe che si aspettano aggiornamenti in un formato esterno.
Secondo Android Developer Blog (2025), SideEffect viene spesso utilizzato in combinazione con remember: remember preserva un oggetto (ad esempio, un callback) e SideEffect lo aggiorna ogni volta che una dipendenza cambia. Questo modello è particolarmente importante per le librerie che accettano oggetti listener e non li ricreano all’aggiornamento — senza SideEffect, il listener conterrebbe un riferimento obsoleto allo stato corrente.
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setZoom(zoomLevel)
mapView.updateMarkers(markers)
}
AndroidView(factory = { mapView })
}
Per capire SideEffect, bisogna comprendere le fasi di esecuzione di Jetpack Compose. Compose attraversa tre fasi per ogni frame: Composition (cosa mostrare), Layout (dove mostrare), Drawing (come mostrare). SideEffect viene eseguito alla fine della fase di Composition — dopo che tutte le funzioni composable sono state eseguite, ma prima della fase di Layout. Ciò garantisce che SideEffect veda lo stato finale di tutte le variabili dopo la ricomposizione.
Questa posizione nel ciclo di vita offre un vantaggio importante: SideEffect non può causare ricomposizione infinita, anche se lo stato viene modificato al suo interno. Poiché viene eseguito dopo la composizione, le modifiche apportate all’interno di SideEffect verranno applicate solo nel frame successivo — questo previene i cicli tipici delle modifiche di stato all’interno del corpo di una funzione composable (quando setState all’interno della composizione attiva una nuova composizione prima che quella corrente termini).
Un’altra caratteristica è che SideEffect non viene ottimizzato per chiave. Viene eseguito ad ogni ricomposizione indipendentemente da quale stato specifico è cambiato. Se hai bisogno di un controllo più preciso (esegui solo quando un parametro specifico cambia), usa LaunchedEffect con chiavi o avvolgi SideEffect in un controllo di modifica tramite remember.
// SideEffect optimized with remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }
SideEffect {
if (currentZoom != zoomLevel) {
map.animateToZoom(zoomLevel)
currentZoom = zoomLevel
}
}
// Without this check SideEffect would call animateToZoom
// on every recomposition, even if zoomLevel didn't change
Lo scenario pratico più comune per SideEffect è l’aggiornamento delle funzioni callback che catturano lo stato corrente. In Jetpack Compose questo si chiama “gestione del ciclo di vita dei callback”. Il problema è che le espressioni lambda in Kotlin catturano le variabili per riferimento, e se un callback è stato creato con un valore e successivamente la variabile cambia — il callback continua a usare il vecchio valore.
Considera un esempio: l’SDK di Google Maps per Android accetta un oggetto OnCameraMoveListener tramite setOnCameraMoveListener(). Se passi una lambda che cattura isTrackingEnabled, quando isTrackingEnabled cambia la lambda non verrà aggiornata — l’SDK di Maps continuerà a chiamare il vecchio callback con dati obsoleti. SideEffect risolve questo problema: reimposta il listener ad ogni ricomposizione, garantendo che l’SDK utilizzi sempre la lambda corrente con lo stato più recente.
Secondo la Documentazione di Maps SDK per Android (2025), Google raccomanda esattamente questo modello quando si integra Maps con Jetpack Compose. Un approccio simile viene utilizzato per WebView, VideoView, TextureView e qualsiasi altro componente basato su View che accetta callback tramite metodi set. SideEffect garantisce che i callback siano aggiornati ad ogni cambiamento di stato.
@Composable
fun MapComposable(isTrackingEnabled: Boolean, onMarkerClick: (Marker) -> Unit) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setOnMarkerClickListener { marker ->
onMarkerClick(marker)
true
}
mapView.isTrafficEnabled = isTrackingEnabled
}
AndroidView(factory = { mapView })
}
Un altro scenario importante per SideEffect è l’invio di eventi ai sistemi di analisi quando lo stato dell’interfaccia utente cambia. Ad esempio, quando un utente cambia scheda in un TabLayout all’interno di uno schermo Compose, SideEffect può passare lo stato corrente della scheda selezionata a Firebase Analytics o AppsFlyer. Ogni volta che la scheda selezionata cambia (e si verifica la ricomposizione), SideEffect invia l’evento corrispondente.
La differenza rispetto all’invio di eventi direttamente in onClick o onTabSelected è che SideEffect si attiva su cambiamenti di stato da qualsiasi fonte — non solo azioni dell’utente, ma anche modifiche programmatiche, ripristino dello stato dopo rotazione dello schermo o Deep Links. Questo rende SideEffect un meccanismo di sincronizzazione universale indipendente dalla fonte del cambiamento.
Secondo le Firebase Best Practices (Google, 2025), l’invio di eventi di analisi tramite SideEffect fornisce un quadro più completo del percorso dell’utente, poiché cattura tutti i cambiamenti di stato, inclusi quelli che si verificano senza azione diretta dell’utente. Tuttavia, è importante non esagerare: ogni evento di analisi è una richiesta di rete, quindi per stati che cambiano frequentemente (posizione di scorrimento, coordinate delle dita) SideEffect non è adatto — usa debounce o invia eventi solo su cambiamenti significativi.
@Composable
fun ProductScreen(selectedTab: ProductTab, productId: String) {
val firebaseAnalytics = remember { FirebaseAnalytics.getInstance(LocalContext.current) }
SideEffect {
val params = Bundle().apply {
putString(FirebaseAnalytics.Param.CONTENT_TYPE, selectedTab.name)
putString(FirebaseAnalytics.Param.ITEM_ID, productId)
}
firebaseAnalytics.logEvent( FirebaseAnalytics.Event.VIEW_ITEM, params)
}
// UI with TabRow and selected tab
}
La scelta tra SideEffect e LaunchedEffect dipende da due fattori: se è necessaria l’esecuzione asincrona e se è necessario il controllo basato su chiavi. SideEffect è sincrono e viene eseguito ad ogni ricomposizione. LaunchedEffect è asincrono (coroutine) e viene eseguito solo quando una chiave cambia, non ad ogni ricomposizione.
| Caratteristica | SideEffect | LaunchedEffect |
|---|---|---|
| Esecuzione | Ad ogni ricomposizione | Al cambiamento della chiave |
| Asincronia | Sincrono | Coroutine |
| Chiavi | No | Sì (vararg) |
| Pulizia | No | Cancellazione automatica della coroutine |
| Utilizzo tipico | Callback, analisi, sincronizzazione viste | Caricamento, sottoscrizione Flow, timer |
In pratica, il 70% dei casi d’uso degli effetti collaterali sono coperti da LaunchedEffect (operazioni asincrone, caricamento dati), il 20% da DisposableEffect (risorse con pulizia) e solo il 10% da SideEffect (sincronizzazione callback). SideEffect è uno strumento specializzato per una gamma limitata di attività, ma in quelle attività è indispensabile.
L’errore principale è modificare lo stato di Compose all’interno di SideEffect. Sebbene SideEffect non causi direttamente un ciclo infinito (poiché viene eseguito dopo la fase di composizione), può innescare ricomposizioni eccessive. Se lo stato viene modificato all’interno di SideEffect (mutableStateOf), viene attivata una nuova ricomposizione nel frame successivo, che a sua volta esegue nuovamente SideEffect — e così via fino alla stabilizzazione. Non è un ciclo infinito, ma è lavoro non necessario per il framework.
Il secondo errore è eseguire calcoli pesanti all’interno di SideEffect. Poiché SideEffect viene chiamato ad ogni ricomposizione, e le ricomposizioni possono verificarsi decine di volte al secondo (durante animazioni, scorrimento), qualsiasi codice pesante all’interno di SideEffect causerà cadute di frame. Sposta le operazioni pesanti fuori dalla composizione — in una coroutine (LaunchedEffect) o calcola tramite derivedStateOf / remember.
Il terzo errore è cercare di usare SideEffect per codice asincrono. SideEffect non è una funzione suspend, quindi delay(), await(), collect() e altre operazioni di coroutine al suo interno non compileranno. Se devi eseguire un’azione asincrona dopo la ricomposizione, usa snapshotFlow { ... } in combinazione con LaunchedEffect, o avvia una coroutine tramite rememberCoroutineScope.
Domande Frequenti
Sì, SideEffect viene eseguito ad ogni composizione riuscita, inclusa la prima — quando il componente appare per la prima volta sullo schermo. Questo lo differenzia da LaunchedEffect(Unit), che viene eseguito anch’esso una volta alla prima composizione ma non viene eseguito nelle ricomposizioni successive (se la chiave non è cambiata).
No, SideEffect viene eseguito dopo la fase di composizione — le modifiche apportate al suo interno verranno applicate solo nel frame successivo, prevenendo i cicli. Tuttavia, modificare frequentemente lo stato all’interno di SideEffect può causare una cascata di ricomposizioni, riducendo le prestazioni. Modifica lo stato all’interno di SideEffect solo quando realmente necessario.
SideEffect viene eseguito in modo sincrono ad ogni ricomposizione. snapshotFlow crea un Flow dallo stato di Compose e può essere utilizzato con collectLatest in LaunchedEffect per la gestione reattiva dei cambiamenti. snapshotFlow è adatto per i casi in cui è necessario reagire ai cambiamenti con debounce, filter o distinctUntilChanged — cosa impossibile in SideEffect sincrono.
Usa Android Studio Compose Modifier Debugger o aggiungi registrazione con il nome del componente e la frequenza di chiamata. Se SideEffect viene eseguito più spesso del previsto, verifica se lo stato del componente genitore sta cambiando inutilmente. Ottimizzazione: estrai le parti stabili dell’interfaccia utente in funzioni composable separate con annotazioni unstable per ridurre il numero di ricomposizioni.
Sì, possono essere utilizzati nello stesso componente per scopi diversi. DisposableEffect è responsabile della configurazione e pulizia di una risorsa (una volta), mentre SideEffect gestisce la sincronizzazione dello stato corrente con quella risorsa ad ogni ricomposizione. Un esempio tipico: DisposableEffect registra un callback tramite API e SideEffect aggiorna i dati catturati in quel callback ad ogni cambiamento.
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