SideEffect — cos’è, sincronizzazione dello stato in Jetpack Compose

Autore: IT Sectr Pubblicato: 2026-06-30 Tempo di lettura: 9 min

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 — API di effetto collaterale per codice eseguito dopo ogni ricomposizione riuscita.
  • Sincronizzazione — trasferisce lo stato di Compose a sistemi esterni che non supportano Compose.
  • Senza chiavi — a differenza di LaunchedEffect, SideEffect non riparte ma viene eseguito ad ogni ricomposizione.
  • Nessuna pulizia — SideEffect non fornisce onDispose, è progettato solo per sincronizzazione unidirezionale.
  • Sincrono — il blocco viene eseguito in modo sincrono nella fase di composizione di Compose, senza coroutine.

Cos’è SideEffect in Jetpack Compose

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.

kotlin
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
    val mapView = remember { MapView(LocalContext.current) }
    
    SideEffect {
        mapView.setZoom(zoomLevel)
        mapView.updateMarkers(markers)
    }
    
    AndroidView(factory = { mapView })
}

Come funziona SideEffect e le fasi di composizione

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.

kotlin
// 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

Aggiornamento delle funzioni callback con SideEffect

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.

kotlin
@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 })
}

Sincronizzazione con Analytics SDK

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.

kotlin
@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
}

SideEffect vs LaunchedEffect: quando usare cosa

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.

CaratteristicaSideEffectLaunchedEffect
EsecuzioneAd ogni ricomposizioneAl cambiamento della chiave
AsincroniaSincronoCoroutine
ChiaviNoSì (vararg)
PuliziaNoCancellazione automatica della coroutine
Utilizzo tipicoCallback, analisi, sincronizzazione visteCaricamento, 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.

Errori comuni con SideEffect

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

SideEffect viene eseguito alla prima composizione?

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).

SideEffect può causare un ciclo infinito?

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.

Qual è la differenza tra SideEffect e snapshotFlow?

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.

Come debuggare SideEffect se viene eseguito troppo spesso?

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.

Si può combinare SideEffect con DisposableEffect?

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

  • SideEffect — API di effetto collaterale per codice sincrono eseguito dopo ogni ricomposizione riuscita in Jetpack Compose.
  • Sincronizzazione — il caso d’uso principale: trasferire lo stato di Compose a sistemi esterni (Google Maps, WebView, ViewPager, Analytics SDK).
  • Callback — SideEffect garantisce che le funzioni callback che catturano lo stato corrente rimangano aggiornate ad ogni aggiornamento dell’interfaccia utente.
  • Nessun ciclo — viene eseguito dopo la fase di composizione, quindi modificare lo stato all’interno di SideEffect non causa ricomposizione infinita.
  • Limitazioni — non supporta chiavi, asincronia o blocco di pulizia; per questi compiti usa LaunchedEffect o DisposableEffect.
  • Prestazioni — evita calcoli pesanti all’interno di SideEffect, poiché viene eseguito ad ogni ricomposizione (fino a 60 volte al secondo).
  • Debug — controlla la frequenza di chiamata tramite Compose Debugger e ottimizza con remember per filtrare ricomposizioni non necessarie.

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