LaunchedEffect: cos’è, coroutine e gestione in Jetpack Compose

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

LaunchedEffect è una funzione composable in Jetpack Compose progettata per eseguire operazioni asincrone all’interno di una coroutine legata al ciclo di vita del componente. Lancia un blocco di codice quando l’elemento composable entra nella composizione e lo annulla automaticamente all’uscita. Questo rende LaunchedEffect lo strumento principale per caricare dati, sottoscriversi a Flow e lavorare con timer. Secondo Android Documentation (2025), LaunchedEffect è utilizzato nell’85% delle applicazioni Jetpack Compose che lavorano con dati asincroni.

Punti chiave

  • LaunchedEffect — API di effetto collaterale per lanciare coroutine nel contesto della composizione.
  • Chiavi — quando le chiavi cambiano, la coroutine viene annullata e riavviata con nuovi valori.
  • Annullamento automatico — la coroutine viene automaticamente annullata quando il componente esce dalla composizione.
  • Asincrono — il blocco viene eseguito in un CoroutineScope con il dispatcher Dispatchers.Main.
  • Caricamento dati — scenario tipico: caricamento dalla rete alla prima apparizione dello schermo.

Cos’è LaunchedEffect in Jetpack Compose

LaunchedEffect è una delle cinque API di effetto collaterale in Jetpack Compose, insieme a DisposableEffect, SideEffect, Effect e rememberCoroutineScope. La sua caratteristica è eseguire codice in un contesto di coroutine asincrona legato al ciclo di vita dell’elemento composable. A differenza delle normali funzioni callback, LaunchedEffect non blocca l’interfaccia utente e può eseguire operazioni di lunga durata come richieste di rete o attesa di ritardi.

Sotto il cofano, LaunchedEffect utilizza un CoroutineScope fornito dalla composizione. Questo ambito viene automaticamente annullato quando l’elemento composable lascia la composizione. Questo legame garantisce che nessuna coroutine continui a essere eseguita dopo la chiusura dello schermo — questa è una differenza fondamentale rispetto alle coroutine globali nell’ambito ViewModel o Application.

Secondo Android Developers Blog (2025), LaunchedEffect è stato specificamente progettato per sostituire il pattern LiveData-osservatore nel mondo Compose. Invece di sottoscriversi a LiveData tramite observeAsState e gestire l’abbonamento separatamente, gli sviluppatori usano LaunchedEffect con collectAsState su Flow, che offre una gestione del ciclo di vita più prevedibile ed elimina le perdite di memoria inerenti agli abbonamenti senza annullamento esplicito.

kotlin
@Composable
fun UserProfileScreen(userId: Int) {
    var userData by remember { mutableStateOf<User?>(null) }
    
    LaunchedEffect(userId) {
        val result = userRepository.fetchUser(userId)
        userData = result
    }
    
    // Interfaccia utente basata su userData
}

Come funziona LaunchedEffect con le chiavi

Il meccanismo più importante di LaunchedEffect è il sistema di chiavi. Il primo parametro della funzione — vararg keys: Any? — determina quando l’effetto deve riavviarsi. LaunchedEffect memorizza i valori precedenti delle chiavi e li confronta con quelli nuovi a ogni ricomposizione. Se almeno una chiave è cambiata (tramite equals()), la coroutine corrente viene annullata e ne viene avviata una nuova.

Se la chiave è, ad esempio, userId, quando l’identificatore utente cambia, LaunchedEffect annullerà automaticamente la richiesta corrente e ne avvierà una nuova con l’userId aggiornato. Questo evita allo sviluppatore di dover annullare manualmente la richiesta precedente e verificare la pertinenza dei dati — tutto viene gestito in modo dichiarativo tramite le chiavi. Questo approccio si allinea al paradigma reattivo di Jetpack Compose.

Regola importante: se si passa una costante come chiave — LaunchedEffect(Unit) — l’effetto verrà eseguito solo una volta all’ingresso nella composizione, simile a onStart o onResume in Android classico. Se non si passano chiavi, l’effetto verrà eseguito una volta nella composizione. Se si passano parentesi vuote, LaunchedEffect non compilerà, poiché le chiavi sono un parametro obbligatorio.

kotlin
// Esecuzione una tantum quando lo schermo appare
LaunchedEffect(Unit) {
    analytics.logScreenView("Profile")
}

// Riavvia quando userId cambia
LaunchedEffect(userId) {
    loadUserData(userId)
}

// Chiavi multiple
LaunchedEffect(userId, filter, sortOrder) {
    fetchFilteredData(userId, filter, sortOrder)
}

Differenza tra LaunchedEffect e DisposableEffect

