LaunchedEffect — este o funcție composable în Jetpack Compose, destinată executării operațiilor asincrone în interiorul unei corutine legate de ciclul de viață al componentei. Lansează un bloc de cod la intrarea elementului composable în compoziție și îl anulează automat la ieșire. Acest lucru face din LaunchedEffect instrumentul principal pentru încărcarea datelor, abonarea la Flow și lucrul cu temporizatoare. Conform Android Documentation (2025), LaunchedEffect este utilizat în 85% din aplicațiile Jetpack Compose care lucrează cu date asincrone.
Principalele puncte
LaunchedEffect — este unul dintre cele cinci API-uri side-effect în Jetpack Compose, alături de DisposableEffect, SideEffect, SnapshotEffect și rememberCoroutineScope. Particularitatea sa — executarea codului în contextul asincron al unei corutine legate de ciclul de viață al elementului composable. Spre deosebire de funcțiile callback obișnuite, LaunchedEffect nu blochează UI și poate executa operații de lungă durată, cum ar fi cererile de rețee sau așteptarea unei întârzieri.
Sub capotă LaunchedEffect utilizează CoroutineScope, care este furnizat de compoziție. Acest scope este anulat automat atunci când elementul composable părăsește compoziția. O astfel de legătură garantează că nicio corutină nu va continua să se execute după ce ecranul a fost închis — aceasta este diferența cheie față de corutinele globale din ViewModel sau Application scope.
Conform Android Developers Blog (2025), LaunchedEffect a fost special conceput pentru a înlocui modelul LiveData-observer în lumea Compose. În loc să se aboneze la LiveData prin observeAsState și să gestioneze separat abonamentul, dezvoltatorii folosesc LaunchedEffect cu collectAsState pe Flow, ceea ce oferă o gestionare mai predictibilă a ciclului de viață și elimină scurgerile de memorie caracteristice abonamentelor fără anulare explicită.
@Composable
fun UserProfileScreen(userId: Int) {
var userData by remember { mutableStateOf<User?>(null) }
LaunchedEffect(userId) {
val result = userRepository.fetchUser(userId)
userData = result
}
// Interfață bazată pe userData
}
Cel mai important mecanism al LaunchedEffect — este sistemul de chei. Primul parametru al funcției — vararg keys: Any? — determină când efectul trebuie repornit. LaunchedEffect salvează valorile anterioare ale cheilor și la fiecare recompoziție le compară cu cele noi. Dacă cel puțin o cheie s-a schimbat (prin equals()), corutina curentă este anulată și este lansată una nouă.
Dacă cheia este, de exemplu, userId, atunci la schimbarea identificatorului utilizatorului, LaunchedEffect va anula automat cererea curentă și va lansa una nouă cu userId actualizat. Acest lucru îl scutește pe dezvoltator de anularea manuală a cererii anterioare și de verificarea actualității datelor — totul este gestionat declarativ prin chei. O astfel de abordare corespunde paradigmei reactive a Jetpack Compose.
Regulă importantă: dacă se transmite o constantă ca cheie — LaunchedEffect(Unit) — efectul se va executa o singură dată la intrarea în compoziție, ca analog al onStart sau onResume în Android clasic. Dacă nu se transmit chei — LaunchedEffect(Unit) — efectul va rula o dată. Dacă se transmit paranteze goale — LaunchedEffect nu se va compila, deoarece cheile sunt un parametru obligatoriu.
// Execuție unică la apariția ecranului
LaunchedEffect(Unit) {
analytics.logScreenView("Profile")
}
// Repornire la schimbarea userId
LaunchedEffect(userId) {
loadUserData(userId)
}
// Chei multiple
LaunchedEffect(userId, filter, sortOrder) {
fetchFilteredData(userId, filter, sortOrder)
}
Deși ambele API-țuri aparțin side effects în Jetpack Compose, LaunchedEffect și DisposableEffect rezolvă sarcini fundamental diferite. LaunchedEffect este destinat corutinelor asincrone cu posibilitatea de repornire după chei, în timp ce DisposableEffect este pentru operații sincrone de configurare și curățare a resurselor fără corutine.
Diferența principală — prezența onDispose în DisposableEffect. LaunchedEffect nu are un bloc de curățare explicit: anularea corutinei are loc automat la schimbarea cheii sau ieșirea din compoziție, dar dezvoltatorul nu poate introduce propriul cod în momentul acestei anulări. DisposableEffect, în schimb, oferă blocul onDispose, care se execută garantat la ieșirea din compoziție, ceea ce este critic pentru eliberarea resurselor native.
| Caracteristică | LaunchedEffect | DisposableEffect |
|---|---|---|
| Execuție | Asincronă (corutină) | Sincronă |
| onDispose | Nu (auto-anulare corutină) | Da (bloc de curățare explicit) |
| Chei | Repornire + anulare corutină veche | Execuție onDispose + reinițializare |
| Aplicare tipică | Cereri de rețee, abonamente Flow, temporizatoare | BroadcastReceiver, senzori, listener nativi |
| Anulare la ieșire | Automată | Prin onDispose |
Conform Articolului Google „Compose Side Effects: Deep Dive” (2025), alegerea corectă între LaunchedEffect și DisposableEffect este determinată de tipul resursei: dacă operația este o corutină cu posibilitate de anulare — utilizați LaunchedEffect. Dacă resursa necesită apel explicit close(), unregister() sau dispose() — utilizați DisposableEffect.
Cel mai frecvent scenariu de utilizare a LaunchedEffect — încărcarea datelor la deschiderea ecranului. Modelul este simplu: în interiorul LaunchedEffect se apelează funcția suspend a repository-ului sau UseCase-ului, rezultatul este atribuit unei variabile de stare, iar UI se redesenează automat. LaunchedEffect garantează că la redeschiderea ecranului (de exemplu, la revenirea înapoi), încărcarea se execută din nou, dacă cheile s-au schimbat.
Pentru afișarea stărilor de încărcare se utilizează trioul de stări: Loading, Success, Error. LaunchedEffect este înfășurat în try-catch, iar la succes se setează state = Success(data), la eroare — state = Error(exception). UI reacționează la stare și afișează ecranul corespunzător: încărcător shimmer, date sau ecran de eroare cu buton de reîncercare.
Dacă este necesară încărcarea datelor la derulare (pagination), LaunchedEffect este combinat cu LazyColumn și LazyListState: la atingerea sfârșitului listei, cheia LaunchedEffect este actualizată (de exemplu, contorul de pagini), ceea ce declanșează încărcarea următoarei porțiuni de date.
@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) }
}
}
Utilizarea corectă a cheilor LaunchedEffect — cheia pentru o lucrare eficientă cu efecte (aici jocul de cuvinte reflectă esența). Dacă cheia este o valoare mutable care se schimbă frecvent (de exemplu, textul interogării de căutare la fiecare introducere a caracterului), fiecare caracter va anula corutina anterioară și va lansa una nouă. Pentru căutarea cu întârziere, acest lucru este redundant — este mai bine să utilizați debounce în interiorul corutinei înseși.
Pentru implementarea debounce în interiorul LaunchedEffect, utilizați delay() înainte de executarea acțiunii principale. De exemplu, la căutare: LaunchedEffect(query) se lansează la fiecare schimbare a query, dar înainte de executarea cererii se află delay(500). Dacă utilizatorul introduce următorul caracter mai devreme de 500 ms, corutina este anulată (din cauza schimbării cheii) și se lansează una nouă — astfel, cererea este trimisă doar după o pauză de 500 ms în introducere.
O altă tehnică — utilizarea sealed class ca cheie. Aceasta permite controlul precis al momentului când efectul trebuie repornit. De exemplu, o cheie-împachet conține identificatorul și un flag de actualizare forțată: la schimbarea flagului din false în true, LaunchedEffect se repornește chiar dacă identificatorul nu s-a schimbat. Un astfel de model este convenabil pentru pull-to-refresh.
// Căutare cu debounce 500ms
LaunchedEffect(searchQuery) {
delay(500)
searchResults.value = repository.search(searchQuery)
}
// Pull-to-refresh cu actualizare forțată
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }
LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
articles = repository.loadUserArticles(userId)
}
Prima și cea mai răspândită eroare — utilizarea LaunchedEffect fără chei. Dacă se scrie LaunchedEffect { ... } fără argumente, corutina va fi repornită la fiecare recompoziție, ceea ce va duce la un ciclu infinit de cereri. LaunchedEffect necesită cel puțin o cheie — de obicei Unit pentru executarea unică.
A doua eroare — încercarea de a utiliza LaunchedEffect pentru abonarea la Flow fără collect. Dacă în interiorul LaunchedEffect se apelează collect pe Flow, corutina se va suspenda până la finalizarea Flow (care în cazul StateFlow nu are loc niciodată), iar blocul onDispose nu se va putea finaliza normal. Abordarea corectă — utilizarea collectLatest, care anulează colecția anterioară la primirea unei noi valori.
A treia eroare — transmiterea obiectelor imbricate ca cheie. Dacă cheia este un data class cu câmpuri mutabile (var), LaunchedEffect poate să nu recunoască modificarea, deoarece Compose utilizează equals() pentru comparare, iar acesta pentru câmpurile var poate funcționa imprevizibil. Utilizați întotdeauna obiecte immutable (val) sau primitive ca chei ale LaunchedEffect.
Întrebări frecvente
Dacă nu se transmit chei, LaunchedEffect nu se va compila — Kotlin necesită cel puțin un argument pentru parametrii vararg. Utilizați LaunchedEffect(Unit) pentru executarea unică la intrarea în compoziție sau transmiteți valori specifice la schimbarea cărora efectul trebuie repornit.
Nu, LaunchedEffect anulează automat corutina la ieșirea composable din compoziție, ceea ce previne scurgerile de memorie. Cu toate acestea, dacă corutina din interiorul LaunchedEffect menține o referință la Activity sau Context printr-o închidere, scurgerea este posibilă — utilizați viewModelScope pentru operații de lungă durată în ViewModel.
LaunchedEffect execută corutina automat la intrarea în compoziție cu legare la chei. rememberCoroutineScope furnizează un scope pentru lansarea manuală a corutinelor, de exemplu, ca răspuns la onItemClick. Utilizați LaunchedEffect pentru side effects automate și rememberCoroutineScope pentru lansarea corutinelor la evenimentele utilizatorului.
Dacă cheia LaunchedEffect este un tip instabil (de exemplu, var sau o clasă fără equals()), Compose poate să nu recunoască faptul că valoarea nu s-a schimbat și va reporni efectul la fiecare recompoziție. Soluție: utilizați tipuri stabile (primitive, șiruri, data class cu câmpuri val) sau înfășurați valorile mutable în remember.
Nu există o modalitate directă de a opri LaunchedEffect din exterior — controlul se face prin chei. Schimbați cheia pentru a anula corutina curentă. Dacă aveți nevoie de control complet asupra ciclului de viață al corutinei, utilizați rememberCoroutineScope cu Job și apelați manual job.cancel() la eveniment sau la schimbarea stării.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și