SideEffect — este o funcție composable în Jetpack Compose care execută blocul de cod transmis la fiecare recompoziție reușită. Spre deosebire de LaunchedEffect și DisposableEffect, SideEffect nu este legat de chei și nu are un bloc de curățare — pur și simplu sincronizează starea Compose cu sisteme externe după fiecare randare. Acest lucru îl face ideal pentru actualizarea funcțiilor callback, sincronizarea cu ViewPager și transmiterea datelor către Analytics SDK. Conform Android Developers Documentation (2025), SideEffect se execută strict după ce Compose a confirmat o recompoziție reușită și nu se execută dacă recompoziția a fost omisă.
Principalele puncte
SideEffect — este cel mai simplu API din familia side-effect în Jetpack Compose. Acesta execută un bloc de cod la fiecare recompoziție reușită a unui component composable. Cuvântul „reușită” este cheie aici: dacă Compose decide că recompoziția nu este necesară (de exemplu, toți parametrii de intrare nu s-au schimbat și rezultatul va fi același), SideEffect nu se execută. Aceasta garantează că blocul de sincronizare este apelat doar atunci când UI s-a schimbat efectiv.
Scenariul principal de utilizare a SideEffect — sincronizarea stării Compose cu obiecte care nu fac parte din arborele Compose. Exemple tipice: actualizarea unei funcții callback în sistemul Legacy View, transmiterea stării curente către ViewPager, trimiterea unui eveniment către Analytics SDK la modificarea datelor afișate, sincronizarea cu SDK-uri de hărți care așteaptă actualizări într-un format extern.
Conform Android Developer Blog (2025), SideEffect este adesea folosit împreună cu remember: remember păstrează un obiect (de exemplu, un callback), iar SideEffect îl actualizează la fiecare modificare a dependenței. Acest model este deosebit de important pentru bibliotecile care acceptă obiecte listener și nu le recrează la actualizare — fără SideEffect, listener-ul ar conține o referință învechită la starea curentă.
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setZoom(zoomLevel)
mapView.updateMarkers(markers)
}
AndroidView(factory = { mapView })
}
Pentru a înțelege SideEffect, trebuie să cunoști fazele de execuție ale Jetpack Compose. Compuse trece prin trei faze pentru fiecare cadru: Composition (ce să afișeze), Layout (unde să afișeze), Drawing (cum să afișeze). SideEffect se execută la sfârșitul fazei Composition — după ce toate funcțiile composable au rulat, dar înainte de faza Layout. Aceasta garantează că SideEffect vede starea finală a tuturor variabilelor după recompoziție.
Această poziționare în ciclul de viață oferă un avantaj important: SideEffect nu poate provoca o recompoziție infinită, chiar dacă starea se modifică în interiorul său. Deoarece se execută după compoziție, modificările făcute în interiorul SideEffect vor fi luate în considerare doar în următorul cadru — acest lucru previne buclele caracteristice modificărilor din corpul funcției composable (când setState în interiorul compoziției declanșează o nouă compoziție înainte de finalizarea celei curente).
O altă caracteristică — SideEffect nu este optimizat prin chei. Se execută la fiecare recompoziție indiferent de ce stare s-a schimbat. Dacă este necesar un control mai precis (executare doar la modificarea unui parametru specific), folosește LaunchedEffect cu chei sau înfășoară SideEffect într-o verificare a modificării prin remember.
// SideEffect optimizat cu remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }
SideEffect {
if (currentZoom != zoomLevel) {
map.animateToZoom(zoomLevel)
currentZoom = zoomLevel
}
}
// Fără această verificare SideEffect ar apela animateToZoom
// la fiecare recompoziție, chiar dacă zoomLevel nu s-a schimbat
Cel mai frecvent scenariu practic pentru SideEffect — actualizarea funcțiilor callback care captează starea curentă. În Jetpack Compuse, acest lucru se numește „gestionarea ciclului de viață al callback-urilor”. Problema este că expresiile lambda în Kotlin capturează variabilele prin referință, iar dacă un callback a fost creat cu o valoare a variabilei, iar apoi variabila s-a schimbat — callback-ul continuă să folosească valoarea veche.
Să considerăm un exemplu: Google Maps SDK pentru Android acceptă obiectul OnCameraMoveListener prin setOnCameraMoveListener(). Dacă trimiți o lambda care capturează isTrackingEnabled, atunci când isTrackingEnabled se schimbă, lambda nu se va actualiza — Maps SDK va continua să apeleze vechiul callback cu date învechite. SideEffect rezolvă această problemă: reconfigură listener-ul la fiecare recompoziție, garantând că SDK-ul folosește întotdeauna lambda actualizată cu starea curentă.
Conform Documentației Maps SDK pentru Android (2025), Google recomandă exact acest model la integrarea Maps cu Jetpack Compose. O abordare similară este folosită pentru WebView, VideoView, TextureView și orice alte componente bazate pe View care acceptă callback-uri prin metode set. SideEffect garantează actualitatea callback-urilor la fiecare modificare a stării.
@Composable
fun MapComposable(isTrackingEnabled: Boolean, onMarkerClick: (Marker) -> Unit) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setOnMarkerClickListener { marker ->
onMarkerClick(marker)
true
}
mapView.isTrafficEnabled = isTrackingEnabled
}
AndroidView(factory = { mapView })
}
Un alt scenariu important al SideEffect — trimiterea de evenimente către sistemele analitice la modificarea stării UI. De exemplu, când utilizatorul comută filele în TabLayout în cadrul unui ecran Compose, SideEffect poate transmite starea curentă a filei selectate către Firebase Analytics sau AppsFlyer. De fiecare dată când fila selectată se schimbă (și are loc recompoziția), SideEffect trimite evenimentul corespunzător.
Diferența față de trimiterea evenimentelor direct în onClick sau onTabSelected este că SideEffect reacționează la modificarea stării cauzată prin orice metodă — nu doar acțiunea utilizatorului, ci și modificarea programatică, restaurarea stării după rotirea ecranului sau Deep Link. Acest lucru face din SideEffect un mecanism universal de sincronizare, independent de sursa modificării.
Conform Firebase Best Practices (Google, 2025), trimiterea evenimentelor analitice prin SideEffect oferă o imagine mai completă a parcursului utilizatorului, deoarece înregistrează toate modificările de stare, inclusiv pe cele care au loc fără acțiunea directă a utilizatorului. Cu toate acestea, este important să nu exagerezi: fiecare eveniment în Analytics este o cerere de rețea, așa că pentru stările care se schimbă frecvent (poziția de derulare, coordonatele degetului) SideEffect nu este potrivit — folosește debounce sau trimite evenimente doar la modificări semnificative.
@Composable
fun ProductScreen(selectedTab: ProductTab, productId: String) {
val firebaseAnalytics = remember { FirebaseAnalytics.getInstance(LocalContext.current) }
SideEffect {
val params = Bundle().apply {
putString(FirebaseAnalytics.Param.CONTENT_TYPE, selectedTab.name)
putString(FirebaseAnalytics.Param.ITEM_ID, productId)
}
firebaseAnalytics.logEvent( FirebaseAnalytics.Event.VIEW_ITEM, params)
}
// UI cu TabRow și fila selectată
}
Alegerea între SideEffect și LaunchedEffect depinde de doi factori: este necesară asincronitatea și este necesară gestionarea prin chei. SideEffect este sincron și se execută la fiecare recompoziție. LaunchedEffect este asincron (corutină) și se execută doar la schimbarea cheii, nu la fiecare recompoziție.
Dacă trebuie să execuți o acțiune la fiecare modificare a UI — folosește SideEffect. Dacă trebuie să execuți o acțiune o dată la apariția ecranului sau la modificarea unui parametru specific — folosește LaunchedEffect cu chei. Dacă este necesară o operație asincronă (încărcare date, întârziere, lucru cu Flow) — doar LaunchedEffect, deoarece SideEffect nu suportă funcții suspend.
| Caracteristică | SideEffect | LaunchedEffect |
|---|---|---|
| Executare | La fiecare recompoziție | La schimbarea cheilor |
| Asincronitate | Sincron | Corutină |
| Chei | Nu | Da (vararg) |
| Curățare | Nu | Anulare automată a corutinei |
| Utilizare tipică | Callback-uri, Analytics, sincronizare View | Încărcare, abonare Flow, temporizatoare |
În practică, 70% din cazurile de utilizare a side effects sunt acoperite de LaunchedEffect (operații asincrone, încărcare date), 20% de DisposableEffect (resurse cu curățare) și doar 10% de SideEffect (sincronizare funcții callback). SideEffect este un instrument specializat pentru un domeniu restrâns de sarcini, dar în aceste sarcini este de neînlocuit.
Principala greșeală — modificarea stării Compose în interiorul SideEffect. Deși SideEffect nu provoacă o buclă infinită direct (deoarece se execută după faza de compoziție), poate cauza recompoziții excesive. Dacă în interiorul SideEffect se modifică starea (mutableStateOf), aceasta declanșează o nouă recompoziție în următorul cadru, care va executa din nou SideEffect — și așa mai departe până la stabilizare. Nu este o buclă infinită, dar este muncă suplimentară pentru framework.
A doua greșeală — executarea de calcule grele în interiorul SideEffect. Deoarece SideEffect este apelat la fiecare recompoziție, iar recompozițiile pot ave loc de zeci de ori pe secundă (la animații, derulare), orice cod greu în interiorul SideEffect va duce la pierderea cadrelor. Mută operațiile grele în afara compoziției — într-o corutină (LaunchedEffect) sau calculează prin derivedStateOf / remember.
A treia greșeală — încercarea de a folosi SideEffect pentru cod asincron. SideEffect nu este o funcție suspend, așadar delay(), await(), collect() și alte operații cu corutine în interiorul său nu se vor compila. Dacă trebuie să execuți o acțiune asincronă după recompoziție, folosește snapshotFlow { ... } în combinație cu LaunchedEffect sau pornește o corutină prin rememberCoroutineScope.
Întrebări frecvente
Da, SideEffect se execută la fiecare compoziție reușită, inclusiv la prima — când componentul apare pe ecran. Acest lucru îl deosebește de LaunchedEffect(Unit), care se execută și el o dată la prima compoziție, dar nu se execută la recompozițiile ulterioare (dacă cheia nu s-a schimbat).
Nu, SideEffect se execută după faza de compoziție — modificările făcute în interiorul său vor fi aplicate doar în următorul cadru, ceea ce previne buclele. Cu toate acestea, modificarea frecventă a stării în interiorul SideEffect poate provoca o avalanșă de recompoziții, reducând performanța. Modifică starea în interiorul SideEffect doar când este cu adevărat necesar.
SideEffect se execută sincron la fiecare recompoziție. snapshotFlow creează un Flow din starea Compose și poate fi folosit cu collectLatest în LaunchedEffect pentru procesarea reactivă a modificărilor. snapshotFlow este potrivit pentru cazurile când trebuie să reacționezi la modificări cu debounce, filter sau distinctUntilChanged — ceea ce este imposibil în SideEffect sincron.
Folosește Android Studio Compose Modifier Debugger sau adaugă logare cu numele componentului și frecvența apelurilor. Dacă SideEffect se execută mai des decât era de așteptat, verifică dacă starea componentului părinte nu se schimbă inutil. Optimizare: extrage părțile stabile ale UI în funcții composable separate cu adnotări unstable pentru a reduce numărul de recompoziții.
Da, pot fi folosite în același component pentru scopuri diferite. DisposableEffect se ocupă de configurarea și curățarea resursei (o singură dată), iar SideEffect — de sincronizarea stării curente cu această resursă la fiecare recompoziție. Un exemplu tipic: DisposableEffect înregistrează un callback prin API, iar SideEffect actualizează datele capturate în acest callback la fiecare modificare.
Concluzii
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