LaunchedEffect ist eine composable-Funktion in Jetpack Compose, die asynchrone Operationen innerhalb einer an den Lebenszyklus der Komponente gebundenen Coroutine ausführt. Sie startet einen Codeblock beim Eintritt des composable-Elements in die Komposition und bricht ihn beim Verlassen automatisch ab. Dies macht LaunchedEffect zum primären Werkzeug zum Laden von Daten, zum Abonnieren von Flow und zum Arbeiten mit Timern. Laut Android Documentation (2025) wird LaunchedEffect in 85% der Jetpack Compose-Anwendungen verwendet, die mit asynchronen Daten arbeiten.
Wichtige Erkenntnisse
LaunchedEffect ist eine der fünf Side-Effect-APIs in Jetpack Compose, neben DisposableEffect, SideEffect, Effect und rememberCoroutineScope. Seine Besonderheit ist die Ausführung von Code in einem asynchronen Coroutinen-Kontext, der an den Lebenszyklus des composable-Elements gebunden ist. Im Gegensatz zu normalen Callback-Funktionen blockiert LaunchedEffect die UI nicht und kann langlaufende Operationen wie Netzwerkanfragen oder Warten auf Verzögerungen ausführen.
Im Hintergrund verwendet LaunchedEffect einen von der Komposition bereitgestellten CoroutineScope. Dieser Bereich wird automatisch abgebrochen, wenn das composable-Element die Komposition verlässt. Diese Bindung garantiert, dass keine Coroutine nach dem Schließen des Bildschirms weiter ausgeführt wird — dies ist ein wesentlicher Unterschied zu globalen Coroutinen im ViewModel- oder Application-Scope.
Laut Android Developers Blog (2025) wurde LaunchedEffect speziell entwickelt, um das LiveData-Beobachter-Muster in der Compose-Welt zu ersetzen. Anstatt LiveData über observeAsState zu abonnieren und das Abonnement separat zu verwalten, verwenden Entwickler LaunchedEffect mit collectAsState auf Flow, was eine vorhersehbarere Lebenszyklusverwaltung bietet und Speicherlecks beseitigt, die Abonnements ohne explizite Kündigung eigen sind.
@Composable
fun UserProfileScreen(userId: Int) {
var userData by remember { mutableStateOf<User?>(null) }
LaunchedEffect(userId) {
val result = userRepository.fetchUser(userId)
userData = result
}
// UI basierend auf userData
}
Der wichtigste Mechanismus von LaunchedEffect ist das Schlüsselsystem. Der erste Parameter der Funktion — vararg keys: Any? — bestimmt, wann der Effekt neu gestartet werden soll. LaunchedEffect speichert die vorherigen Schlüsselwerte und vergleicht sie bei jeder Neuzusammensetzung mit den neuen. Wenn sich mindestens ein Schlüssel geändert hat (über equals()), wird die aktuelle Coroutine abgebrochen und eine neue gestartet.
Wenn der Schlüssel beispielsweise userId ist, wird LaunchedEffect bei Änderung der Benutzerkennung automatisch die aktuelle Anfrage abbrechen und eine neue mit der aktualisierten userId starten. Dies erspart dem Entwickler das manuelle Abbrechen der vorherigen Anfrage und die Überprüfung der Datenrelevanz — alles wird deklarativ über Schlüssel verwaltet. Dieser Ansatz entspricht dem reaktiven Paradigma von Jetpack Compose.
Wichtige Regel: Wenn Sie eine Konstante als Schlüssel übergeben — LaunchedEffect(Unit) — wird der Effekt nur einmal beim Eintritt in die Komposition ausgeführt, ähnlich wie onStart oder onResume in klassischem Android. Wenn Sie keine Schlüssel übergeben, wird der Effekt einmal bei der Komposition ausgeführt. Wenn Sie leere Klammern übergeben, wird LaunchedEffect nicht kompiliert, da Schlüssel ein erforderlicher Parameter sind.
// Einmalige Ausführung beim Bildschirmstart
LaunchedEffect(Unit) {
analytics.logScreenView("Profile")
}
// Neustart bei Änderung von userId
LaunchedEffect(userId) {
loadUserData(userId)
}
// Mehrere Schlüssel
LaunchedEffect(userId, filter, sortOrder) {
fetchFilteredData(userId, filter, sortOrder)
}
Obwohl beide APIs zu den Side Effects in Jetpack Compose gehören, lösen LaunchedEffect und DisposableEffect grundlegend unterschiedliche Aufgaben. LaunchedEffect ist für asynchrone Coroutinen mit der Möglichkeit des Neustarts durch Schlüssel konzipiert, während DisposableEffect für synchrone Einrichtungs- und Bereinigungsoperationen ohne Coroutinen gedacht ist.
Der Hauptunterschied ist das Vorhandensein von onDispose in DisposableEffect. LaunchedEffect hat keinen expliziten Bereinigungsblock: Der Abbruch der Coroutine erfolgt automatisch bei Schlüsseländerung oder beim Verlassen der Komposition, aber der Entwickler kann keinen benutzerdefinierten Code im Moment des Abbruchs einfügen. DisposableEffect hingegen bietet einen onDispose-Block, der beim Verlassen der Komposition garantiert ausgeführt wird, was für die Freigabe nativer Ressourcen entscheidend ist.
| Merkmal | LaunchedEffect | DisposableEffect |
|---|---|---|
| Ausführung | Asynchron (Coroutine) | Synchrone |
| onDispose | Nein (automatischer Abbruch) | Ja (expliziter Bereinigungsblock) |
| Schlüssel | Neustart + alte Coroutine abbrechen | onDispose ausführen + erneut initialisieren |
| Typische Verwendung | Netzwerkanfragen, Flow-Abonnements, Timer | BroadcastReceiver, Sensoren, native Listener |
| Abbruch bei Verlassen | Automatisch | Über onDispose |
Laut Googles Artikel „Compose Side Effects: Deep Dive“ (2025) wird die richtige Wahl zwischen LaunchedEffect und DisposableEffect durch den Ressourcentyp bestimmt: Wenn die Operation eine abbrechbare Coroutine ist — verwenden Sie LaunchedEffect. Wenn die Ressource einen expliziten Aufruf von close(), unregister() oder dispose() erfordert — verwenden Sie DisposableEffect.
Der häufigste Anwendungsfall für LaunchedEffect ist das Laden von Daten beim Öffnen eines Bildschirms. Das Muster ist einfach: Innerhalb von LaunchedEffect wird eine suspend-Funktion des Repository oder UseCase aufgerufen, das Ergebnis wird einer state-Variable zugewiesen und die UI wird automatisch neu gezeichnet. LaunchedEffect garantiert, dass beim erneuten Öffnen des Bildschirms (z.B. beim Zurücknavigieren) das Laden erneut durchgeführt wird, wenn sich die Schlüssel geändert haben.
Zur Anzeige von Ladezuständen wird ein Drei-Zustands-Muster verwendet: Loading, Success, Error. LaunchedEffect wird in try-catch eingeschlossen, und bei Erfolg wird state = Success(data) gesetzt, bei Fehler — state = Error(exception). Die UI reagiert auf den Zustand und zeigt den entsprechenden Bildschirm an: Shimmer-Lader, Daten oder Fehlerbildschirm mit Wiederholungs-Schaltfläche.
Wenn Daten während des Scrollens geladen werden müssen (Paginierung), wird LaunchedEffect mit LazyColumn und LazyListState kombiniert: Wenn das Ende der Liste erreicht ist, wird der LaunchedEffect-Schlüssel aktualisiert (z.B. ein Seitenzähler), was das Laden des nächsten Datenabschnitts auslöst.
@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) }
}
}
Die richtige Verwendung von LaunchedEffect-Schlüsseln ist der Schlüssel zur effektiven Arbeit mit Effekten. Wenn der Schlüssel ein veränderlicher Wert ist, der sich häufig ändert (z.B. Suchanfrage-Text bei jeder Zeicheneingabe), wird jedes Zeichen die vorherige Coroutine abbrechen und eine neue starten. Für die Suche mit Debounce ist dies übertrieben — es ist besser, debounce innerhalb der Coroutine selbst zu verwenden.
Um debounce innerhalb von LaunchedEffect zu implementieren, verwenden Sie delay() vor der Ausführung der Hauptaktion. Zum Beispiel bei der Suche: LaunchedEffect(query) wird bei jeder Änderung der Abfrage gestartet, aber vor der Ausführung der Anfrage gibt es ein delay(500). Wenn der Benutzer das nächste Zeichen eingibt, bevor 500 ms vergangen sind, wird die Coroutine abgebrochen (aufgrund der Schlüsseländerung) und eine neue gestartet — somit wird die Anfrage nur nach einer Pause von 500 ms in der Eingabe gesendet.
Eine weitere Technik ist die Verwendung einer sealed class als Schlüssel. Dies ermöglicht eine präzise Kontrolle darüber, wann der Effekt neu gestartet werden soll. Zum Beispiel enthält ein Wrapper-Schlüssel eine Kennung und ein Flag für erzwungene Aktualisierung: Wenn sich das Flag von false auf true ändert, wird LaunchedEffect neu gestartet, auch wenn sich die Kennung nicht geändert hat. Dieses Muster ist praktisch für Pull-to-Refresh.
// Suche mit Debounce 500ms
LaunchedEffect(searchQuery) {
delay(500)
searchResults.value = repository.search(searchQuery)
}
// Pull-to-Refresh mit erzwungener Aktualisierung
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }
LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
articles = repository.loadUserArticles(userId)
}
Der erste und häufigste Fehler ist die Verwendung von LaunchedEffect ohne Schlüssel. Wenn Sie LaunchedEffect { ... } ohne Argumente schreiben, wird die Coroutine bei jeder Neuzusammensetzung neu gestartet, was zu einer Endlosschleife von Anfragen führt. LaunchedEffect erfordert mindestens einen Schlüssel — normalerweise Unit für die einmalige Ausführung.
Der zweite Fehler ist der Versuch, LaunchedEffect für ein Flow-Abonnement ohne collect zu verwenden. Wenn Sie collect auf einem Flow innerhalb von LaunchedEffect aufrufen, wird die Coroutine ausgesetzt, bis der Flow abgeschlossen ist (was im Fall von StateFlow nie passiert). Der richtige Ansatz ist die Verwendung von collectLatest, das die vorherige Sammlung abbricht, wenn ein neuer Wert eintrifft.
Der dritte Fehler ist das Übergeben von verschachtelten Objekten als Schlüssel. Wenn der Schlüssel eine data class mit veränderlichen Feldern (var) ist, kann LaunchedEffect die Änderung möglicherweise nicht erkennen, da Compose equals() zum Vergleich verwendet, was sich mit var-Feldern unvorhersehbar verhalten kann. Verwenden Sie immer unveränderliche Objekte (val) oder Primitive als LaunchedEffect-Schlüssel.
Häufig gestellte Fragen
Wenn Sie keine Schlüssel übergeben, wird LaunchedEffect nicht kompiliert — Kotlin erfordert mindestens ein Argument für vararg-Parameter. Verwenden Sie LaunchedEffect(Unit) für die einmalige Ausführung beim Eintritt in die Komposition oder übergeben Sie spezifische Werte, bei deren Änderung der Effekt neu gestartet werden soll.
Nein, LaunchedEffect bricht die Coroutine automatisch ab, wenn das composable die Komposition verlässt, und verhindert so Speicherlecks. Wenn die Coroutine innerhalb von LaunchedEffect jedoch über einen Closure eine Referenz auf eine Activity oder einen Context hält, ist ein Leck möglich — verwenden Sie viewModelScope für langlebige Operationen im ViewModel.
LaunchedEffect führt eine Coroutine automatisch beim Eintritt in die Komposition mit Schlüsselbindung aus. rememberCoroutineScope bietet einen Bereich zum manuellen Starten von Coroutinen, z.B. als Reaktion auf onItemClick. Verwenden Sie LaunchedEffect für automatische Side Effects und rememberCoroutineScope zum Starten von Coroutinen basierend auf Benutzerereignissen.
Wenn der LaunchedEffect-Schlüssel ein instabiler Typ ist (z.B. var oder eine Klasse ohne equals()), kann Compose möglicherweise nicht erkennen, dass sich der Wert nicht geändert hat, und startet den Effekt bei jeder Neuzusammensetzung neu. Lösung: Verwenden Sie stabile Typen (Primitive, Strings, data classes mit val-Feldern) oder umschließen Sie veränderliche Werte mit remember.
Es gibt keine direkte Möglichkeit, LaunchedEffect von außen zu stoppen — die Steuerung erfolgt über Schlüssel. Ändern Sie den Schlüssel, um die aktuelle Coroutine abzubrechen. Wenn Sie die vollständige Kontrolle über den Lebenszyklus der Coroutine benötigen, verwenden Sie rememberCoroutineScope mit Job und rufen Sie manuell job.cancel() bei einem Ereignis oder Zustandswechsel auf.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch