LaunchedEffect: co to jest, korutyny i zarządzanie w Jetpack Compose

Autor: IT Sectr Opublikowano: 2026-06-30 Czas czytania: 9 min

LaunchedEffect — to funkcja composable w Jetpack Compose, przeznaczona do wykonywania operacji asynchronicznych wewnątrz korutyny, powiązanej z cyklem życia komponentu. Uruchamia blok kodu przy wejściu elementu composable do kompozycji i automatycznie go anuluje przy wyjściu. To czyni LaunchedEffect głównym narzędziem do ładowania danych, subskrypcji Flow i pracy z timerami. Według Android Documentation (2025), LaunchedEffect jest używany w 85% aplikacji na Jetpack Compose pracujących z danymi asynchronicznymi.

Najważniejsze

  • LaunchedEffect — side-effect API do uruchamiania korutyn w kontekście kompozycji.
  • Klucze — przy zmianie kluczy korutyna jest anulowana i uruchamiana ponownie z nowymi wartościami.
  • Autoanulowanie — korutyna automatycznie anuluje się przy wyjściu component z kompozycji.
  • Asynchroniczność — blok wykonuje się w CoroutineScope z dyspozytorem Dispatchers.Main.
  • Ładowanie danych — typowy scenariusz: ładowanie z sieci przy pierwszym pojawieniu się ekranu.

Co to jest LaunchedEffect w Jetpack Compose

LaunchedEffect — to jeden z pięciu side-effect API w Jetpack Compose, obok DisposableEffect, SideEffect, SnapshotEffect i rememberCoroutineScope. Jego cechą szczególną jest wykonywanie kodu w asynchronicznym kontekście korutyny, powiązanym z cyklem życia elementu composable. W przeciwieństwie do zwykłych funkcji callback, LaunchedEffect nie blokuje UI i może wykonywać długotrwałe operacje, takie jak zapytania sieciowe lub oczekiwanie na opóźnienie.

Pod maską LaunchedEffect używa CoroutineScope, który jest dostarczany przez kompozycję. Ten scope jest automatycznie anulowany, gdy element composable opuszcza kompozycję. Takie powiązanie gwarantuje, że żadna korutyna nie będzie kontynuować działania po zamknięciu ekranu — to kluczowa różnica w porównaniu z globalnymi korutynami w ViewModel lub Application scope.

Według Android Developers Blog (2025), LaunchedEffect został specjalnie zaprojektowany do zastąpienia wzorca LiveData-observer w świecie Compose. Zamiast subskrybować LiveData przez observeAsState i osobno zarządzać subskrypcją, programiści używają LaunchedEffect z collectAsState na Flow, co daje bardziej przewidywalne zarządzanie cyklem życia i eliminuje wycieki pamięci charakterystyczne dla subskrypcji bez jawnego anulowania.

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

Jak działa LaunchedEffect z kluczami

Najważniejszy mechanizm LaunchedEffect — to system kluczy. Pierwszy parametr funkcji — vararg keys: Any? — określa, kiedy efekt powinien zostać ponownie uruchomiony. LaunchedEffect zapisuje poprzednie wartości kluczy i przy każdej rekompozycji porównuje je z nowymi. Jeśli choć jeden klucz się zmienił (przez equals()), bieżąca korutyna jest anulowana i uruchamiana jest nowa.

Jeśli kluczem jest na przykład userId, to przy zmianie identyfikatora użytkownika LaunchedEffect automatycznie anuluje bieżące zapytanie i uruchomi nowe z zaktualizowanym userId. To zwalnia programistę z ręcznego anulowania poprzedniego zapytania i sprawdzania aktualności danych — wszystko jest zarządzane deklaratywnie przez klucze. Takie podejście odpowiada reaktywnej paradigmie Jetpack Compose.

Ważna zasada: jeśli przekazać stałą jako klucz — LaunchedEffect(Unit) — efekt wykona się tylko raz przy wejściu do kompozycji, jako analog onStart lub onResume w klasycznym Androidzie. Jeśli nie przekazywać kluczy — LaunchedEffect(Unit) — efekt zadziała raz. Jeśli przekazać puste nawiasy — LaunchedEffect nie skompiluje się, ponieważ klucze są obowiązkowym parametrem.

kotlin
// Jednorazowe wykonanie po pojawieniu się ekranu
LaunchedEffect(Unit) {
    analytics.logScreenView("Profile")
}

// Restart przy zmianie userId
LaunchedEffect(userId) {
    loadUserData(userId)
}

// Wiele kluczy
LaunchedEffect(userId, filter, sortOrder) {
    fetchFilteredData(userId, filter, sortOrder)
}

Różnica między LaunchedEffect a DisposableEffect

Chociaż oba API należą do side effects w Jetpack Compose, LaunchedEffect i DisposableEffect rozwiązują zasadniczo różne zadania. LaunchedEffect jest przeznaczony do asynchronicznych korutyn z możliwością ponownego uruchamiania według kluczy, podczas gdy DisposableEffect — do synchronicznych operacji konfiguracji i zwalniania zasobów bez korutyn.

Główna różnica — obecność onDispose w DisposableEffect. LaunchedEffect nie ma jawnego bloku oczyszczania: anulowanie korutyny następuje automatycznie przy zmianie klucza lub wyjściu z kompozycji, ale programista nie może wstawić własnego kodu w momencie tego anulowania. DisposableEffect natomiast dostarcza blok onDispose, który jest gwarantowanie wykonywany przy wyjściu z kompozycji, co jest krytyczne dla zwalniania natywnych zasobów.

CharakterystykaLaunchedEffectDisposableEffect
WykonanieAsynchroniczne (korutyna)Synchroniczne
onDisposeNie (autoanulowanie korutyny)Tak (jawny blok oczyszczania)
KluczePonowne uruchomienie + anulowanie starej korutynyWykonanie onDispose + ponowna inicjalizacja
Typowe zastosowanieZapytania sieciowe, subskrypcje Flow, timeryBroadcastReceiver, czujniki, natywne listener
Anulowanie przy wyjściuAutomatycznePrzez onDispose

Według Artykułu Google „Compose Side Effects: Deep Dive” (2025), prawidłowy wybór między LaunchedEffect a DisposableEffect określa typ zasobu: jeśli operacja to korutyna z możliwością anulowania — użyj LaunchedEffect. Jeśli zasób wymaga jawnego wywołania close(), unregister() lub dispose() — użyj DisposableEffect.

Ładowanie danych przez LaunchedEffect

Najczęstszy scenariusz użycia LaunchedEffect — ładowanie danych przy otwarciu ekranu. Wzór jest prosty: wewnątrz LaunchedEffect wywoływana jest suspend-funkcja repozytorium lub UseCase, wynik przypisywany jest do zmiennej stanu, a UI automatycznie się przerysowuje. LaunchedEffect gwarantuje, że przy ponownym otwarciu ekranu (na przykład po powrocie) ładowanie jest wykonywane ponownie, jeśli klucze się zmieniły.

Do wyświetlania stanów ładowania używana jest trójka stanów: Loading, Success, Error. LaunchedEffect jest owinięty w try-catch, a przy sukcesie ustawiany jest state = Success(data), przy błędzie — state = Error(exception). UI reaguje na state i wyświetla odpowiedni ekran: shimmer-ładowarkę, dane lub ekran błędu z przyciskiem ponawiania.

Jeśli wymagane jest ładowanie danych przy przewijaniu (pagination), LaunchedEffect jest łączony z LazyColumn i LazyListState: po osiągnięciu końca listy klucz LaunchedEffect jest aktualizowany (na przykład licznik stron), co wyzwala ładowanie następnej porcji danych.

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

Zarządzanie kluczami i ponowne uruchamianie

Prawidłowe używanie kluczy LaunchedEffect — klucz do efektywnej pracy z efektami (tu gra słów oddaje sedno). Jeśli klucz to mutable wartość, która często się zmienia (na przykład tekst zapytania wyszukiwania przy każdym wprowadzaniu znaku), każdy znak będzie anulować poprzednią korutynę i uruchamiać nową. Dla wyszukiwania z opóźnieniem jest to zbędne — lepiej użyć debounce wewnątrz samej korutyny.

Do implementacji debounce wewnątrz LaunchedEffect użyj delay() przed wykonaniem głównego działania. Na przykład przy wyszukiwaniu: LaunchedEffect(query) uruchamia się przy każdej zmianie query, ale przed wykonaniem zapytania następuje delay(500). Jeśli użytkownik wprowadzi następny znak wcześniej niż po 500 ms, korutyna jest anulowana (z powodu zmiany klucza) i uruchamiana jest nowa — w ten sposób zapytanie jest wysyłane dopiero po 500 ms pauzy w wprowadzaniu.

Inna technika — użycie sealed class jako klucza. Pozwala to precyzyjnie kontrolować, kiedy efekt powinien zostać ponownie uruchomiony. Na przykład klucz-opakowanie zawiera identyfikator i flagę wymuszonej aktualizacji: przy zmianie flagi z false na true LaunchedEffect uruchamia się ponownie, nawet jeśli identyfikator się nie zmienił. Taki wzór jest wygodny dla pull-to-refresh.

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

// Pull-to-refresh z wymuszoną aktualizacją
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }

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

