Recomposition — co to jest, przebudowa UI przy zmianie stanu

Autor: IT Sectr Opublikowano: 2026-06-27 Czas czytania: 7 min

Recomposition — to mechanizm Jetpack Compose, który automatycznie przebudowuje części interfejsu użytkownika przy zmianie danych, bez ręcznej aktualizacji elementów View. Gdy zmienna stanu, od której zależy funkcja Composable, zmienia wartość, Compose uruchamia ponownie tylko tę funkcję, pozostawiając resztę drzewa UI nietkniętą. Według danych Google Android Developers, 2026, prawidłowe zrozumienie Recomposition pozwala zmniejszyć liczbę zbędnych przerysowań o 40–60%.

Najważniejsze

  • Recomposition — ponowne uruchamianie funkcji Composable przy zmianie ich danych wejściowych lub State
  • Skipping — pomijanie funkcji, których parametry się nie zmieniły (porównanie przez equals)
  • Stability określa, czy Compose może pominąć funkcję — stabilne typy są porównywane poprawnie
  • Smart Recomposition uruchamia ponownie tylko minimalny zestaw funkcji, a nie całe drzewo
  • Rekompozycja nie gwarantuje przerysowania — Layout i Drawing mogą pominąć fazę

Czym jest Recomposition w Jetpack Compose

Recomposition — to ponowne wykonanie funkcji Composable, które już uczestniczyły w Composition, z nowymi wartościami parametrów lub stanu. Głównym celem rekompozycji jest synchronizacja drzewa UI z aktualnymi danymi bez przebudowywania całego interfejsu od zera. W przeciwieństwie do Composition, które zachodzi jednorazowo, Recomposition może uruchamiać się setki razy w ciągu życia ekranu.

Recomposition działa na zasadzie smart invalidation: Compose śledzi, które obiekty State czyta każda funkcja Composable, i oznacza do ponownego uruchomienia tylko te, których zależności się zmieniły. Osiąga się to dzięki systemowi snapshot, który rejestruje wszystkie operacje odczytu State podczas wykonywania, oraz Composer, który mapuje te zależności na konkretne funkcje.

Ważne jest zrozumienie: rekompozycja nie oznacza natychmiastowego przerysowania ekranu. Compose działa w trzech fazach: Composition (budowanie opisu UI), Layout (obliczanie rozmiarów i pozycji) oraz Drawing (rysowanie na płótnie). Jeśli po rekompozycji rozmiary i położenie elementów się nie zmieniły, fazę Layout można pominąć. Jeśli wygląd się nie zmienił — pomijane jest Drawing. Ta trzyfazowa architektura zapewnia minimalny koszt każdej aktualizacji UI.

Wyzwalacze rekompozycji: co powoduje ponowne uruchomienie funkcji

Istnieją trzy główne wyzwalacze rekompozycji. Pierwszy — zmiana obiektu State odczytanego w ciele funkcji Composable. Gdy mutableStateOf lub derivedStateOf zmienia swoją wartość, wszystkie funkcje, które zarejestrowały odczyt tego State w poprzedniej kompozycji, są oznaczane do ponownego uruchomienia.

Drugi wyzwalacz — zmiana parametrów funkcji Composable przy jej wywołaniu z funkcji nadrzędnej. Jeśli funkcja nadrzędna przekazała nową wartość (np. zmienił się tekst lub liczba), funkcja podrzędna zostanie uruchomiona ponownie, nawet jeśli nie czyta State wewnątrz siebie. Compose porównuje nowe i stare wartości parametrów przez equals, a jeśli są równe — funkcja może zostać pominięta.

Trzeci wyzwalacz — zmiana CompositionLocal przez CompositionLocalProvider. Wszystkie funkcje czytające CompositionLocal przez .current są uruchamiane ponownie przy zmianie providera. Ten mechanizm jest używany przez MaterialTheme: zmiana motywu (jasny/ciemny) powoduje rekompozycję wszystkich komponentów czytających MaterialTheme.colorScheme.

kotlin
@Composable
fun RecompositionDemo() {
    var counter by remember { mutableStateOf(0) }
    var text by remember { mutableStateOf("Hello") }

    Column {
        Text("Licznik: $counter")  // rekompozycja przy zmianie licznika
        Text("Wiadomość: $text")    // rekompozycja przy zmianie tekstu

        Button(onClick = { counter++ }) {
            Text("+1")
        }
        Button(onClick = { text = "Świat" }) {
            Text("Zmień tekst")
        }
    }
}

