LaunchedEffect — je composable funkce v Jetpack Compose určená k provádění asynchronních operací uvnitř korutiny vázané na životní cyklus komponenty. Spouští blok kódu při vstupu prvku composable do kompozice a automaticky jej ruší při výstupu. To činí LaunchedEffect hlavním nástrojem pro načítání dat, předplatné Flow a práci s časovači. Podle Android Documentation (2025) se LaunchedEffect používá v 85 % aplikací Jetpack Compose pracujících s asynchronními daty.
Hlavní body
LaunchedEffect — je jedním z pěti side-effect API v Jetpack Compose, vedle DisposableEffect, SideEffect, SnapshotEffect a rememberCoroutineScope. Jeho zvláštností je provádění kódu v asynchronním kontextu korutiny vázané na životní cyklus prvku composable. Na rozdíl od běžných callback funkcí LaunchedEffect neblokuje UI a může provádět dlouhodobé operace, jako jsou síťové požadavky nebo čekání na zpoždění.
Pod kapotou LaunchedEffect používá CoroutineScope, který je poskytován kompozicí. Tento scope je automaticky zrušen, když prvek composable opustí kompozici. Takové vázání zaručuje, že žádná korutina nebude pokračovat v běhu po zavření obrazovky — to je klíčový rozdíl od globálních korutin ve ViewModel nebo Application scope.
Podle Android Developers Blog (2025) byl LaunchedEffect speciálně navržen k nahrazení vzoru LiveData-observer ve světě Compose. Místo předplatného LiveData přes observeAsState a samostatné správy předplatného vývojáři používají LaunchedEffect s collectAsState na Flow, což poskytuje předvídatelnější správu životního cyklu a eliminuje úniky paměti charakteristické pro předplatná bez explicitního zrušení.
@Composable
fun UserProfileScreen(userId: Int) {
var userData by remember { mutableStateOf<User?>(null) }
LaunchedEffect(userId) {
val result = userRepository.fetchUser(userId)
userData = result
}
// Uživatelské rozhraní založené na userData
}
Nejdůležitější mechanismus LaunchedEffect — je systém klíčů. První parametr funkce — vararg keys: Any? — určuje, kdy má být efekt restartován. LaunchedEffect ukládá předchozí hodnoty klíčů a při každé rekompozici je porovnává s novými. Pokud se alespoň jeden klíč změnil (přes equals()), aktuální korutina je zrušena a je spuštěna nová.
Pokud je klíčem například userId, pak při změně identifikátoru uživatele LaunchedEffect automaticky zruší aktuální požadavek a spustí nový s aktualizovaným userId. To zbavuje vývojáře ručního rušení předchozího požadavku a kontroly aktuálnosti dat — vše je řízeno deklarativně pomocí klíčů. Takový přístup odpovídá reaktivnímu paradigmatu Jetpack Compose.
Důležité pravidlo: pokud je jako klíč předána konstanta — LaunchedEffect(Unit) — efekt se provede pouze jednou při vstupu do kompozice, jako analogie onStart nebo onResume v klasickém Androidu. Pokud nejsou předány klíče — LaunchedEffect(Unit) — efekt se spustí jednou. Pokud jsou předány prázdné závorky — LaunchedEffect se nezkompiluje, protože klíče jsou povinným parametrem.
// Jednorázové provedení při zobrazení obrazovky
LaunchedEffect(Unit) {
analytics.logScreenView("Profile")
}
// Restart při změně userId
LaunchedEffect(userId) {
loadUserData(userId)
}
// Více klíčů
LaunchedEffect(userId, filter, sortOrder) {
fetchFilteredData(userId, filter, sortOrder)
}
Ačkoli obě API patří mezi side effects v Jetpack Compose, LaunchedEffect a DisposableEffect řeší zásadně odlišné úkoly. LaunchedEffect je určen pro asynchronní korutiny s možností restartu podle klíčů, zatímco DisposableEffect je pro synchronní operace konfigurace a čištění zdrojů bez korutin.
Hlavní rozdíl — přítomnost onDispose v DisposableEffect. LaunchedEffect nemá explicitní blok čištění: zrušení korutiny probíhá automaticky při změně klíče nebo výstupu z kompozice, ale vývojář nemůže vložit svůj vlastní kód v okamžiku tohoto zrušení. DisposableEffect naopak poskytuje blok onDispose, který je zaručeně proveden při výstupu z kompozice, což je kritické pro uvolnění nativních zdrojů.
| Vlastnost | LaunchedEffect | DisposableEffect |
|---|---|---|
| Provádění | Asynchronní (korutina) | Synchromní |
| onDispose | Ne (automatické zrušení korutiny) | Ano (explicitní blok čištění) |
| Klíče | Restart + zrušení staré korutiny | Provedení onDispose + reinicializace |
| Typické použití | Síťové požadavky, předplatná Flow, časovače | BroadcastReceiver, senzory, nativní listenery |
| Zrušení při výstupu | Automatické | Přes onDispose |
Podle článku Google „Compose Side Effects: Deep Dive” (2025) je správná volba mezi LaunchedEffect a DisposableEffect určena typem zdroje: pokud je operace korutina s možností zrušení — použijte LaunchedEffect. Pokud zdroj vyžaduje explicitní volání close(), unregister() nebo dispose() — použijte DisposableEffect.
Nejčastější scénář použití LaunchedEffect — načítání dat při otevření obrazovky. Vzor je jednoduchý: uvnitř LaunchedEffect je volána suspend funkce repository nebo UseCase, výsledek je přiřazen proměnné stavu a UI se automaticky překreslí. LaunchedEffect zaručuje, že při opětovném otevření obrazovky (například při návratu zpět) je načítání provedeno znovu, pokud se klíče změnily.
Pro zobrazení stavů načítání se používá trojice stavů: Loading, Success, Error. LaunchedEffect je zabalen do try-catch a při úspěchu se nastaví state = Success(data), při chybě — state = Error(exception). UI reaguje na stav a zobrazí odpovídající obrazovku: shimmer načítání, data nebo obrazovku chyby s tlačítkem opakování.
Pokud je vyžadováno načítání dat při rolování (pagination), LaunchedEffect je kombinován s LazyColumn a LazyListState: při dosažení konce seznamu je klíč LaunchedEffect aktualizován (například počítadlo stránek), což spustí načítání další části dat.
@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) }
}
}
Správné použití klíčů LaunchedEffect — klíč k efektivní práci s efekty (zde slovní hříčka odráží podstatu). Pokud je klíč měnitelná hodnota, která se často mění (například text vyhledávacího dotazu při každém zadání znaku), každý znak zruší předchozí korutinu a spustí novou. Pro vyhledávání se zpožděním je to nadbytečné — je lepší použít debounce uvnitř samotné korutiny.
Pro implementaci debounce uvnitř LaunchedEffect použijte delay() před provedením hlavní akce. Například při vyhledávání: LaunchedEffect(query) se spouští při každé změně query, ale před provedením požadavku je delay(500). Pokud uživatel zadá následující znak dříve než za 500 ms, korutina je zrušena (kvůli změně klíče) a spustí se nová — tím je požadavek odeslán až po 500 ms pauze v zadávání.
Další technika — použití sealed class jako klíče. To umožňuje přesnou kontrolu nad tím, kdy má být efekt restartován. Například klíč-obal obsahuje identifikátor a příznak vynucené aktualizace: při změně příznaku z false na true se LaunchedEffect restartuje, i když se identifikátor nezměnil. Takový vzor je vhodný pro pull-to-refresh.
// Vyhledávání s debounce 500ms
LaunchedEffect(searchQuery) {
delay(500)
searchResults.value = repository.search(searchQuery)
}
// Pull-to-refresh s vynucenou aktualizací
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }
LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
articles = repository.loadUserArticles(userId)
}
První a nejčastější chyba — použití LaunchedEffect bez klíčů. Pokud napíšete LaunchedEffect { ... } bez argumentů, korutina se bude restartovat při každé rekompozici, což povede k nekonečnému cyklu požadavků. LaunchedEffect vyžaduje alespoň jeden klíč — obvykle Unit pro jednorázové provedení.
Druhá chyba — pokus o použití LaunchedEffect pro předplatné Flow bez collect. Pokud uvnitř LaunchedEffect zavoláte collect na Flow, korutina se pozastaví do dokončení Flow (které v případě StateFlow nikdy nenastane) a blok onDispose se nebude moci normálně dokončit. Správný přístup — použití collectLatest, které ruší předchozí kolekci při přijetí nové hodnoty.
Třetí chyba — předávání vnořených objektů jako klíče. Pokud je klíč data class s měnitelnými poli (var), LaunchedEffect nemusí rozpoznat změnu, protože Compose používá equals() pro porovnání, a ten pro var-pole může fungovat nepředvídatelně. Vždy používejte neměnné objekty (val) nebo primitivy jako klíče LaunchedEffect.
Často kladené otázky
Pokud nepředáte klíče, LaunchedEffect se nezkompiluje — Kotlin vyžaduje alespoň jeden argument pro parametry vararg. Použijte LaunchedEffect(Unit) pro jednorázové provedení při vstupu do kompozice nebo předejte konkrétní hodnoty, při jejichž změně má být efekt restartován.
Ne, LaunchedEffect automaticky ruší korutinu při výstupu composable z kompozice, což zabraňuje únikům paměti. Pokud však korutina uvnitř LaunchedEffect drží odkaz na Activity nebo Context prostřednictvím uzávěru, únik je možný — použijte viewModelScope pro dlouho žijící operace ve ViewModel.
LaunchedEffect provádí korutinu automaticky při vstupu do kompozice s vazbou na klíče. rememberCoroutineScope poskytuje scope pro ruční spouštění korutin, například jako odpověď na onItemClick. Použijte LaunchedEffect pro automatické side effects a rememberCoroutineScope pro spouštění korutin na základě událostí uživatele.
Pokud je klíč LaunchedEffect nestabilní typ (například var nebo třída bez equals()), Compose nemusí rozpoznat, že se hodnota nezměnila, a bude restartovat efekt při každé rekompozici. Řešení: používejte stabilní typy (primitivy, řetězce, data class s poli val) nebo zabalte měnitelné hodnoty do remember.
Neexistuje přímý způsob, jak zastavit LaunchedEffect zvenčí — ovládání probíhá přes klíče. Změňte klíč pro zrušení aktuální korutiny. Pokud potřebujete úplnou kontrolu nad životním cyklem korutiny, použijte rememberCoroutineScope s Job a ručně volejte job.cancel() při události nebo změně stavu.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také