composable() — to funkcja biblioteki Navigation Compose, która rejestruje ekran w NavHost i łączy URL-ścieżkę z układem Compose. Gdy nawigacja przechodzi na określoną ścieżkę, Jetpack Compose wywołuje odpowiednią funkcję composable i wyświetla ją jako bieżący ekran. W przeciwieństwie do FragmentManager lub nawigacji opartej na Intent, composable() działa na poziomie pojedynczej Activity i jest w pełni zarządzany przez Kotlin DSL. Według danych Android Developers (2025), ponad 73% nowoczesnych aplikacji Android zbudowanych na Jetpack Compose używa właśnie Navigation Compose do organizacji zmiany ekranów.
Najważniejsze
composable() — to funkcja-rozszerzenie (extension function) obiektu NavHost. Kotlin DSL pozwala wywoływać ją wewnątrz bloku NavHost do deklaratywnego opisywania wszystkich ekranów aplikacji. Każde wywołanie tworzy wpis w grafie nawigacyjnym, łącząc ścieżkę tekstową z funkcją composable. Gdy użytkownik przechodzi na określoną ścieżkę, NavHost wyświetla odpowiedni composable jako bieżący ekran, ukrywając poprzedni.
Biblioteka Navigation Compose została przedstawiona przez Google w 2021 roku jako alternatywa dla nawigacji opartej na Fragment w Jetpack Compose. Główną zaletą jest pełna kompatybilność z paradygmatem Compose: composable() działa w tym samym cyklu życia co pozostałe komponenty Compose, bez potrzeby używania FragmentManager lub transakcji. Eliminuje to klasę błędów związanych z niezgodnością cyklu życia Fragment i Compose.
Każdy composable() przyjmuje ścieżkę tekstową (route) i funkcję lambda, która otrzymuje obiekt NavBackStackEntry i zwraca interfejs Composable UI. Wewnątrz lambdy można odwołać się do NavController przez wywołanie navController z zakresu, co pozwala organizować przejścia do innych ekranów. Taka architektura czyni nawigację jawną i przewidywalną.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile")
}
)
}
composable("profile") {
ProfileScreen(
onBack = { navController.popBackStack() }
)
}
}
}
Każde wywołanie composable() tworzy w wewnętrznym grafie NavHost wierzchołek z unikalnym identyfikatorem ścieżki. Gdy NavController wykonuje navigate(), biblioteka porównuje żądaną ścieżkę ze wszystkimi zarejestrowanymi wierzchołkami composable i znajduje odpowiednią. Po dopasowaniu tworzony jest NavBackStackEntry, który jest umieszczany na stosie nawigacji i uruchamiana jest kompozycja UI.
Wewnętrzna implementacja composable() wykorzystuje mechanizm leniwej inicjalizacji: kompozycja ekranu następuje dopiero w momencie pierwszego przejścia na tę ścieżkę. Oznacza to, że ekrany, do których użytkownik nigdy nie przeszedł, nie zajmują pamięci i nie wykonują żadnego kodu. Takie podejście znacznie poprawia wydajność aplikacji z dużą liczbą ekranów.
Parametr key w composable() pozwala zarządzać odtwarzaniem ekranu. Domyślnie composable nie jest odtwarzany przy ponownym przejściu na tę samą ścieżkę — NavHost używa istniejącego wpisu na stosie. Jeśli jednak key zostanie przekazany i ulegnie zmianie, NavHost utworzy nową instancję funkcji composable. Jest to przydatne dla ekranów z danymi dynamicznymi, gdzie trzeba wymusić odświeżenie stanu przy ponownym otwarciu.
val NavGraphBuilder.Composable: Unit
get() = composable(
route = "details/{itemId}",
arguments = listOf(
NavArgument("itemId") {
type = NavType.IntType
}
),
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://details/{itemId}" }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailsScreen(itemId = itemId)
}
composable() obsługuje elastyczny system argumentów przez parametr arguments. Każdy argument jest opisywany przez obiekt NavArgument, który określa typ, wartość domyślną i obowiązkowość. Argumenty są przekazywane w ścieżce jako parametry ścieżki (przez nawiasy klamrowe) lub parametry zapytania (przez znak zapytania).
Parametry ścieżki są wskazywane bezpośrednio w szablonie ścieżki: "profile/{userId}". Przy przejściu na ścieżkę "profile/42" NavHost automatycznie wyodrębnia wartość 42 i udostępnia ją przez backStackEntry.arguments. Parametry zapytania są dodawane po znaku zapytania: "search?query={text}" i również są automatycznie parsowane przez bibliotekę.
Przy wyodrębnianiu argumentów ważne jest sprawdzanie obowiązkowości parametru przez NavType.isNullableAllowed i dostarczanie wartości domyślnych przez NavArgument defaultValue. Jeśli obowiązkowy parametr jest nieobecny, Navigation Compose generuje wyjątek IllegalArgumentException, co zapobiega niezauważonym błędom z nieprawidłowymi ścieżkami.
| Typ argumentu | NavType | Przykład w ścieżce |
|---|---|---|
| Int | NavType.IntType | "item/{id}" |
| String | NavType.StringType | "user/{name}" |
| Boolean | NavType.BoolType | "filter?enabled={value}" |
| Float | NavType.FloatType | "map/{lat}/{lon}" |
| Long | NavType.LongType | "article/{timestamp}" |
Do przekazywania złożonych obiektów zaleca się używanie NavType.ParcelableType lub NavType.SerializableType. Google radzi jednak minimalizować rozmiar przesyłanych danych — lepiej przekazywać identyfikator i ładować obiekt po identyfikatorze wewnątrz ekranu. Zapobiega to problemom z dużymi danymi serializowanymi i upraszcza obsługę zmian konfiguracji.
data class Profile(val id: Int, val name: String) : Parcelable
// Nawiguj z minimalną ilością danych
navController.navigate("profile/42")
// Pobieraj argumenty na ekranie
composable(
route = "profile/{userId}",
arguments = listOf(
NavArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
ProfileDetailScreen(userId = userId)
}
W rzeczywistych aplikacjach często wymagane jest organizowanie zagnieżdżonych grafów nawigacji — na przykład oddzielny stos ekranów wewnątrz zakładki BottomNavigation. composable() obsługuje zagnieżdżanie przez mechanizm zagnieżdżonych NavHost: wewnątrz ekranu composable można zadeklarować własny NavHost z niezależnym stosem ścieżek.
Każdy zagnieżdżony NavHost ma własny NavController i stos. Oznacza to, że nawigacja wewnątrz zakładki nie wpływa na nawigację w innych zakładkach — użytkownik może swobodnie przełączać się między zakładkami, nie tracąc historii przejść wewnątrz każdej z nich. Taka architektura nazywa się Scoped Navigation i jest zalecana przez Google dla aplikacji ze złożoną wielopoziomową nawigacją.
Przy implementacji zagnieżdżonej nawigacji ważne jest prawidłowe zarządzanie stanem NavController: każdy zagnieżdżony NavHost powinien przechowywać swój rememberNavController wewnątrz zakresu funkcji composable. Według danych Android Developer Summit 2024, ponad 40% aplikacji na Jetpack Compose z trzema i więcej zakładkami używa architektury zagnieżdżonych NavHost do izolacji nawigacji między modułami.
// Główny NavHost z zakładkami
composable("tabs") {
MainTabsScreen { tab ->
when (tab) {
Tab.Home -> HomeNavGraph()
Tab.Search -> SearchNavGraph()
}
}
}
// Zagnieżdżony graf w zakładce Home
@Composable
fun HomeNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_feed"
) {
composable("home_feed") { FeedScreen() }
composable("home_detail/{postId}") { PostDetailScreen() }
}
}
Przed Jetpack Compose standardowym sposobem nawigacji w Androidzie było użycie Intent i FragmentManager. Intent — to wiadomość systemowa, która uruchamia nową Activity, co oznacza odtworzenie całego drzewa View. W przeciwieństwie do tego, composable() działa wewnątrz jednej Activity i po prostu zastępuje część drzewa Compose, co jest znacznie szybsze i bardziej efektywne pamięciowo.
Główne różnice między composable() a nawigacją opartą na Intent:
| Cecha | composable() | Intent / Fragment |
|---|---|---|
| Architektura | Single Activity, drzewo Compose | Multi Activity, stosy Fragment |
| Przekazywanie danych | parametry ścieżki/zapytania, współdzielony ViewModel | Intent extras, Bundle, SharedPreferences |
| Głębokie linki | Wbudowane wsparcie navDeepLink | intent-filter w manifeście |
| Stos wsteczny | Automatyczne zarządzanie popBackStack | FragmentManager.popBackStack() |
| Czas przełączania | 5–15 ms (wewnątrz procesu) | 50–200 ms (z odtworzeniem) |
Przejście z Intent na composable() — to nie tylko zmiana API, ale zmiana paradygmatu architektonicznego. Zamiast jawnego wskazywania, która Activity ma się otworzyć, programista deklaratywnie opisuje wszystkie możliwe ścieżki w jednym miejscu, co poprawia czytelność kodu i upraszcza testowanie nawigacji. Według danych Google I/O 2024, Jetpack Compose z Navigation Compose zmniejsza ilość kodu nawigacji o 40–60% w porównaniu z FragmentManager.
Jednym z najczęstszych błędów jest odtwarzanie NavController przy rekompozycji. Jeśli NavController jest tworzony przez rememberNavController() na poziomie nadrzędnego composable, który może być odtwarzany przy zmianie stanu, nawigacja ulega zniszczeniu — historia przejść zostaje utracona. Prawidłowym rozwiązaniem jest podniesienie NavController na poziom stabilnego composable, na przykład na poziom Activity lub głównego composable aplikacji.
Drugim częstym problemem jest nieskończona rekompozycja podczas nawigacji. Dzieje się tak, gdy wywołanie navController.navigate() znajduje się bezpośrednio w ciele funkcji composable. Ponieważ nawigacja zmienia stan NavHost, wyzwala to rekompozycję, która ponownie wywołuje navigate(), tworząc cykl. Wszystkie wywołania nawigacji powinny być owinięte w lambdy-obserwatory (onClick, onButtonPressed), a nie wykonywane w kompozycji.
Trzecim błędem jest nieprawidłowe zarządzanie stosem wstecznym przy użyciu BottomNavigation. Prosta nawigacja przez navigate() przy każdym przełączaniu zakładki dodaje nowy wpis do stosu, zamiast wracać do istniejącego. Dla BottomNavigation należy używać navController.navigate() z restoreState = true i launchSingleTop = true, co zapewnia prawidłowe przywracanie stanu przy przełączaniu zakładek.
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
Często zadawane pytania
composable() — to nie adnotacja, ale funkcja-rozszerzenie NavHost, która wiąże ścieżkę z interfejsem UI. Zwykła funkcja @Composable po prostu opisuje układ, a composable() rejestruje ten układ w grafie nawigacyjnym z określoną ścieżką, czyniąc go dostępnym do nawigacji przez NavController.
Zaleca się przekazywanie tylko identyfikatora (ID) przez parametr ścieżki, a sam obiekt ładować na ekranie po ID przez repozytorium lub ViewModel. Jeśli obiekt jednak trzeba przekazać, użyj NavType.ParcelableType, ale unikaj przekazywania obiektów większych niż 1 KB — może to prowadzić do TransactionTooLargeException.
Obrót ekranu powoduje zmianę konfiguracji, która domyślnie odtwarza Activity. Aby zachować stan ekranów composable, używaj rememberSaveable dla prostych danych lub ViewModel z zakresem danego ekranu. Navigation Compose przywraca stos wsteczny po odtworzeniu, ale stan wewnątrz funkcji composable() jest resetowany bez rememberSaveable.
Nie, composable() — to funkcja-rozszerzenie NavGraphBuilder, która jest dostępna tylko wewnątrz bloku NavHost. Do prostej zamiany części UI bez nawigacji używaj renderowania warunkowego (when, if) lub AnimatedContent. composable() jest przeznaczony właśnie do routingu z obsługą stosu wstecznego i głębokich linków.
Użyj SavedStateHandle wewnątrz ViewModel: przy pierwszym przejściu handle.get("initialized") zwróci null, przy powrocie — zapisaną wartość. Alternatywnie, analizuj bieżącą pozycję w stosie wstecznym przez navController.previousBackStackEntry — jeśli jest null, oznacza to pierwszy ekran w stosie nawigacji.
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ż