Naciśnięcie przycisku +1 zmienia counter, co powoduje rekompozycję tylko pierwszego wiersza Text i samej Column. Drugi wiersz Text, wyświetlający text, nie jest uruchamiany ponownie. Taka izolacja — wynik systemu snapshot: każda funkcja Composable wie tylko o tych obiektach State, które odczytała.

Optymalizacja rekompozycji: praktyczne techniki

Optymalizacja rekompozycji zaczyna się od prawidłowego wyboru struktur danych. Używaj niezmiennych kolekcji (listOf, mapOf) zamiast zmiennych (mutableListOf). Compose porównuje parametry przez equals, a jeśli kolekcja się zmieniła, ale equals zwrócił true — funkcja nie zostanie uruchomiona ponownie. Dla zmiennych kolekcji używaj SnapshotStateList, który implementuje poprawne śledzenie zmian na poziomie elementów.

Druga technika — wydzielanie stabilnych części UI do osobnych funkcji Composable. Jeśli część ekranu nie zależy od często zmieniającego się stanu, wydziel ją do osobnej funkcji z parametrami. Gdy następuje rekompozycja, stabilna funkcja otrzymuje te same parametry, Compose je porównuje i pomija wykonanie. Jest to korzystniejsze niż uruchamianie tej części w ramach dużej funkcji, gdzie część parametrów się zmieniła.

Trzecia technika — klucze w LazyColumn. Zawsze podawaj key dla elementu w LazyColumn, LazyGrid i innych leniwych kontenerach. Klucz pozwala Compose identyfikować elementy przy zmianie listy: dodawaniu, usuwaniu lub przestawianiu. Bez klucza Compose uruchamia ponownie wszystkie elementy listy przy każdej zmianie, co na dużych listach daje zauważalną utratę wydajności.

kotlin
// Zoptymalizowana struktura: stabilne części wydzielone osobno
@Composable
fun OptimizedScreen(items: List<Item>) {
    Column {
        Header()                         // nie zależy od elementów — brak rekompozycji
        Spacer(modifier = Modifier.height(8.dp))
        LazyColumn {
            items(items, key = { it.id }) { item ->
                ItemRow(item = item)   // rekompozycja tylko dla zmienionych elementów
            }
        }
    }
}

@Composable
fun Header() {
    Text("Lista elementów", style = MaterialTheme.typography.headlineMedium)
}

@Composable
fun ItemRow(item: Item) {
    Text(item.title)
}

Skipping i Stability w Compose

Skipping — to mechanizm, w którym Compose pomija wykonanie funkcji Composable, jeśli wszystkie jej parametry się nie zmieniły. Aby skipping działał poprawnie, typy parametrów muszą być stabilne (stable). Kompilator Kotlin oznacza jako stabilne: typy prymitywne (Int, Float, Boolean), String, funkcje lambda, a także klasy, których wszystkie pola są stabilne i val.

Stability — to adnotacja @Stable lub @Immutable, którą można dodać do własnych klas danych. Jeśli klasa zawiera pole zmienne (var), kompilator uznaje ją za niestabilną i Compose nie będzie mógł pominąć funkcji z takimi parametrami. Dla klas z var używaj @Stable, jeśli gwarantujesz, że powiadomienie o zmianie zostanie wysłane przez system snapshot.

Sprawdzić stability można przez flagę kompilatora -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports". Generuje ona raport z listą wszystkich funkcji Composable i ich parametrów z oznaczeniem stability. Jeśli parametr jest niestabilny — skipping dla tej funkcji jest niemożliwy i będzie ona uruchamiana ponownie przy każdej rekompozycji rodzica.

TypStabilnośćSkipping
Int, Float, BooleanStabilnyTak
StringStabilnyTak
LambdaStabilnyTak
data class z polami valStabilnyTak
data class z polami varNiestabilnyNie
List<String>NiestabilnyNie

Zwróć uwagę: List<String> jest uznawana za niestabilną, ponieważ to interfejs, a nie konkretna implementacja. Używaj immutableListOf() z biblioteki Kotlin Collections Immutable lub opakowuj listę w klasę @Stable. Lambda jest zawsze stabilna, ponieważ jej equals porównuje tylko referencje, a przy tworzeniu nowej lambdy w miejscu wywołania funkcja nadrzędna również jest uruchamiana ponownie.

