Composition: istota, budowanie drzewa UI w Compose

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

Composition — to centralny proces w Jetpack Compose, podczas którego z opisowych funkcji Composable budowane jest żywe drzewo UI wyświetlane na ekranie. W przeciwieństwie do systemu View w Androidzie, gdzie układ ładowano z XML i przekształcano w niezmienne obiekty, Composition działa jako dynamiczny system: funkcje są wykonywane, tworzą sloty w pamięci, formują hierarchię węzłów i wiążą ją ze stanem. Według Google Android Developers, 2026, zrozumienie Composition jest krytycznie ważne dla optymalizacji wydajności aplikacji Compose.

Najważniejsze

  • Composition — to wykonywanie funkcji Composable w celu budowania drzewa UI
  • Sloty — komórki pamięci przechowujące parametry i stan każdej funkcji
  • Pozycja w Compose (Positional Memorization) wiąże stan z miejscem w kodzie
  • Pierwsze przejście Composition tworzy początkowe drzewo UI przy uruchomieniu ekranu
  • CompositionLocal przekazuje dane przez drzewo bez jawnych parametrów

Czym jest Composition w Jetpack Compose

Composition — to proces wykonywania funkcji Composable, w wyniku którego powstaje wewnętrzna reprezentacja interfejsu użytkownika w postaci drzewa węzłów. Każdy węzeł tego drzewa odpowiada albo wbudowanemu komponentowi (Text, Button, Image), albo wywołaniu własnej funkcji Composable. Composition nie tworzy bezpośrednio obiektów View Androida — buduje abstrakcyjny opis, który jest następnie przetwarzany przez fazy Layout i Drawing.

Kluczową cechą Composition jest jego możliwość restartu (restartability). Każda funkcja Composable w składzie kompozycji może zostać ponownie uruchomiona w dowolnym momencie, jeśli zmienią się jej parametry wejściowe lub odczytane przez nią obiekty stanu. System nie restartuje całego drzewa — tylko te funkcje, które rzeczywiście zależą od zmienionych danych.

Technicznie Composition jest zarządzane przez Composer — wewnętrzny silnik, który kompilator Kotlin wbudowuje w każdą funkcję Composable. Composer zapisuje w slotach (grupach pozycji) informacje o tym, które funkcje zostały wywołane, z jakimi parametrami i w jakiej kolejności. Przy kolejnych wywołaniach Composer porównuje nowe dane z zapisanymi i podejmuje decyzję o restarcie.

Jak budowane jest drzewo UI w procesie Composition

Proces budowania drzewa UI rozpoczyna się od wywołania metody setContent wewnątrz Activity lub Fragment. Ta metoda tworzy początkowy Composition i uruchamia wykonywanie głównej funkcji Composable. Następnie każda zagnieżdżona funkcja Composable dodaje swoje węzły do drzewa, tworząc hierarchię: Row zawiera Text i Button, Column zawiera Image i Card, i tak dalej.

Każdy węzeł drzewa otrzymuje unikalny klucz pozycji, oparty na jego położeniu w kodzie źródłowym. Ten klucz jest używany do identyfikacji węzła przy kolejnych wykonaniach. Klucz pozycji jest powodem, dla którego kolejność wywoływania funkcji Composable nie powinna zależeć od warunków: jeśli w jednym uruchomieniu wywołano A -> B, a w następnym B -> A, Compose nie będzie w stanie dopasować starych i nowych węzłów.

kotlin
@Composable
fun AppScreen() {
    Column {                     // Węzeł Column (pozycja 1)
        HeaderSection()            // Węzeł HeaderSection (pozycja 2)
        ContentSection()           // Węzeł ContentSection (pozycja 3)
        FooterSection()            // Węzeł FooterSection (pozycja 4)
    }
}

@Composable
fun HeaderSection() {
    Row {                       // Węzeł Row (pozycja 2.1)
        Text("Tytuł")         // Węzeł Text (pozycja 2.2)
        Icon(...)                // Węzeł Icon (pozycja 2.3)
    }
}

