Composable Function — to fundamentalna jednostka interfejsu użytkownika w Jetpack Compose, która określa, jak powinna wyglądać i zachowywać się część ekranu. Każda taka funkcja jest oznaczona adnotacją @Composable i wykonywana w specjalnym kontekście, umożliwiającym Compose śledzenie zależności i automatyczne przebudowywanie UI przy zmianie danych. Według Google Android Developers, 2026, prawidłowe budowanie Composable-funkcji bezpośrednio wpływa na wydajność aplikacji i efektywność rekompozycji.
Najważniejsze
Composable Function — to funkcja w języku Kotlin oznaczona adnotacją @Composable, która opisuje część interfejsu użytkownika w sposób deklaratywny. Zamiast tworzyć i konfigurować obiekty View przez kod Java lub XML, programista po prostu pisze, jak UI powinien wyglądać przy każdym stanie danych.
Podstawowa różnica między Composable-funkcją a tradycyjnym systemem View w Androidzie polega na modelu aktualizacji. W klasycznym podejściu programista ręcznie wywoływał findViewById, zmieniał tekst przez setText, zarządzał widocznością przez setVisibility. Composable Function uwalnia od tej rutyny: przy zmianie danych system sam określa, które funkcje należy uruchomić ponownie, i wykonuje tylko je.
Kompilator Kotlin, przetwarzając adnotację @Composable, generuje dodatkowy kod, który integruje funkcję z mechanizmem kompozycji. Ten kod obejmuje odczyt i zapis do slotów — specjalnych komórek pamięci przechowujących stan i parametry każdej Composable-funkcji w bieżącym drzewie UI. Dzięki tej integracji Compose wie, które funkcje zależą od których danych.
Składnia Composable-funkcji jest maksymalnie zwięzła: wystarczy dodać @Composable przed słowem kluczowym fun. Funkcja może przyjmować dowolne parametry, zawierać inne wywołania Composable w swoim ciele i używać konstrukcji Kotlin — warunków, pętli, wyrażeń when — do warunkowego wyświetlania UI.
@Composable
fun ProductItem(
product: Product,
modifier: Modifier = Modifier,
onAddToCart: () -> Unit
) {
Card(modifier = modifier.padding(8.dp)) {
Row(modifier = Modifier.fillMaxWidth().padding(12.dp),
verticalAlignment = Alignment.CenterVertically) {
Column(modifier = Modifier.weight(1f)) {
Text(text = product.name, style = MaterialTheme.typography.titleMedium)
Text(text = "${product.price}", color = MaterialTheme.colorScheme.primary)
}
Button(onClick = onAddToCart) {
Text("Dodaj do koszyka")
}
}
}
}
W tym przykładzie Composable-funkcja ProductItem przyjmuje obiekt Product, modyfikator i callback. Wszystkie trzy parametry są niemutowalne, co gwarantuje przewidywalne zachowanie przy rekompozycji. Modyfikator jest przekazany jako parametr z domyślną wartością — to standardowa praktyka, pozwalająca stronie wywołującej dostosowywać odstępy i rozmiary.
Wewnątrz Composable-funkcji używane są wbudowane komponenty Material Design (Text, Button, Card, TextField) lub fundamentalne prymitywy (Canvas, Layout). Każdy komponent przyjmuje parametry do konfiguracji wyglądu i zachowania, a także jeden lub więcej modyfikatorów przez parametr modifier.
Modyfikatory (Modifier) — to łańcuch funkcji, które zmieniają rozmiar, położenie, obsługę zdarzeń i wygląd komponentu. Kolejność modyfikatorów w łańcuchu ma znaczenie: clickable.semantics działa inaczej niż semantics.clickable, a padding.background koloruje tło obszaru wraz z paddingiem, co jest krytyczne przy projektowaniu.
Wewnątrz Composable-funkcji można używać warunków if i when do warunkowego wyświetlania części UI, a także pętli for do dynamicznych list. Wszystkie te konstrukcje działają naturalnie, ponieważ Kotlin to pełnoprawny język programowania. Należy jednak pamiętać: jeśli warunek lub pętla zawiera wywołania Composable-funkcji, one również uczestniczą w rekompozycji.
@Composable
fun ProductList(
products: List<Product>,
modifier: Modifier = Modifier
) {
LazyColumn(modifier = modifier) {
items(products, key = { it.id }) { product ->
ProductItem(
product = product,
onAddToCart = { /* add to cart */ }
)
}
}
}
Rozważmy przykład ekranu wyszukiwania produktów z użyciem kilku Composable-funkcji. Pokazano tutaj typowe wzorce: pole wprowadzania ze stanem, filtrowanie listy, obsługa pustego wyniku i ładowania.
data class Product(
val id: String,
val name: String,
val price: Double,
val category: String
)
@Composable
fun SearchScreen() {
var query by remember { mutableStateOf("") }
val products = remember(query) { getFilteredProducts(query) }
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
OutlinedTextField(
value = query,
onValueChange = { query = it },
label = { Text("Szukaj produktów") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
when (products) {
is Loading -> CircularProgressIndicator()
is Empty -> Text("Nie znaleziono wyników")
is Result -> LazyColumn {
items(products.items, key = { it.id }) { product ->
ProductItem(product = product, onAddToCart = {})
}
}
}
}
}
Ten przykład demonstruje kilka idiomów jednocześnie: remember do przechowywania stanu zapytania, remember(query) do filtrowania z kluczem, when dla trzech stanów UI oraz LazyColumn do efektywnego wyświetlania listy. Każdy z tych idiomów jest wynikiem praktycznego doświadczenia w tworzeniu aplikacji Compose.
Composable-funkcje przyjmują parametry tak samo jak zwykłe funkcje Kotlin, ale z jedną ważną różnicą: parametr może być inną Composable-funkcją przekazaną przez lambdę z adnotacją @Composable. Ten mechanizm nazywa się Slot API i jest głównym wzorcem do tworzenia wielokrotnego użytku kontenerów.
Slot API rozwiązuje problem, który w tradycyjnym systemie View był rozwiązywany przez ViewGroup i programowe dodawanie dzieci View. Zamiast metod addView Compose używa lambd content — ostatniego parametru z typem @Composable () -> Unit. Strona wywołująca przekazuje do tej lambdy dowolny UI, a sam kontener tylko określa jego rozmieszczenie.
Parametry Composable-funkcji mogą mieć wartości domyślne, co upraszcza ich użycie w różnych kontekstach. Zaleca się czynić obowiązkowymi tylko te parametry, bez których funkcja nie może wykonać swojego zadania, a pozostałe wyposażać w rozsądne wartości domyślne.
| Parametr | Typ | Przykład |
|---|---|---|
| Obowiązkowy | Dowolny typ | name: String |
| Opcjonalny | Z wartością domyślną | modifier: Modifier = Modifier |
| Content | @Composable () -> Unit | content: @Composable () -> Unit |
| Callback | Lambda bez @Composable | onClick: () -> Unit |
W społeczności Compose wykształciło się kilka ustalonych idiomów, które czynią Composable-funkcje bardziej czytelnymi i przewidywalnymi. Pierwszy — State Hoisting: stan jest podnoszony na wyższy poziom, a Composable-funkcja otrzymuje go przez parametry. To czyni funkcję czystą i wielokrotnego użytku w różnych kontekstach.
Drugi idiom — Event-driven parametry. Zamiast przekazywać do Composable-funkcji ViewModel lub useCase, przekazywane są tylko konkretne callbacki: onSave, onDelete, onNavigateToDetail. To zmniejsza zależności i upraszcza testowanie — do testu ProductItem nie potrzeba ViewModel, tylko lambda-stub.
Trzeci idiom — CompositionLocal do przekazywania wspólnych danych przez drzewo kompozycji. Motyw, gęstość ekranu, bieżąca trasa — wszystko to jest przekazywane przez CompositionLocal, unikając łańcuchów parametrów przez dziesiątki Composable-funkcji. Nie należy jednak nadużywać CompositionLocal: jawne parametry są zawsze preferowane nad niejawnymi zależnościami.
// State Hoisting: stan podniesiony do funkcji nadrzędnej
@Composable
fun CounterDisplay(
count: Int,
onIncrement: () -> Unit
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Licznik: $count", style = MaterialTheme.typography.headlineLarge)
Button(onClick = onIncrement) {
Text("+1")
}
}
}
// Użycie State Hoisting
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
CounterDisplay(
count = count,
onIncrement = { count++ }
)
}
Często zadawane pytania
Tak, return jest dozwolone, ale z ostrożnością. Compose optymalizuje rekompozycję na poziomie poszczególnych funkcji, a wczesny return może zakłócić tę optymalizację. Lepiej używać operatorów warunkowych if lub when wewnątrz ciała funkcji.
W Kotlin Unit to obiekt-singleton, a nie pusty typ. Composable-funkcje zwracają Unit, co technicznie oznacza, że zwracają sam obiekt Unit. Jednak w praktyce nie ma to znaczenia — zwracana wartość jest ignorowana przez system kompozycji.
Przekazywanie mutowalnych kolekcji jest możliwe, ale to zła praktyka. Jeśli kolekcja się zmieni, Compose nie dowie się o tym, ponieważ referencja do obiektu pozostała ta sama. Używaj immutable-list lub mutableStateListOf do śledzenia zmian.
Do debugowania używaj Android Studio z Layout Inspector, który pokazuje bieżące drzewo Composable-funkcji, wartości parametrów i przyczyny rekompozycji. Działa również zwykły debugger Kotlin — punkty przerwania wewnątrz Composable-funkcji poprawnie wyzwalają się przy każdej rekompozycji.
Composable-funkcja zawsze zwraca Unit, dlatego return type nie jest określany. Próba zwrócenia innego typu spowoduje błąd kompilacji, ponieważ adnotacja @Composable jest niezgodna z typami zwracanymi innymi niż Unit.
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ż