LaunchedEffect — är en composable-funktion i Jetpack Compose, utformad för att utföra asynkrona operationer inom en korutin bunden till komponentens livscykel. Den startar ett kodblock när composable-elementet går in i kompositionen och avbryter det automatiskt vid utträde. Detta gör LaunchedEffect till det främsta verktyget för att ladda data, prenumerera på Flow och arbeta med timer. Enligt Android Documentation (2025) används LaunchedEffect i 85% av Jetpack Compose-applikationer som arbetar med asynkron data.
Huvudpunkter
LaunchedEffect — är ett av fem side-effect API:er i Jetpack Compose, tillsammans med DisposableEffect, SideEffect, SnapshotEffect och rememberCoroutineScope. Dess särdrag är att exekvera kod i den asynkrona kontexten av en korutin bunden till composable-elementets livscykel. Till skillnad från vanliga callback-funktioner blockerar LaunchedEffect inte UI och kan utföra långvariga operationer, såsom nätverksförfrågningar eller väntan på fördröjning.
Under huven använder LaunchedEffect CoroutineScope som tillhandahålls av kompositionen. Denna scope avbryts automatiskt när composable-elementet lämnar kompositionen. En sådan bindning garanterar att ingen korutin fortsätter att köras efter att skärmen stängts — detta är den viktigaste skillnaden från globala korutiner i ViewModel eller Application scope.
Enligt Android Developers Blog (2025) är LaunchedEffect speciellt utformad för att ersätta LiveData-observer-mönstret i Compose-världen. Istället för att prenumerera på LiveData via observeAsState och hantera prenumerationen separat, använder utvecklare LaunchedEffect med collectAsState på Flow, vilket ger mer förutsägbar livscykelhantering och eliminerar minnesläckor som är typiska för prenumerationer utan explicit avbrytning.
@Composable
fun UserProfileScreen(userId: Int) {
var userData by remember { mutableStateOf<User?>(null) }
LaunchedEffect(userId) {
val result = userRepository.fetchUser(userId)
userData = result
}
// UI baserad på userData
}
Den viktigaste mekanismen i LaunchedEffect — är nyckelsystemet. Funktionens första parameter — vararg keys: Any? — bestämmer när effekten ska startas om. LaunchedEffect sparar tidigare nyckelvärden och vid varje rekomposition jämför dem med de nya. Om minst en nyckel har ändrats (via equals()), avbryts den aktuella korutinen och en ny startas.
Om nyckeln till exempel är userId, kommer LaunchedEffect automatiskt att avbryta den aktuella förfrågan när användaridentifikatorn ändras och starta en ny med uppdaterad userId. Detta befriar utvecklaren från manuell avbrytning av föregående förfrågan och kontroll av dataaktualitet — allt hanteras deklarativt via nycklar. Ett sådant tillvägagångssätt överensstämmer med Jetpack Composes reaktiva paradigm.
Viktig regel: om en konstant skickas som nyckel — LaunchedEffect(Unit) — utförs effekten endast en gång vid inträde i kompositionen, som en analog till onStart eller onResume i klassisk Android. Om inga nycklar skickas — LaunchedEffect(Unit) — körs effekten en gång. Om tomma parenteser skickas — LaunchedEffect kompileras inte, eftersom nycklar är en obligatorisk parameter.
// Engångsexekvering när skärmen visas
LaunchedEffect(Unit) {
analytics.logScreenView("Profile")
}
// Omstart när userId ändras
LaunchedEffect(userId) {
loadUserData(userId)
}
// Flera nycklar
LaunchedEffect(userId, filter, sortOrder) {
fetchFilteredData(userId, filter, sortOrder)
}
Även om båda API:erna tillhör side effects i Jetpack Compose, löser LaunchedEffect och DisposableEffect fundamentalt olika uppgifter. LaunchedEffect är avsedd för asynkrona korutiner med möjlighet till omstart baserat på nycklar, medan DisposableEffect är för synkrona konfigurations- och resursrensningsoperationer utan korutiner.
Den största skillnaden — förekomsten av onDispose i DisposableEffect. LaunchedEffect har inget explicit rensningsblock: avbrytning av korutinen sker automatiskt vid nyckeländring eller utträde från kompositionen, men utvecklaren kan inte infoga sin egen kod vid detta avbrott. DisposableEffect å andra sidan tillhandahåller onDispose-blocket som garanterat exekveras vid utträde från kompositionen, vilket är kritiskt för att frigöra naturliga resurser.
| Egenskap | LaunchedEffect | DisposableEffect |
|---|---|---|
| Exekvering | Asynkron (korutin) | Synkron |
| onDispose | Nej (automatisk avbrytning av korutin) | Ja (explicit rensningsblock) |
| Nycklar | Omstart + avbrytning av gammal korutin | Exekvering av onDispose + ominitialisering |
| Typisk tillämpning | Nätverksförfrågningar, Flow-prenumerationer, timer | BroadcastReceiver, sensorer, naturliga listener |
| Avbrytning vid utträde | Automatisk | Via onDispose |
Enligt Google-artikeln „Compose Side Effects: Deep Dive” (2025) bestäms det korrekta valet mellan LaunchedEffect och DisposableEffect av resurstypen: om operationen är en korutin med avbrytningsmöjlighet — använd LaunchedEffect. Om resursen kräver explicit anrop av close(), unregister() eller dispose() — använd DisposableEffect.
Det vanligaste användningsscenariot för LaunchedEffect — att ladda data när skärmen öppnas. Mönstret är enkelt: inuti LaunchedEffect anropas suspend-funktionen för datalagret eller UseCase, resultatet tilldelas en state-variabel, och UI ritas automatiskt om. LaunchedEffect garanterar att när skärmen öppnas igen (till exempel vid återgång) utförs laddningen igen om nycklarna har ändrats.
För att visa laddningsstatus används en triad av tillstånd: Loading, Success, Error. LaunchedEffect är inslagen i try-catch, och vid framgång sätts state = Success(data), vid fel — state = Error(exception). UI reagerar på tillståndet och visar motsvarande skärm: shimmer-laddare, data eller felskärm med knapp för nytt försök.
Om data måste laddas vid scrollning (pagination), kombineras LaunchedEffect med LazyColumn och LazyListState: när slutet av listan nås uppdateras LaunchedEffect-nyckeln (till exempel sidräknaren), vilket utlöser laddning av nästa datadel.
@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) }
}
}
Korrekt användning av LaunchedEffect-nycklar — nyckeln till effektivt arbete med effekter (här återspeglar ordleken essensen). Om nyckeln är ett muterbart värde som ändras ofta (till exempel sökfrågetexten vid varje inmatat tecken), kommer varje tecken att avbryta föregående korutin och starta en ny. För sökning med fördröjning är detta överflödigt — det är bättre att använda debounce inom själva korutinen.
För att implementera debounce inom LaunchedEffect, använd delay() före utförandet av huvudåtgärden. Till exempel vid sökning: LaunchedEffect(query) startas vid varje ändring av query, men före utförandet av förfrågan finns delay(500). Om användaren anger nästa tecken tidigare än 500 ms, avbryts korutinen (på grund av nyckeländring) och en ny startas — således skickas förfrågan först efter 500 ms paus i inmatningen.
En annan teknik — användning av sealed class som nyckel. Detta möjliggör exakt kontroll över när effekten ska startas om. Till exempel innehåller en nyckelomslag identifieraren och en flagga för tvingad uppdatering: när flaggan ändras från false till true startas LaunchedEffect om även om identifieraren inte har ändrats. Ett sådant mönster är praktiskt för pull-to-refresh.
// Sökning med debounce 500ms
LaunchedEffect(searchQuery) {
delay(500)
searchResults.value = repository.search(searchQuery)
}
// Pull-to-refresh med tvingad uppdatering
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }
LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
articles = repository.loadUserArticles(userId)
}
Det första och vanligaste misstaget — användning av LaunchedEffect utan nycklar. Om du skriver LaunchedEffect { ... } utan argument kommer korutinen att startas om vid varje rekomposition, vilket leder till en oändlig loop av förfrågningar. LaunchedEffect kräver minst en nyckel — vanligtvis Unit för engångsexekvering.
Andra misstaget — försök att använda LaunchedEffect för att prenumerera på Flow utan collect. Om du anropar collect på Flow inuti LaunchedEffect, kommer korutinen att pausas tills Flow slutförs (vilket i fallet med StateFlow aldrig händer), och onDispose-blocket kommer inte att kunna avslutas normalt. Rätt tillvägagångssätt — användning av collectLatest, som avbryter föregående samling vid mottagande av ett nytt värde.
Tredje misstaget — skicka nästlade objekt som nyckel. Om nyckeln är en data class med föränderliga fält (var), kan LaunchedEffect inte känna igen ändringen, eftersom Compose använder equals() för jämförelse, och för var-fält kan det fungera oförutsägbart. Använd alltid oföränderliga objekt (val) eller primitiver som nycklar för LaunchedEffect.
Vanliga frågor
Om inga nycklar skickas, kommer LaunchedEffect inte att kompileras — Kotlin kräver minst ett argument för vararg-parametrar. Använd LaunchedEffect(Unit) för engångsexekvering vid inträde i kompositionen eller skicka specifika värden vid vars ändring effekten ska startas om.
Nej, LaunchedEffect avbryter automatiskt korutinen när composable lämnar kompositionen, vilket förhindrar minnesläckor. Men om korutinen inuti LaunchedEffect håller en referens till Activity eller Context genom en closure, är en läcka möjlig — använd viewModelScope för långlivade operationer i ViewModel.
LaunchedEffect exekverar korutinen automatiskt vid inträde i kompositionen med bindning till nycklar. rememberCoroutineScope tillhandahåller en scope för manuell start av korutiner, till exempel som svar på onItemClick. Använd LaunchedEffect för automatiska side effects och rememberCoroutineScope för att starta korutiner baserat på användarhändelser.
Om LaunchedEffect-nyckeln är en instabil typ (till exempel var eller en klass utan equals()), kan Compose inte känna igen att värdet inte har ändrats och kommer att starta om effekten vid varje rekomposition. Lösning: använd stabila typer (primitiver, strängar, data class med val-fält) eller linda in muterbara värden i remember.
Det finns inget direkt sätt att stoppa LaunchedEffect utifrån — kontrollen sker via nycklar. Ändra nyckeln för att avbryta den aktuella korutinen. Om du behöver fullständig kontroll över korutinens livscykel, använd rememberCoroutineScope med Job och anropa manuellt job.cancel() vid händelse eller tillståndsändring.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också