Adnotacja @Composable — fundamentalny element Jetpack Compose, który zamienia zwykłą funkcję Kotlin w deklaratywny blok konstrukcyjny interfejsu użytkownika. Bez tej adnotacji nie można utworzyć żadnego ekranu we współczesnym programowaniu na Androida. Według Google Android Developers, 2026, ponad 80% nowych projektów w Kotlin używa Compose do budowy UI, a @Composable jest najczęściej używaną adnotacją w ekosystemie.
Najważniejsze
@Composable — to adnotacja języka Kotlin, która oznacza funkcję jako przeznaczoną do opisywania interfejsu użytkownika w frameworku Jetpack Compose. Gdy kompilator Kotlin napotka tę adnotację, generuje dodatkowy kod umożliwiający funkcji działanie w kontekście kompozycji — systemu zarządzania drzewem UI.
Adnotacja @Composable została wprowadzona przez Google w 2021 roku wraz z pierwszą stabilną wersją Jetpack Compose 1.0. Przed jej pojawieniem się tworzenie interfejsów na Androida odbywało się wyłącznie poprzez znaczniki XML i system View. @Composable radykalnie zmieniła podejście: zamiast opisywać UI w osobnym pliku znaczników, programista pisze interfejs bezpośrednio w Kotlin.
Główna różnica między @Composable a zwykłymi funkcjami Kotlin — zdolność do odczytywania i reagowania na zmiany stanu. Gdy zmienna, którą odczytuje funkcja Composable, zmienia wartość, system automatycznie planuje ponowne uruchomienie (rekompozycję) tej funkcji. To uwalnia programistę od ręcznej aktualizacji UI przez findViewById i setText.
Wewnętrzna mechanika @Composable opiera się na koncepcji slotu — specjalnego obszaru pamięci przydzielanego każdej funkcji w ramach kompozycji. W tym slocie przechowywane są wartości przekazane do funkcji oraz informacje pomocnicze niezbędne do porównania przy kolejnych wywołaniach.
Aby zadeklarować funkcję Composable, wystarczy dodać adnotację @Composable przed słowem kluczowym fun. Funkcja musi znajdować się w pakiecie importującym adnotację z androidx.compose.runtime. Nazwę funkcji zaleca się pisać wielką literą — to powszechnie przyjęta konwencja w społeczności Compose, która wizualnie odróżnia komponenty UI od zwykłych funkcji.
import androidx.compose.runtime.Composable
@Composable
fun Greeting(name: String) {
var count by remember { mutableStateOf(0) }
Column {
Text("Witaj, $name!")
Button(onClick = { count++ }) {
Text("Kliknięto $count razy")
}
}
}
Parametry funkcji Composable mogą być dowolne — typy prymitywne, ciągi znaków, lambdy, a nawet inne funkcje Composable przekazywane przez Slot API. Zaleca się czynić parametry niemutowalnymi (val), aby uniknąć efektów ubocznych podczas rekompozycji. Wszystkie zmienne dane powinny być zarządzane przez mechanizmy stanu Compose.
Funkcje Composable nie mogą zwracać dowolnych wartości jak zwykłe funkcje — ich jedynym zadaniem jest zbudowanie lub zaktualizowanie fragmentu drzewa UI. Istnieją jednak specjalne wzorce, takie jak State Hoisting, w którym funkcja Composable przyjmuje stan i callbacki przez parametry, pozostając czystą i wielokrotnego użytku.
System Compose nakłada kilka ścisłych ograniczeń na to, jak powinny wyglądać i działać funkcje Composable. Pierwsza zasada: funkcja Composable może wywoływać tylko inne funkcje Composable lub zwykłe funkcje niemające efektów ubocznych. Zapewnia to przewidywalność kompozycji i prawidłowe działanie optymalizacji Compose.
Druga zasada dotyczy kolejności wykonywania. Compose ma prawo wywoływać funkcje Composable w dowolnej kolejności, dlatego kod w treści takiej funkcji nie powinien polegać na sekwencji wywołań sąsiednich funkcji. Każda funkcja Composable powinna być samowystarczalna na poziomie swojej pozycji w drzewie UI.
Trzecia zasada — zakaz efektów ubocznych wewnątrz samej treści funkcji Composable. Operacje takie jak zapis do bazy danych, wysyłanie zapytań sieciowych czy zmiana zmiennych zewnętrznych muszą być wykonywane tylko wewnątrz specjalnych efektów LaunchedEffect, DisposableEffect lub SideEffect. Naruszenie tej zasady prowadzi do nieprzewidywalnego zachowania podczas rekompozycji.
Czwarta zasada: funkcje Composable powinny być idempotentne. Ponowne wywołanie z tymi samymi argumentami powinno dawać ten sam UI. Jest to wymaganie niezbędne do poprawnego działania optymalizacji skippingu, przy której Compose pomija przerysowywanie funkcji, których dane wejściowe się nie zmieniły.
// Poprawnie: czysta funkcja Composable bez efektów ubocznych
@Composable
fun UserCard(user: User, onClick: () -> Unit) {
Card(modifier = Modifier.clickable { onClick() }) {
Text(text = user.name)
}
}
// Źle: efekt uboczny w ciele funkcji
@Composable
fun WrongCard(userId: String) {
// val result = viewModel.loadUser(userId) // NIEDOZWOLONE
Text("Ładowanie...")
}
Rozważmy praktyczny przykład tworzenia ekranu profilu z użyciem adnotacji @Composable. Demonstrujemy tutaj łączenie kilku funkcji Composable, pracę ze stanem i modyfikatorami — kluczowymi elementami każdego układu Compose.
@Composable
fun ProfileScreen(userId: String) {
var isFollowed by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
ProfileHeader(userId = userId)
Spacer(modifier = Modifier.height(16.dp))
StatsRow(posts = 42, followers = 1280)
Spacer(modifier = Modifier.height(24.dp))
FollowButton(
isFollowed = isFollowed,
onToggle = { isFollowed = !isFollowed }
)
}
}
@Composable
fun ProfileHeader(userId: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
AsyncImage(model = "https://example.com/avatars/$userId",
contentDescription = "User avatar")
Spacer(modifier = Modifier.width(12.dp))
Text(text = "Użytkownik #$userId", style = MaterialTheme.typography.headlineMedium)
}
}
@Composable
fun StatsRow(posts: Int, followers: Int) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
StatItem("Posts", posts)
StatItem("Followers", followers)
}
}
@Composable
fun StatItem(label: String, value: Int) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "$value", style = MaterialTheme.typography.headlineSmall)
Text(text = label, style = MaterialTheme.typography.bodySmall)
}
}
W przykładzie każda funkcja Composable odpowiada za swoją część ekranu: ProfileScreen zarządza ogólnym stanem i kompozycją funkcji potomnych, ProfileHeader wyświetla awatar i nazwę, a StatsRow — blok ze statystykami. Takie podejście stosuje zasadę pojedyńczej odpowiedzialności i ułatwia ponowne użycie komponentów.
W Jetpack Compose istnieją trzy główne typy funkcji Composable. Pierwszy typ — kontenery (Row, Column, Box, LazyColumn), które określają rozmieszczenie elementów potomnych. Drugi — elementy wyświetlania (Text, Image, Icon, Button), które renderują konkretne komponenty UI. Trzeci — niestandardowe funkcje Composable, które łączą wbudowane komponenty w wielokrotnego użytku bloki.
Kontenery różnią się od zwykłych elementów tym, że przyjmują lambdę content — ostatni parametr typu @Composable () -> Unit. To właśnie ten mechanizm umożliwia budowanie zagnieżdżonych drzew UI: każdy kontener tworzy kompozycję potomną z własnym kontekstem i obszarem pamięci.
Niestandardowe funkcje Composable dzielą się na dwa podtypy: inteligentne (smart) i głupie (dumb). Inteligentne funkcje zarządzają stanem i logiką — zawierają wywołania remember, LaunchedEffect i innych API Compose. Głupie funkcje przyjmują wszystkie dane przez parametry i tylko je wyświetlają. Podział na inteligentne i głupie komponenty poprawia testowalność i możliwość ponownego użycia kodu.
| Typ | Przykład | Przeznaczenie |
|---|---|---|
| Kontener | Column, Row, Box | Zarządzanie rozmieszczeniem elementów potomnych |
| Element | Text, Image, Button | Wyświetlanie treści i obsługa wprowadzania danych |
| Niestandardowy | ProfileCard, UserList | Połączenie standardowych komponentów |
Główną zaletą adnotacji @Composable jest możliwość tworzenia wielokrotnego użytku komponentów UI bez dziedziczenia i złożonych hierarchii klas. W przeciwieństwie do systemu View, gdzie każdy niestandardowy element wymagał utworzenia klasy Java z konstruktorami, komponent Composable to po prostu funkcja Kotlin z parametrami.
Aby zapewnić możliwość ponownego użycia, stosuje się wzorzec Slot API, w którym funkcja Composable przyjmuje lambdy content dla różnych obszarów swojego układu. Na przykład komponent Card może przyjmować osobne content dla nagłówka, treści i dolnej części, co czyni go uniwersalnym dla dowolnych ekranów aplikacji.
Modyfikatory (Modifier) odgrywają kluczową rolę w ponownym użyciu: umożliwiają dostosowywanie odstępów, rozmiarów, kliknięć i animacji bez zmiany samego komponentu. Zaleca się zawsze przekazywać Modifier jako parametr funkcji Composable z wartością domyślną: Modifier = Modifier — to standardowa praktyka przyjęta w oficjalnych bibliotekach Google.
@Composable
fun SectionCard(
modifier: Modifier = Modifier,
title: String,
content: @Composable () -> Unit
) {
Card(modifier = modifier) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(8.dp))
content()
}
}
}
Dzięki Slot API komponent SectionCard można używać na różnych ekranach z różną zawartością — formularzami, listami, blokami tekstowymi. Połączenie modyfikatorów i Slot API czyni komponenty Compose maksymalnie elastycznymi bez utraty bezpieczeństwa typów, które zapewnia Kotlin.
Często zadawane pytania
@Composable funkcja jest wykonywana w kontekście kompozycji i może odczytywać stan, automatycznie uruchamiając się ponownie przy jego zmianie. Zwykłe funkcje Kotlin nie mają dostępu do mechanizmów śledzenia stanu i nie uczestniczą w budowaniu drzewa UI.
Nie, funkcji Composable można wywoływać tylko z innych funkcji Composable, ponieważ wymagany jest specjalny kontekst kompozycji. Do integracji kodu Compose ze zwykłym Kotlin używa się metody setContent { } w Activity lub ComposeView w systemie View.
To konwencja nazewnictwa przyjęta w społeczności Compose. Wielka litera wizualnie odróżnia komponenty UI od zwykłych funkcji, zgodnie z zasadami nazewnictwa klas. Nie jest to wymóg kompilatora, ale zalecana praktyka w dokumentacji Google.
Nie ma ograniczeń co do liczby. W praktyce duży ekran może zawierać 50–100 funkcji Composable, w tym wbudowane komponenty (Text, Button) i niestandardowe. Compose optymalizuje drzewo funkcji i wykonuje tylko te, których dane wejściowe się zmieniły.
Zazwyczaj funkcje Composable zwracają Unit, ponieważ ich zadaniem jest budowanie UI. Istnieją jednak wyspecjalizowane funkcje, takie jak remember i derivedStateOf, które są oznaczone @Composable i zwracają wartości. To wyjątek, a nie reguła.
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ż