Monitorowanie rekompozycji w Android Studio

Do monitorowania rekompozycji Android Studio udostępnia Layout Inspector z trybem Compose Recomposition Counts. W tym trybie każda funkcja Composable wyświetla liczbę rekompozycji i przyczyny ponownego uruchomienia. Pozwala to szybko znaleźć funkcje, które są uruchamiane zbyt często, i określić główną przyczynę — niestabilne parametry lub zbędne zależności State.

Dodatkowe narzędzia: Compose Metrics (zbieranie statystyk przez testy instrumentacyjne) i Recomposition Timer (pomiar czasu wykonania każdej funkcji). Google zaleca włączanie tych narzędzi na etapie profilowania i wyłączanie w wersjach release, ponieważ dodają one narzut do 20% na każdą rekompozycję.

Podczas analizy rekompozycji szukaj wzorców unnecessary recomposition: funkcja jest uruchamiana ponownie, mimo że jej wynikowy UI nie powinien się zmienić. Częstą przyczyną jest używanie lambd bez remember, gdy każdorazowo tworzony jest nowy obiekt lambdy, a Compose uznaje parametr za zmieniony. Rozwiązanie: opakowywanie lambd w remember { } ze stałymi przechwyceniami.

kotlin
// Źle: nowa lambda przy każdej rekompozycji rodzica
@Composable
fun Parent() {
    Child(onClick = { doSomething() })  // nowa lambda za każdym razem
}

// Dobrze: remember stabilizuje lambdę
@Composable
fun Parent() {
    val onClick = remember { { doSomething() } }
    Child(onClick = onClick)  // ta sama referencja
}

Często zadawane pytania

Czy rekompozycja oznacza przerysowanie ekranu?

Nie, rekompozycja to tylko faza Composition. Po niej wykonywane są Layout i Drawing. Jeśli po rekompozycji rozmiary i położenie elementów się nie zmieniły, Layout i Drawing mogą być całkowicie pominięte, co oszczędza zasoby GPU.

Jak często może zachodzić rekompozycja?

Przy animacjach rekompozycja może uruchamiać się do 120 razy na sekundę (120fps). Dla zwykłej interakcji — 10–60 razy na sekundę. Ważne, aby każda rekompozycja mieściła się w budżecie klatki (8–16 ms), w przeciwnym razie aplikacja będzie działać z opóźnieniami.

Dlaczego funkcja jest rekomponowana, mimo że State się nie zmienił?

Przyczyną jest zmiana parametru od funkcji nadrzędnej. Rodzic uruchamia się ponownie (z własnego powodu) i przekazuje nową wartość. Aby tego uniknąć, sprawdź stability parametrów i używaj remember do stabilizacji lambd i obliczanych wartości.

Czy można wyłączyć rekompozycję dla konkretnej funkcji?

Bezpośredniego wyłączenia nie ma, ale istnieje wymuszone pomijanie przez readInComposition — State jest odczytywany poza ciałem funkcji, co nie rejestruje zależności. Używaj tego ostrożnie: funkcja nie będzie reagować na zmiany, co może prowadzić do nieaktualnego UI.

Co jest droższe: Composition czy Recomposition?

Composition jest droższy, ponieważ tworzy wszystkie sloty i węzły drzewa od zera. Recomposition wykorzystuje istniejące sloty i tylko aktualizuje ich wartości. W praktyce Composition ekranu zajmuje 2–10 ms, a rekompozycja jednego elementu — 0.1–1 ms.

Podsumowanie

  • Recomposition — selektywne ponowne uruchamianie funkcji Composable przy zmianie ich State lub parametrów
  • Snapshot system śledzi zależności funkcji od State i planuje rekompozycję
  • Skipping jest możliwy tylko dla funkcji ze stabilnymi parametrami (@Stable lub immutable)
  • Trzy wyzwalacze rekompozycji: zmiana State, zmiana parametrów, zmiana CompositionLocal
  • List<T> jest uznawany za niestabilny — używaj niezmiennych kolekcji do poprawnego skipping
  • Layout Inspector pokazuje liczniki rekompozycji dla każdej funkcji
  • Zalecenie: wydzielaj stabilne części UI do osobnych funkcji i używaj remember dla lambd

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również