Composition — este procesul central în Jetpack Compose, în cadrul căruia din funcțiile Composable descriptive se construiește un arbore UI viu, afișat pe ecran. Spre deosebire de sistemul View din Android, unde aspectul era încărcat din XML și transformat în obiecte imutabile, Composition funcționează ca un sistem dinamic: funcțiile se execută, creează sloturi în memorie, formează o ierarhie de noduri și o leagă de stare. Potrivit Google Android Developers, 2026, înțelegerea Composition este critică pentru optimizarea performanței aplicațiilor Compose.
Puncte cheie
Composition — este procesul de executare a funcțiilor Composable, în urma căruia se formează o reprezentare internă a interfeței de utilizator sub forma unui arbore de noduri. Fiecare nod al acestui arbore corespunde fie unui component încorporat (Text, Button, Image), fie apelului unei funcții Composable personalizate. Composition nu creează direct obiecte View Android — construiește o descriere abstractă care este apoi procesată de fazele Layout și Drawing.
Caracteristica cheie a Composition este reproductibilitatea sa (restartability). Fiecare funcție Composable din componența compoziției poate fi repornită în orice moment, dacă s-au modificat parametrii săi de intrare sau obiectele de stare citite de ea. Sistemul nu repornește întregul arbore — doar acele funcții care depind efectiv de datele modificate.
Din punct de vedere tehnic, Composition este gestionat prin Composer — un motor intern pe care compilatorul Kotlin îl încorporează în fiecare funcție Composable. Composer scrie în sloturi (grupuri de poziții) informații despre care funcții au fost apelate, cu ce parametri și în ce ordine. La apelurile ulterioare, Composer compară datele noi cu cele salvate și decide repornirea.
Procesul de construire a arborelui UI începe cu apelul metodei setContent în interiorul Activity sau Fragment. Această metodă creează Composition inițial și lansează executarea funcției Composable rădăcină. Apoi, fiecare funcție Composable imbricată își adaugă nodurile în arbore, formând o ierarhie: Row conține Text și Button, Column conține Image și Card, și așa mai departe.
Fiecare nod al arborelui primește o cheie de poziție unică, bazată pe poziția sa în codul sursă. Această cheie este utilizată pentru identificarea nodului la execuțiile repetate. Cheia de poziție este motivul pentru care ordinea de apelare a funcțiilor Composable nu trebuie să depindă de condiții: dacă într-o execuție s-a apelat A -> B, iar în următoarea B -> A, Compose nu va putea potrivi nodurile vechi cu cele noi.
@Composable
fun AppScreen() {
Column { // Nod Column (poziția 1)
HeaderSection() // Nod HeaderSection (poziția 2)
ContentSection() // Nod ContentSection (poziția 3)
FooterSection() // Nod FooterSection (poziția 4)
}
}
@Composable
fun HeaderSection() {
Row { // Nod Row (poziția 2.1)
Text("Titlu") // Nod Text (poziția 2.2)
Icon(...) // Nod Icon (poziția 2.3)
}
}
În acest exemplu, fiecare apel primește o poziție bazată pe ordinea în cod. Column (poziția 1) conține trei noduri copil (pozițiile 2, 3, 4). HeaderSection adaugă încă două noduri copil (2.1, 2.2, 2.3). Dacă în următoarea recompoziție ContentSection este apelat înaintea HeaderSection, Composer nu va putea potrivi corect nodurile — de aici regula: ordinea apelurilor funcțiilor Composable trebuie să fie stabilă.
Starea în Composition este gestionată prin obiecte de tip State<T>. Când o funcție Composable citește o valoare din State printr-o proprietate delegată (by), înregistrează o dependență de acel State. La modificarea valorii, toate funcțiile care au citit acest State sunt marcate pentru repornire în următoarea fază a compoziției.
Mecanismul de înregistrare a dependențelor se numește sistemul de snapshot-uri. De fiecare dată când State se modifică, snapshot-ul înregistrează toate modificările și notifică Composer care funcții depind de acest State. Este important de înțeles: citirea State în cod ne-Composable (de exemplu, în lambda onClick) nu înregistrează dependența — doar citirea în interiorul funcției Composable sau în lambda-uri executate în contextul compoziției.
Sistemul de snapshot-uri funcționează tranzacțional: mai multe modificări ale State în cadrul unui singur eveniment sunt combinate într-o singură tranzacție, prevenind recompozițiile multiple. Acest lucru este deosebit de important la procesarea gesturilor: într-o singură mișcare se modifică mai multe obiecte State, dar Compose execută doar o singură recompoziție.
@Composable
fun StateExample() {
var text by remember { mutableStateOf("Hello") }
var isVisible by remember { mutableStateOf(true) }
Column {
Text(text) // înregistrează dependența de text
if (isVisible) { // înregistrează dependența de isVisible
TextField(value = text, onValueChange = { text = it })
}
Button(onClick = { isVisible = !isVisible }) {
Text(if (isVisible) "Ascunde" else "Arată")
}
}
}
Modificarea text duce la recompoziția doar a Column, Text și TextField. Button și condiția isVisible rămân neschimbate. O astfel de izolare a recompoziției este un avantaj cheie al Compose față de sistemele care redesenează întregul ecran. Fiecare funcție Composable urmărește doar acele obiecte State pe care le citește direct.
Composition (compunerea) și Recomposition (recompunerea) — sunt două moduri diferite de executare a funcțiilor Composable. Composition are loc o singură dată la crearea ecranului: sistemul execută toate funcțiile Composable cu valorile inițiale și construiește arborele UI inițial. Recomposition are loc de mai multe ori la modificarea datelor: sistemul repornește doar acele funcții care depind de starea modificată.
Modul Composition activează toate nodurile arborelui, alocă sloturi pentru fiecare funcție, înregistrează toți descendenții. Recomposition funcționează selectiv: Composer compară valorile noi și vechi ale parametrilor fiecărei funcții, iar dacă nu s-au modificat — funcția nu este executată (skipping).
Composition și Recomposition diferă ca și cost. Prima Composition este mai costisitoare, deoarece necesită construirea completă a arborelui și alocarea sloturilor. Recomposition este mai ieftină, mai ales dacă majoritatea funcțiilor sunt stabile — parametrii lor sunt comparați prin equals, iar Compose omite apelul lor. Pentru performanță maximă, trebuie să urmărim ca cât mai puține funcții să fie afectate de recompoziție.
| Caracteristică | Composition | Recomposition |
|---|---|---|
| Când are loc | O singură dată, la prima afișare | De mai multe ori, la modificarea datelor |
| Volum | Întregul arbore | Doar funcțiile modificate |
| Compararea parametrilor | Nu se execută | Se execută pentru skipping |
| Crearea sloturilor | Da, toate sloturile sunt create | Doar pentru noduri noi |
CompositionLocal — un mecanism de transmitere implicită a datelor prin arborele compoziției. Rezolvă problema când un parametru trebuie transmis prin zeci de funcții Composable imbricate care nu îl folosesc direct. În locul unui lanț explicit de parametri, datele sunt setate la nivelul superior și citite în orice funcție imbricată prin CompositionLocal.current.
Tema MaterialTheme — cel mai cunoscut exemplu de CompositionLocal. Toate componentele Compose citesc culorile, tipografia și formele prin MaterialTheme.colorScheme, MaterialTheme.typography, MaterialTheme.shapes, fără a le primi prin parametri. Dezvoltatorul poate crea propriile CompositionLocal pentru date precum utilizatorul curent, setările de localizare sau configurația ecranului.
Limitare importantă: CompositionLocal nu trebuie utilizat pentru date care se schimbă frecvent (poziția de derulare, textul într-un câmp de intrare). Componentul care citește CompositionLocal este repornit la fiecare modificare a valorii, prin urmare pentru date dinamice este mai bine să se utilizeze parametri expliciți sau State. CompositionLocal este optim pentru date de configurare care se schimbă rar sau deloc.
val LocalUser = compositionLocalOf<User?> { null }
@Composable
fun AppRoot(user: User, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalUser.provides(user)) {
content()
}
}
@Composable
fun UserAvatar() {
val user = LocalUser.current // citire fără parametru explicit
AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}
CompositionLocalProvider creează un domeniu de vizibilitate, în interiorul căruia LocalUser.current returnează valoarea setată. UserAvatar citește utilizatorul fără transmiterea explicită a parametrului prin funcții intermediare. Acest lucru este deosebit de valoros în ierarhii adânci, unde datele sunt necesare doar în câteva noduri frunză.
Întrebări frecvente
Modificarea State în timpul Composition programează o nouă recompoziție care se va executa după finalizarea celei curente. Nu apare o buclă infinită: Compose garantează că fiecare recompoziție se execută într-o tranzacție separată a sistemului de snapshot-uri.
Pe dispozitive moderne, Composition unui ecran cu 50–100 de funcții Composable durează 1–5 ms. Google recomandă încadrarea în 16 ms pentru un cadru la 60fps. Dacă Composition depășește această limită, utilizați LazyColumn sau împărțiți ecranul în funcții mai mici.
Pornirea manuală directă a Composition nu este posibilă — este gestionată automat de Composer. Cu toate acestea, se poate forța programarea unei recompoziții prin modificarea State sau apelarea invalidate() pe compozitul rădăcină, dacă există acces la CompositionContext.
Ierarhia View — este un arbore imutabil de obiecte Java care se creează o singură dată. Composition — un arbore virtual care se reconstruiește la fiecare modificare a datelor. View își păstrează starea în variabile de instanță, Composition — în sloturi legate de poziția de apel a funcției.
Dacă o funcție Composable încetează să mai fie apelată (de exemplu, condiția if devine false), Composition șterge nodul său și apelează curățarea DisposableEffect. La reapariție (if devine din nou true) se creează un nod nou — cel vechi nu este restaurat.
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