Recomposition este un mecanism Jetpack Compose care reconstruiește automat părți ale interfeței de utilizator la modificarea datelor, fără actualizarea manuală a elementelor View. Când o variabilă de stare de care depinde o funcție Composable își schimbă valoarea, Compose repornește doar acea funcție, lăsând restul arborelui UI neatins. Conform Google Android Developers, 2026, înțelegerea corectă a Recomposition permite reducerea numărului de re-rendări inutile cu 40–60%.
Principalele puncte
Recomposition este re-executarea funcțiilor Composable care au participat deja în Composition, cu noi valori ale parametrilor sau stării. Scopul principal al recompoziției este sincronizarea arborelui UI cu datele actuale fără reconstruirea întregului interfață de la zero. Spre deosebire de Composition, care are loc o singură dată, Recomposition se poate declanșa de sute de ori pe durata de viață a unui ecran.
Recomposition funcționează pe principiul smart invalidation: Compose urmărește ce obiecte State citește fiecare funcție Composable și marchează pentru repornire doar pe cele ale căror dependențe s-au modificat. Acest lucru se realizează prin sistemul de snapshot, care înregistrează toate operațiile de citire State în timpul execuției, și Composer, care asociază aceste dependențe cu funcții specifice.
Este important de înțeles: recompoziția nu înseamnă redesenarea imediată a ecranului. Compose funcționează în trei faze: Composition (construirea descrierii UI), Layout (calcularea dimensiunilor și pozițiilor) și Drawing (desenarea pe pânză). Dacă după recompoziție dimensiunile și poziția elementelor nu s-au modificat, faza Layout poate fi omisă. Dacă aspectul nu s-a modificat — Drawing este omis. Această arhitectură în trei faze asigură costul minim al fiecărei actualizări UI.
Există trei declanșatori principali ai recompoziției. Primul — modificarea obiectului State citit în corpul funcției Composable. Când mutableStateOf sau derivedStateOf își schimbă valoarea, toate funcțiile care au înregistrat citirea acestui State în compoziția anterioară sunt marcate pentru repornire.
Al doilea declanșator — modificarea parametrilor funcției Composable la apelul din funcția părinte. Dacă funcția părinte a transmis o nouă valoare (de exemplu, s-a modificat textul sau numărul), funcția copil va fi repornită, chiar dacă nu citește State în interiorul ei. Compose compară valorile noi și vechi ale parametrilor prin equals, iar dacă sunt egale — funcția poate fi omisă.
Al treilea declanșator — modificarea CompositionLocal prin CompositionLocalProvider. Toate funcțiile care citesc CompositionLocal prin .current sunt repornite la schimbarea provider-ului. Acest mecanism este folosit de MaterialTheme: schimbarea temei (deschis/închis) cauzează recompoziția tuturor componentelor care citesc MaterialTheme.colorScheme.
@Composable
fun RecompositionDemo() {
var counter by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("Hello") }
Column {
Text("Contor: $counter") // recompoziție la modificarea contorului
Text("Mesaj: $text") // recompoziție la modificarea textului
Button(onClick = { counter++ }) {
Text("+1")
}
Button(onClick = { text = "Lume" }) {
Text("Schimbă textul")
}
}
}
Apăsarea butonului +1 modifică counter, ceea ce cauzează recompoziția doar a primului rând Text și a Column însăși. Al doilea rând Text, care afișează text, nu este repornit. O astfel de izolare — rezultatul sistemului de snapshot: fiecare funcție Composable știe doar despre acele obiecte State pe care le-a citit.
Optimizarea recompoziției începe cu alegerea corectă a structurilor de date. Utilizați colecții imuabile (listOf, mapOf) în locul celor mutabile (mutableListOf). Compose compară parametrii prin equals, iar dacă colecția s-a modificat dar equals a returnat true — funcția nu va fi repornită. Pentru colecțiile mutabile, utilizați SnapshotStateList, care implementează urmărirea corectă a modificărilor la nivel de elemente.
A doua tehnică — separarea părților stabile UI în funcții Composable separate. Dacă o parte a ecranului nu depinde de o stare care se modifică frecvent, extrageți-o într-o funcție separată cu parametri. Când are loc recompoziția, funcția stabilă primește aceiași parametri, Compose îi compară și omite execuția. Acest lucru este mai avantajos decât repornirea acestei părți în cadrul unei funcții mari, unde o parte a parametrilor s-a modificat.
A treia tehnică — chei în LazyColumn. Indicați întotdeauna key pentru item în LazyColumn, LazyGrid și alte containere leneșe. Cheia permite Compose să identifice elementele la modificarea listei: adăugare, ștergere sau reordonare. Fără cheie, Compose repornește toate elementele listei la orice modificare, ceea ce pe liste mari duce la o pierdere notabilă de performanță.
// Structură optimizată: părți stabile extrase separat
@Composable
fun OptimizedScreen(items: List<Item>) {
Column {
Header() // nu depinde de elemente — fără recompoziție
Spacer(modifier = Modifier.height(8.dp))
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item = item) // recompoziție doar pentru elemente modificate
}
}
}
}
@Composable
fun Header() {
Text("Listă de elemente", style = MaterialTheme.typography.headlineMedium)
}
@Composable
fun ItemRow(item: Item) {
Text(item.title)
}
Skipping este un mecanism prin care Compose omite execuția unei funcții Composable dacă toți parametrii săi nu s-au modificat. Pentru ca skipping să funcționeze corect, tipurile parametrilor trebuie să fie stabile (stable). Compilatorul Kotlin marchează ca stabile: tipurile primitive (Int, Float, Boolean), String, funcțiile lambda, precum și clasele ale căror câmpuri sunt toate stabile și val.
Stability este o adnotare @Stable sau @Immutable care poate fi adăugată claselor de date personalizate. Dacă o clasă conține un câmp mutabil (var), compilatorul o consideră instabilă, iar Compose nu poate omite funcțiile cu astfel de parametri. Pentru clasele cu var, utilizați @Stable dacă garantați că notificarea despre modificare va fi trimisă prin sistemul de snapshot.
Stability poate fi verificată prin flag-ul compilatorului -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports". Acesta generează un raport cu lista tuturor funcțiilor Composable și a parametrilor lor cu indicarea stability. Dacă un parametru este instabil — skipping pentru acea funcție este imposibil și va fi repornită la fiecare recompoziție a părintelui.
| Tip | Stabilitate | Skipping |
|---|---|---|
| Int, Float, Boolean | Stabil | Da |
| String | Stabil | Da |
| Lambda | Stabil | Da |
| data class cu câmpuri val | Stabil | Da |
| data class cu câmpuri var | Instabil | Nu |
| List<String> | Instabil | Nu |
Rețineți: List<String> este considerată instabilă deoarece este o interfață, nu o implementare concretă. Utilizați immutableListOf() din biblioteca Kotlin Collections Immutable sau înfășurați lista într-o clasă @Stable. Lambda este întotdeauna stabilă, deoarece equals-ul său compară doar referințe, iar la crearea unei noi lambda în locul apelului, funcția părinte este de asemenea repornită.
Pentru monitorizarea recompozițiilor, Android Studio oferă Layout Inspector cu modul Compose Recomposition Counts. În acest mod, fiecare funcție Composable afișează numărul de recompoziții și motivele repornirii. Acest lucru permite găsirea rapidă a funcțiilor care sunt recompoziționate prea des și determinarea cauzei principale — parametri instabili sau dependențe State inutile.
Instrumente suplimentare: Compose Metrics (colectarea statisticilor prin teste de instrumentation) și Recomposition Timer (măsurarea timpului de execuție a fiecărei funcții). Google recomandă activarea acestor instrumente în faza de profiling și dezactivarea în versiunile release, deoarece adaugă un overhead de până la 20% pentru fiecare recompoziție.
La analiza recompozițiilor, căutați modele de unnecessary recomposition: o funcție este repornită deși UI-ul său de ieșire nu ar trebui să se modifice. O cauză frecventă este utilizarea lambda fără remember, când de fiecare dată se creează un nou obiect lambda, iar Compose consideră parametrul modificat. Soluția: înfășurarea lambda în remember { } cu capturi fixe.
// Rău: lambda nouă la fiecare recompoziție a părintelui
@Composable
fun Parent() {
Child(onClick = { doSomething() }) // lambda nouă de fiecare dată
}
// Bine: remember stabilizează lambda
@Composable
fun Parent() {
val onClick = remember { { doSomething() } }
Child(onClick = onClick) // aceeași referință
}
Întrebări frecvente
Nu, recompoziția este doar faza Composition. După ea se execută Layout și Drawing. Dacă după recompoziție dimensiunile și poziția elementelor nu s-au modificat, Layout și Drawing pot fi complet omise, ceea ce economisește resursele GPU.
La animații, recompoziția se poate declanșa de până la 120 de ori pe secundă (120fps). Pentru interacțiunea obișnuită — de 10–60 de ori pe secundă. Este important ca fiecare recompoziție să se încadreze în bugetul cadrului (8–16 ms), altfel aplicația va încetini.
Cauza — modificarea parametrului de la funcția părinte. Părintele se repornește (din motive proprii) și transmite o nouă valoare. Pentru a evita acest lucru, verificați stability parametrilor și utilizați remember pentru stabilizarea lambda și a valorilor calculate.
Nu există dezactivare directă, dar există skipping forțat prin readInComposition — State este citit în afara corpului funcției, ceea ce nu înregistrează dependența. Folosiți acest lucru cu prudență: funcția nu va reacționa la modificări, ceea ce poate duce la un UI învechit.
Composition este mai costisitor deoarece creează toate sloturile și nodurile arborelui de la zero. Recomposition reutilizează sloturile existente și doar actualizează valorile acestora. În practică, Composition unui ecran durează 2–10 ms, iar recompoziția unui element — 0.1–1 ms.
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