W tym przykładzie każde wywołanie otrzymuje pozycję opartą na kolejności w kodzie. Column (pozycja 1) zawiera trzy węzły potomne (pozycje 2, 3, 4). HeaderSection dodaje jeszcze dwa węzły potomne (2.1, 2.2, 2.3). Jeśli w następnej rekompozycji ContentSection zostanie wywołany przed HeaderSection, Composer nie będzie w stanie poprawnie dopasować węzłów — stąd zasada: kolejność wywołań funkcji Composable musi być stabilna.

Zarządzanie stanem w Composition

Stan w Composition jest zarządzany przez obiekty typu State<T>. Gdy funkcja Composable odczytuje wartość z State poprzez delegowaną właściwość (by), rejestruje zależność od tego State. Przy zmianie wartości wszystkie funkcje, które odczytały ten State, są oznaczane do restartu w następnej fazie kompozycji.

Mechanizm rejestracji zależności nazywa się systemem snapshotów. Za każdym razem, gdy State się zmienia, snapshot rejestruje wszystkie zmiany i powiadamia Composer, które funkcje zależą od tego State. Ważne jest zrozumienie: odczyt State w kodzie nie będącym Composable (np. w lambdzie onClick) nie rejestruje zależności — tylko odczyt wewnątrz funkcji Composable lub w lambdach wykonanych w kontekście kompozycji.

System snapshotów działa transakcyjnie: kilka zmian State w ramach jednego zdarzenia łączy się w jedną transakcję, co zapobiega wielokrotnym rekompozycjom. Jest to szczególnie ważne przy obsłudze gestów: podczas jednego ruchu zmienia się kilka obiektów State, ale Compose wykonuje tylko jedną rekompozycję.

kotlin
@Composable
fun StateExample() {
    var text by remember { mutableStateOf("Hello") }
    var isVisible by remember { mutableStateOf(true) }

    Column {
        Text(text)  // rejestruje zależność od text

        if (isVisible) {  // rejestruje zależność od isVisible
            TextField(value = text, onValueChange = { text = it })
        }

        Button(onClick = { isVisible = !isVisible }) {
            Text(if (isVisible) "Ukryj" else "Pokaż")
        }
    }
}

Zmiana text prowadzi do rekompozycji tylko Column, Text i TextField. Button i warunek isVisible pozostają bez zmian. Taka izolacja rekompozycji to kluczowa zaleta Compose w porównaniu z systemami przerysowującymi cały ekran. Każda funkcja Composable śledzi tylko te obiekty State, które bezpośrednio odczytuje.

Composition vs Recomposition: kluczowe różnice

Composition (kompozycja) i Recomposition (rekompozycja) to dwa różne tryby wykonywania funkcji Composable. Composition odbywa się raz przy tworzeniu ekranu: system wykonuje wszystkie funkcje Composable z wartościami początkowymi i buduje początkowe drzewo UI. Recomposition odbywa się wielokrotnie przy zmianie danych: system restartuje tylko te funkcje, które zależą od zmienionego stanu.

Tryb Composition aktywuje wszystkie węzły drzewa, przydziela sloty dla każdej funkcji, rejestruje wszystkich potomków. Recomposition działa wybiórczo: Compose porównuje nowe i stare wartości parametrów każdej funkcji, a jeśli się nie zmieniły — funkcja nie jest wykonywana (skipping).

Composition i Recomposition różnią się kosztem. Pierwszy Composition jest droższy, ponieważ wymaga pełnego zbudowania drzewa i przydzielenia slotów. Recomposition jest tańszy, szczególnie jeśli większość funkcji jest stabilna — ich parametry są porównywane przez equals, a Compose pomija ich wywołanie. Dla maksymalnej wydajności należy dążyć do tego, aby jak najmniej funkcji było dotkniętych rekompozycją.

CechaCompositionRecomposition
Kiedy występujeRaz, przy pierwszym wyświetleniuWielokrotnie, przy zmianie danych
ZakresCałe drzewoTylko zmienione funkcje
Porównanie parametrówNie wykonywaneWykonywane dla skipping
Tworzenie slotówTak, wszystkie sloty są tworzoneTylko dla nowych węzłów

CompositionLocal: przekazywanie danych przez drzewo

CompositionLocal — mechanizm niejawnego przekazywania danych przez drzewo kompozycji. Rozwiązuje problem, gdy parametr musi być przekazany przez dziesiątki zagnieżdżonych funkcji Composable, które go bezpośrednio nie używają. Zamiast jawnego łańcucha parametrów dane są ustawiane na górnym poziomie i odczytywane w dowolnej zagnieżdżonej funkcji przez CompositionLocal.current.

Motyw MaterialTheme — najbardziej znany przykład CompositionLocal. Wszystkie komponenty Compose odczytują kolory, typografię i kształty przez MaterialTheme.colorScheme, MaterialTheme.typography, MaterialTheme.shapes, nie otrzymując ich przez parametry. Deweloper może tworzyć własne CompositionLocal dla takich danych jak bieżący użytkownik, ustawienia lokalizacji czy konfiguracja ekranu.

Ważne ograniczenie: CompositionLocal nie powinien być używany dla często zmieniających się danych (pozycja przewijania, tekst w polu wejściowym). Komponent odczytujący CompositionLocal jest restartowany przy każdej zmianie wartości, dlatego dla dynamicznych danych lepiej używać jawnych parametrów lub State. CompositionLocal jest optymalny dla danych konfiguracyjnych, które zmieniają się rzadko lub wcale.

kotlin
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  // odczyt bez jawnego parametru
    AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}

CompositionLocalProvider tworzy zakres widoczności, wewnątrz którego LocalUser.current zwraca ustawioną wartość. UserAvatar odczytuje użytkownika bez jawnego przekazywania parametru przez funkcje pośrednie. Jest to szczególnie cenne w głębokich hierarchiach, gdzie dane są potrzebne tylko w kilku węzłach liściowych.

Często zadawane pytania

Co się stanie, jeśli zmienię State podczas Composition?

Zmiana State podczas Composition planuje nową rekompozycję, która zostanie wykonana po zakończeniu bieżącej. Zapętlanie nie występuje: Compose gwarantuje, że każda rekompozycja jest wykonywana w osobnej transakcji systemu snapshotów.

Ile czasu zajmuje Composition złożonego ekranu?

Na nowoczesnych urządzeniach Composition ekranu z 50–100 funkcjami Composable zajmuje 1–5 ms. Google zaleca mieścić się w 16 ms na klatkę przy 60fps. Jeśli Composition przekracza ten limit, użyj LazyColumn lub podziel ekran na mniejsze funkcje.

Czy można uruchomić Composition ręcznie?

Bezpośrednie ręczne uruchomienie Composition jest niemożliwe — jest zarządzane automatycznie przez Composer. Można jednak wymusić rekompozycję, zmieniając State lub wywołując invalidate() na głównym kompozycie, jeśli ma się dostęp do CompositionContext.

Czym różni się Composition od hierarchii View w klasycznym Androidzie?

Hierarchia View — to niezmienne drzewo obiektów Java, które tworzy się raz. Composition — wirtualne drzewo, które jest przebudowywane przy każdej zmianie danych. View zachowuje swój stan w zmiennych instancji, Composition — w slotach związanych z pozycją wywołania funkcji.

Jak Composition obsługuje usuwanie węzłów?

Jeśli funkcja Composable przestaje być wywoływana (np. warunek if stał się false), Composition usuwa jej węzeł i wywołuje czyszczenie DisposableEffect. Przy ponownym pojawieniu się (if ponownie true) tworzony jest nowy węzeł — stary nie jest przywracany.

Podsumowanie

  • Composition — proces wykonywania funkcji Composable do budowania drzewa UI z powiązaniem ze stanem
  • Composer zarządza slotami, zapisuje wywołania funkcji i porównuje parametry przy rekompozycji
  • System snapshotów rejestruje zależności funkcji od State i łączy zmiany w transakcje
  • Composition wykonywany jest raz przy starcie, Recomposition — przy zmianie danych
  • CompositionLocal przekazuje dane konfiguracyjne przez drzewo bez jawnego łańcucha parametrów
  • Pozycja wywołania funkcji służy jako jej unikalny identyfikator w drzewie kompozycji
  • Zalecenie: twórz funkcje Composable małe, z niezmiennymi parametrami dla efektywnego skippingu

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ż