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 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.
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.
@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.
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ę.
@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 (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ą.
| Cecha | Composition | Recomposition |
|---|---|---|
| Kiedy występuje | Raz, przy pierwszym wyświetleniu | Wielokrotnie, przy zmianie danych |
| Zakres | Całe drzewo | Tylko zmienione funkcje |
| Porównanie parametrów | Nie wykonywane | Wykonywane dla skipping |
| Tworzenie slotów | Tak, wszystkie sloty są tworzone | Tylko dla nowych węzłów |
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.
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
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.
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.
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.
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.
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
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.
Przeczytaj również