Recomposition — ce este, reconstruirea UI la modificarea stării

Autor: IT Sectr Publicat: 2026-06-27 Timp de citire: 7 min

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 — repornirea funcțiilor Composable la modificarea datelor de intrare sau a stării
  • Skipping — omiterea funcțiilor ai căror parametri nu s-au modificat (comparare prin equals)
  • Stability determină dacă Compose poate omite o funcție — tipurile stabile sunt comparate corect
  • Smart Recomposition repornește doar setul minim de funcții, nu întregul arbore
  • Recompoziția nu garantează redesenarea — Layout și Drawing pot omite faza

Ce este Recomposition în Jetpack Compose

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.

Declanșatorii recompoziției: ce cauzează repornirea funcțiilor

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.

kotlin
@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: tehnici practice

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ță.

kotlin
// 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 și Stability în Compose

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.

TipStabilitateSkipping
Int, Float, BooleanStabilDa
StringStabilDa
LambdaStabilDa
data class cu câmpuri valStabilDa
data class cu câmpuri varInstabilNu
List<String>InstabilNu

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ă.

Monitorizarea recompozițiilor în Android Studio

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.

kotlin
// 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

Recompoziția înseamnă redesenarea ecranului?

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.

Cât de des poate avea loc recompoziția?

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.

De ce o funcție este recompoziționată deși State nu s-a modificat?

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.

Se poate dezactiva recompoziția pentru o funcție specifică?

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.

Ce este mai costisitor: Composition sau Recomposition?

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

  • Recomposition — repornirea selectivă a funcțiilor Composable la modificarea State sau a parametrilor
  • Snapshot system urmărește dependențele funcțiilor de State și planifică recompoziția
  • Skipping este posibil doar pentru funcțiile cu parametri stabili (@Stable sau immutable)
  • Trei declanșatori ai recompoziției: modificarea State, modificarea parametrilor, modificarea CompositionLocal
  • List<T> este considerată instabilă — utilizați colecții imuabile pentru skipping corect
  • Layout Inspector arată contoarele de recompoziție pentru fiecare funcție
  • Recomandare: extrageți părțile stabile ale UI în funcții separate și utilizați remember pentru lambda

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.

Discutați proiectul

Citiți și