Typowe błędy z LaunchedEffect

Pierwszy i najczęstszy błąd — użycie LaunchedEffect bez kluczy. Jeśli napisać LaunchedEffect { ... } bez argumentów, korutyna będzie uruchamiana ponownie przy każdej rekompozycji, co doprowadzi do nieskończonej pętli zapytań. LaunchedEffect wymaga co najmniej jednego klucza — zwykle Unit dla jednorazowego wykonania.

Drugi błąd — próba użycia LaunchedEffect do subskrypcji Flow bez collect. Jeśli wewnątrz LaunchedEffect wywołać collect na Flow, korutyna zostanie wstrzymana do zakończenia Flow (które w przypadku StateFlow nigdy nie następuje), a blok onDispose nie będzie mógł normalnie się zakończyć. Prawidłowym podejściem jest użycie collectLatest, które anuluje poprzednią kolekcję po otrzymaniu nowej wartości.

Trzeci błąd — przekazywanie zagnieżdżonych obiektów jako klucza. Jeśli klucz to data class z mutowalnymi polami (var), LaunchedEffect może nie rozpoznać zmiany, ponieważ Compose używa equals() do porównania, a on dla var-pól może działać nieprzewidywalnie. Zawsze używaj immutable obiektów (val) lub prymitywów jako kluczy LaunchedEffect.

Często zadawane pytania

Co się stanie, jeśli nie przekazać klucza w LaunchedEffect?

Jeśli nie przekazać kluczy, LaunchedEffect nie skompiluje się — Kotlin wymaga co najmniej jednego argumentu dla vararg parameters. Użyj LaunchedEffect(Unit) do jednorazowego wykonania przy wejściu do kompozycji lub przekazuj konkretne wartości, przy zmianie których efekt powinien być ponownie uruchamiany.

Czy LaunchedEffect może spowodować wyciek pamięci?

Nie, LaunchedEffect automatycznie anuluje korutynę przy wyjściu composable z kompozycji, co zapobiega wyciekom pamięci. Jednak jeśli korutyna wewnątrz LaunchedEffect przechowuje referencję do Activity lub Context przez domknięcie, wyciek jest możliwy — użyj viewModelScope dla długożywotnych operacji w ViewModel.

Jaka jest różnica między LaunchedEffect a rememberCoroutineScope?

LaunchedEffect wykonuje korutynę automatycznie przy wejściu do kompozycji z powiązaniem z kluczami. rememberCoroutineScope dostarcza scope do ręcznego uruchamiania korutyn, na przykład w odpowiedzi na onItemClick. Użyj LaunchedEffect do automatycznych side effects i rememberCoroutineScope do uruchamiania korutyn na zdarzenia użytkownika.

Dlaczego LaunchedEffect wykonuje się kilka razy przy rekompozycji?

Jeśli klucz LaunchedEffect to niestabilny typ (na przykład var lub klasa bez equals()), Compose może nie rozpoznać, że wartość się nie zmieniła, i będzie ponownie uruchamiać efekt przy każdej rekompozycji. Rozwiązanie: używaj stabilnych typów (prymitywy, stringi, data class z val polami) lub zawiń mutable wartości w remember.

Jak ręcznie zatrzymać LaunchedEffect?

Nie ma bezpośredniego sposobu na zatrzymanie LaunchedEffect z zewnątrz — sterowanie odbywa się przez klucze. Zmień klucz, aby anulować bieżącą korutynę. Jeśli potrzebujesz pełnej kontroli nad cyklem życia korutyny, użyj rememberCoroutineScope z Job i ręcznie wywołuj job.cancel() na zdarzenie lub przy zmianie stanu.

Podsumowanie

  • LaunchedEffect — side-effect API Jetpack Compose do uruchamiania asynchronicznych korutyn powiązanych z cyklem życia composable.
  • Klucze — system ponownego uruchamiania według kluczy: zmiana klucza anuluje bieżącą korutynę i uruchamia nową z zaktualizowanymi parametrami.
  • Autoanulowanie — korutyna automatycznie anuluje się przy wyjściu composable z kompozycji, zapobiegając wyciekom pamięci.
  • Ładowanie danych — typowy wzór: LaunchedEffect(key) do ładowania danych z sieci z obsługą stanów Loading, Success, Error.
  • Debounce — implementowany przez delay() wewnątrz LaunchedEffect: przy zmianie klucza przed upływem opóźnienia korutyna jest anulowana.
  • Subskrypcja Flow — używaj collectLatest zamiast collect do poprawnej obsługi Flow wewnątrz LaunchedEffect.
  • Stabilne klucze — stosuj tylko immutable typy (val, prymitywy, data class) jako klucze dla przewidywalnego działania.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również