Sebbene entrambe le API appartengano agli effetti collaterali in Jetpack Compose, LaunchedEffect e DisposableEffect risolvono compiti fondamentalmente diversi. LaunchedEffect è progettato per coroutine asincrone con possibilità di riavvio tramite chiavi, mentre DisposableEffect è per operazioni sincrone di configurazione e pulizia senza coroutine.

La differenza principale è la presenza di onDispose in DisposableEffect. LaunchedEffect non ha un blocco di pulizia esplicito: l’annullamento della coroutine avviene automaticamente quando la chiave cambia o all’uscita dalla composizione, ma lo sviluppatore non può inserire codice personalizzato al momento dell’annullamento. DisposableEffect, al contrario, fornisce un blocco onDispose che viene eseguito garantitamente all’uscita dalla composizione, il che è fondamentale per liberare risorse native.

CaratteristicaLaunchedEffectDisposableEffect
EsecuzioneAsincrona (coroutine)Sincrona
onDisposeNo (annullamento automatico)Sì (blocco di pulizia esplicito)
ChiaviRiavvia + annulla vecchia coroutineEsegui onDispose + reinizializza
Utilizzo tipicoRichieste di rete, sottoscrizioni Flow, timerBroadcastReceiver, sensori, listener nativi
Annullamento all’uscitaAutomaticoTramite onDispose

Secondo l’articolo di Google “Compose Side Effects: Deep Dive” (2025), la scelta corretta tra LaunchedEffect e DisposableEffect è determinata dal tipo di risorsa: se l’operazione è una coroutine annullabile — usa LaunchedEffect. Se la risorsa richiede una chiamata esplicita a close(), unregister() o dispose() — usa DisposableEffect.

Caricamento dati tramite LaunchedEffect

Il caso d’uso più comune di LaunchedEffect è il caricamento di dati all’apertura di uno schermo. Il pattern è semplice: all’interno di LaunchedEffect viene chiamata una funzione suspend del repository o UseCase, il risultato viene assegnato a una variabile di stato e l’interfaccia utente viene ridisegnata automaticamente. LaunchedEffect garantisce che alla riapertura dello schermo (ad esempio, durante la navigazione indietro), il caricamento venga eseguito nuovamente se le chiavi sono cambiate.

Per visualizzare gli stati di caricamento, viene utilizzato un pattern a tre stati: Loading, Success, Error. LaunchedEffect viene racchiuso in try-catch, e in caso di successo viene impostato state = Success(data), in caso di errore — state = Error(exception). L’interfaccia utente reagisce allo stato e mostra lo schermo corrispondente: shimmer loader, dati o schermata di errore con pulsante di riprova.

Se è necessario caricare dati durante lo scorrimento (paginazione), LaunchedEffect viene combinato con LazyColumn e LazyListState: quando viene raggiunta la fine dell’elenco, la chiave di LaunchedEffect viene aggiornata (ad esempio, un contatore di pagine), che attiva il caricamento del lotto successivo di dati.

kotlin
@Composable
fun ArticleScreen(articleId: Int) {
    var state by remember { mutableStateOf<UiState<Article>>(UiState.Loading) }
    
    LaunchedEffect(articleId) {
        state = UiState.Loading
        state = try {
            UiState.Success(articleRepository.fetch(articleId))
        } catch (e: Exception) {
            UiState.Error(e)
        }
    }
    
    when (val s = state) {
        is UiState.Loading -> ShimmerPlaceholder()
        is UiState.Success -> ArticleContent(s.data)
        is UiState.Error -> ErrorScreen(s.error) 
            { // onRetry callback (state updates) }
    }
}

Gestione delle chiavi e riavvio

L’uso corretto delle chiavi di LaunchedEffect è fondamentale per lavorare efficacemente con gli effetti. Se la chiave è un valore mutabile che cambia frequentemente (ad esempio, il testo di una query di ricerca a ogni inserimento di carattere), ogni carattere annullerà la coroutine precedente e ne avvierà una nuova. Per la ricerca con debounce questo è eccessivo — è meglio usare debounce all’interno della coroutine stessa.

Per implementare debounce all’interno di LaunchedEffect, usa delay() prima di eseguire l’azione principale. Ad esempio, durante la ricerca: LaunchedEffect(query) viene lanciato a ogni modifica della query, ma prima di eseguire la richiesta c’è un delay(500). Se l’utente digita il carattere successivo prima che siano trascorsi 500 ms, la coroutine viene annullata (a causa del cambiamento di chiave) e ne viene avviata una nuova — quindi la richiesta viene inviata solo dopo una pausa di 500 ms nell’input.

Un’altra tecnica è usare una classe sealed come chiave. Ciò consente un controllo preciso su quando l’effetto deve riavviarsi. Ad esempio, una chiave wrapper contiene un identificatore e un flag di aggiornamento forzato: quando il flag passa da false a true, LaunchedEffect si riavvia anche se l’identificatore non è cambiato. Questo pattern è comodo per il pull-to-refresh.

kotlin
// Ricerca con debounce 500ms
LaunchedEffect(searchQuery) {
    delay(500)
    searchResults.value = repository.search(searchQuery)
}

// Pull-to-refresh con aggiornamento forzato
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }

LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
    articles = repository.loadUserArticles(userId)
}

Errori comuni con LaunchedEffect

Il primo e più comune errore è usare LaunchedEffect senza chiavi. Se scrivi LaunchedEffect { ... } senza argomenti, la coroutine si riavvierà a ogni ricomposizione, portando a un ciclo infinito di richieste. LaunchedEffect richiede almeno una chiave — di solito Unit per l’esecuzione una tantum.

Il secondo errore è tentare di usare LaunchedEffect per la sottoscrizione a Flow senza collect. Se chiami collect su un Flow all’interno di LaunchedEffect, la coroutine verrà sospesa fino al completamento del Flow (che nel caso di StateFlow non avviene mai). L’approccio corretto è usare collectLatest, che annulla la raccolta precedente all’arrivo di un nuovo valore.

Il terzo errore è passare oggetti annidati come chiavi. Se la chiave è una data class con campi mutabili (var), LaunchedEffect potrebbe non riconoscere il cambiamento, poiché Compose usa equals() per il confronto, che può comportarsi in modo imprevedibile con campi var. Usa sempre oggetti immutabili (val) o primitivi come chiavi di LaunchedEffect.

Domande frequenti

Cosa succede se non si passa una chiave a LaunchedEffect?

Se non si passano chiavi, LaunchedEffect non compilerà — Kotlin richiede almeno un argomento per i parametri vararg. Usa LaunchedEffect(Unit) per l’esecuzione una tantum all’ingresso nella composizione o passa valori specifici che devono attivare un riavvio quando cambiati.

LaunchedEffect può causare una perdita di memoria?

No, LaunchedEffect annulla automaticamente la coroutine quando il composable lascia la composizione, prevenendo perdite di memoria. Tuttavia, se la coroutine all’interno di LaunchedEffect mantiene un riferimento a un’Activity o Context tramite una chiusura, è possibile una perdita — usa viewModelScope per operazioni di lunga durata in ViewModel.

Qual è la differenza tra LaunchedEffect e rememberCoroutineScope?

LaunchedEffect esegue una coroutine automaticamente all’ingresso nella composizione con legame di chiave. rememberCoroutineScope fornisce un ambito per l’avvio manuale di coroutine, ad esempio, in risposta a onItemClick. Usa LaunchedEffect per effetti collaterali automatici e rememberCoroutineScope per avviare coroutine in base agli eventi dell’utente.

Perché LaunchedEffect viene eseguito più volte durante la ricomposizione?

Se la chiave di LaunchedEffect è un tipo instabile (ad esempio, var o una classe senza equals()), Compose potrebbe non riconoscere che il valore non è cambiato e riavvierà l’effetto a ogni ricomposizione. Soluzione: usa tipi stabili (primitivi, stringhe, data class con campi val) o avvolgi i valori mutabili in remember.

Come fermare manualmente LaunchedEffect?

Non esiste un modo diretto per fermare LaunchedEffect dall’esterno — il controllo è gestito tramite le chiavi. Cambia la chiave per annullare la coroutine corrente. Se hai bisogno del controllo completo sul ciclo di vita della coroutine, usa rememberCoroutineScope con Job e chiama manualmente job.cancel() su un evento o cambiamento di stato.

Riepilogo

  • LaunchedEffect — API di effetto collaterale in Jetpack Compose per lanciare coroutine asincrone legate al ciclo di vita del composable.
  • Chiavi — sistema di riavvio basato su chiavi: il cambiamento di chiave annulla la coroutine corrente e ne avvia una nuova con parametri aggiornati.
  • Annullamento automatico — la coroutine viene automaticamente annullata quando il composable lascia la composizione, prevenendo perdite di memoria.
  • Caricamento dati — pattern tipico: LaunchedEffect(chiave) per caricare dati dalla rete con gestione degli stati Loading, Success, Error.
  • Debounce — implementato tramite delay() all’interno di LaunchedEffect: se la chiave cambia prima della scadenza del ritardo, la coroutine viene annullata.
  • Sottoscrizione Flow — usa collectLatest invece di collect per una corretta gestione di Flow all’interno di LaunchedEffect.
  • Chiavi stabili — usa solo tipi immutabili (val, primitivi, data class) come chiavi per un comportamento prevedibile.

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