Recomposition је механизам Jetpack Compose-а који аутоматски престројава делове корисничког интерфејса при промени података, без ручног ажурирања View елемената. Када променљива стања од које зависи Composable функција промени вредност, Compose поново покреће само ту функцију, остављајући остатак UI стабла нетакнутим. Према подацима Google Android Developers, 2026, правилно разумевање Recomposition-а омогућава смањење броја непотребних прецртавања за 40–60%.
Главно
Recomposition је поновно извршавање Composable функција које су већ учествовале у Composition-у, са новим вредностима параметара или стања. Основни циљ рекомпозиције је синхронизација UI стабла са актуелним подацима без престројавања целог интерфејса од нуле. За разлику од Composition-а који се дешава једнократно, Recomposition се може покренути стотине пута током живота екрана.
Recomposition ради по принципу smart invalidation: Compose прати које State објекте чита свака Composable функција и означава за поновно покретање само оне чије су се зависности промениле. То се постиже захваљујући snapshot систему, који бележи све операције читања State-а током извршавања, и Composer-у који ове зависности повезује са конкретним функцијама.
Важно је разумети: рекомпозиција не значи тренутно прецртавање екрана. Compose ради у три фазе: Composition (изградња UI описа), Layout (израчунавање величина и позиција) и Drawing (цртање на платну). Ако се после рекомпозиције величине и положај елемената нису променили, фаза Layout се може прескочити. Ако се изглед није променио — прескаче се Drawing. Ова трофазна архитектура обезбеђује минималну цену сваког ажурирања UI-ја.
Постоје три главна окидача рекомпозиције. Први — промена State објекта прочитаног у телу Composable функције. Када mutableStateOf или derivedStateOf промени своју вредност, све функције које су регистровале читање овог State-а у претходној композицији се означавају за поновно покретање.
Други окидач — промена параметара Composable функције при њеном позиву из родитељске функције. Ако је родитељска функција проследила нову вредност (нпр. променио се текст или број), подређена функција ће бити поново покренута, чак и ако не чита State унутар себе. Compose пореди нове и старе вредности параметара путем equals, и ако су једнаке — функција може бити прескочена.
Трећи окидач — промена CompositionLocal-а путем CompositionLocalProvider-а. Све функције које читају CompositionLocal путем .current се поново покрећу при промени provider-а. Овај механизам користи MaterialTheme: промена теме (светла/тамна) изазива рекомпозицију свих компоненти које читају MaterialTheme.colorScheme.
@Composable
fun RecompositionDemo() {
var counter by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("Hello") }
Column {
Text("Бројач: $counter") // рекомпозиција при промени бројача
Text("Порука: $text") // рекомпозиција при промени текста
Button(onClick = { counter++ }) {
Text("+1")
}
Button(onClick = { text = "Свет" }) {
Text("Промени текст")
}
}
}
Притисак на дугме +1 мења counter, што изазива рекомпозицију само првог Text реда и саме Column. Други Text ред, који приказује text, не бива поново покренут. Таква изолација — резултат snapshot система: свака Composable функција зна само за оне State објекте које је прочитала.
Оптимизација рекомпозиције почиње правилним избором структура података. Користите immutable колекције (listOf, mapOf) уместо изменљивих (mutableListOf). Compose пореди параметре по equals, и ако се колекција променила али је equals вратио true — функција неће бити поново покренута. За изменљиве колекције користите SnapshotStateList, који имплементира коректно праћење промена на нивоу елемената.
Други приступ — издвајање стабилних делова UI-ја у засебне Composable функције. Ако део екрана не зависи од често мењајућег стања, издвојте га у засебну функцију са параметрима. Када дође рекомпозиција, стабилна функција добија исте параметре, Compose их пореди и прескаче извршавање. Ово је исплативије него поновно покретање овог дела у саставу велике функције где се део параметара променио.
Трећи приступ — кључеви у LazyColumn-у. Увек наводите key за item у LazyColumn, LazyGrid и другим лењим контејнерима. Кључ омогућава Compose-у да идентификује елементе при промени листе: додавању, брисању или премештању. Без кључа, Compose поново покреће све елементе листе при свакој промени, што на великим листама даје приметан губитак перформанси.
// Оптимизована структура: стабилни делови издвојени засебно
@Composable
fun OptimizedScreen(items: List<Item>) {
Column {
Header() // не зависи од ставки — нема рекомпозиције
Spacer(modifier = Modifier.height(8.dp))
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item = item) // рекомпозиција само за промењене ставке
}
}
}
}
@Composable
fun Header() {
Text("Листа ставки", style = MaterialTheme.typography.headlineMedium)
}
@Composable
fun ItemRow(item: Item) {
Text(item.title)
}
Skipping је механизам при ком Compose прескаче извршавање Composable функције ако се сви њени параметри нису променили. Да би skipping радио коректно, типови параметара морају бити стабилни (stable). Kotlin компајлер означава као стабилне: примитивне типове (Int, Float, Boolean), String, ламбда функције, као и класе чија су сва поља стабилна и val.
Stability је анотација @Stable или @Immutable која се може додати корисничким класама података. Ако класа садржи изменљиво поље (var), компајлер је сматра нестабилном и Compose не може да прескочи функције са таквим параметрима. За класе са var користите @Stable ако гарантујете да ће обавештење о промени бити послато путем snapshot система.
Stability се може проверити путем компајлерског флага -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports". Он генерише извештај са списком свих Composable функција и њихових параметара са ознаком stability. Ако је параметар нестабилан — skipping за ту функцију је немогућ и она ће бити поново покретана при свакој рекомпозицији родитеља.
| Тип | Стабилност | Skipping |
|---|---|---|
| Int, Float, Boolean | Стабилан | Да |
| String | Стабилан | Да |
| Ламбда | Стабилан | Да |
| data class са val пољима | Стабилан | Да |
| data class са var пољима | Нестабилан | Не |
| List<String> | Нестабилан | Не |
Обратите пажњу: List<String> се сматра нестабилним јер је ово интерфејс, а не конкретна имплементација. Користите immutableListOf() из библиотеке Kotlin Collections Immutable или умотајте листу у @Stable класу. Ламбда је увек стабилна, јер њен equals пореди само референце, а при стварању нове ламбде на месту позива родитељска функција се такође поново покреће.
За праћење рекомпозиција, Android Studio обезбеђује Layout Inspector са режимом Compose Recomposition Counts. У овом режиму, свака Composable функција приказује број рекомпозиција и разлоге поновног покретања. Ово омогућава брзо проналажење функција које се рекомпозирају превише често и утврђивање основног узрока — нестабилни параметри или непотребне State зависности.
Додатни алати: Compose Metrics (прикупљање статистике путем instrumentation тестова) и Recomposition Timer (мерење времена извршења сваке функције). Google препоручује укључивање ових алата у фази профилисања и искључивање у релизним верзијама, јер додају overhead до 20% на сваку рекомпозицију.
При анализи рекомпозиција тражите обрасце unnecessary recomposition: функција се поново покреће иако се њен излазни UI не би требао променити. Чест узрок је коришћење ламбди без remember, када се сваки пут ствара нови објекат ламбде, а Compose сматра да се параметар променио. Решење: умотавање ламбди у remember { } са фиксним захватима.
// Лоше: нова ламбда при свакој рекомпозицији родитеља
@Composable
fun Parent() {
Child(onClick = { doSomething() }) // нова ламбда сваки пут
}
// Добро: remember стабилизује ламбду
@Composable
fun Parent() {
val onClick = remember { { doSomething() } }
Child(onClick = onClick) // иста референца
}
Често постављана питања
Не, рекомпозиција је само фаза Composition. После ње се извршавају Layout и Drawing. Ако се после рекомпозиције величине и положај елемената нису променили, Layout и Drawing могу бити потпуно прескочени, што штеди ресурсе GPU-а.
Код анимација рекомпозиција се може покретати до 120 пута у секунди (120fps). За уобичајену интеракцију — 10–60 пута у секунди. Важно је да се свака рекомпозиција уклапа у буџет оквира (8–16 ms), иначе ће апликација да успорава.
Узрок је промена параметра од родитељске функције. Родитељ се поново покреће (из свог разлога) и прослеђује нову вредност. Да бисте то избегли, проверите stability параметара и користите remember за стабилизацију ламбди и израчунатих вредности.
Директног искључивања нема, али постоји принудно прескакање путем readInComposition — State се чита ван тела функције, што не региструје зависност. Користите ово опрезно: функција неће реаговати на промене, што може довести до застарелог UI-ја.
Composition је скупљи јер ствара све слотове и чворове стабла од нуле. Recomposition поново користи постојеће слотове и само ажурира њихове вредности. У пракси, Composition екрана траје 2–10 ms, а рекомпозиција једног елемента — 0.1–1 ms.
Закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође