composable(): co to jest, NavHost i routing w Jetpack Compose

Autor: IT Sectr Opublikowano: 2026-06-30 Czas czytania: 9 min

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() — funkcja rejestracji ekranu w NavHost biblioteki Navigation Compose.
  • Ścieżka — każdy ekran jest identyfikowany przez ścieżkę tekstową przekazywaną jako pierwszy argument.
  • Parametry — composable() obsługuje argumenty przez NavArgument, w tym obowiązkowe i opcjonalne.
  • Zagnieżdżanie — obsługiwana jest nawigacja zagnieżdżona przez zagnieżdżone NavHost z oddzielnymi grafami ścieżek.
  • Wydajność — composable() używa leniwej inicjalizacji: ekran jest tworzony dopiero przy pierwszym przejściu.

Co to jest composable() w NavHost

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ą.

kotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToProfile = {
                    navController.navigate("profile")
                }
            )
        }
        composable("profile") {
            ProfileScreen(
                onBack = { navController.popBackStack() }
            )
        }
    }
}

Jak działa composable(): klucze i parametry

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.

kotlin
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)
    }

Przekazywanie argumentów przez composable()

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 argumentuNavTypePrzykład w ścieżce
IntNavType.IntType"item/{id}"
StringNavType.StringType"user/{name}"
BooleanNavType.BoolType"filter?enabled={value}"
FloatNavType.FloatType"map/{lat}/{lon}"
LongNavType.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.

kotlin
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)
}

Zagnieżdżona nawigacja z composable()

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.

kotlin
// 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() }
    }
}

Porównanie composable() z nawigacją Intent

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:

  • Szybkość — composable() przełącza ekrany w milisekundach bez odtwarzania Activity, Intent wymaga ponownego uruchomienia Activity.
  • Animacje — w Navigation Compose animacje przejść są definiowane deklaratywnie przez AnimatedNavHost, bez potrzeby używania overridePendingTransition.
  • Stan współdzielony — composable() działa w wspólnym zakresie ViewModel, co upraszcza przekazywanie danych między ekranami bez Intent extras.
Cechacomposable()Intent / Fragment
ArchitekturaSingle Activity, drzewo ComposeMulti Activity, stosy Fragment
Przekazywanie danychparametry ścieżki/zapytania, współdzielony ViewModelIntent extras, Bundle, SharedPreferences
Głębokie linkiWbudowane wsparcie navDeepLinkintent-filter w manifeście
Stos wstecznyAutomatyczne zarządzanie popBackStackFragmentManager.popBackStack()
Czas przełączania5–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.

Typowe błędy z composable()

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.

kotlin
fun NavController.navigateToTab(route: String) {
    navigate(route) {
        popUpTo(navController.graph.findStartDestination().id) {
            saveState = true
        }
        launchSingleTop = true
        restoreState = true
    }
}

Często zadawane pytania

Jaka jest różnica między composable() a zwykłą funkcją @Composable?

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.

Jak przekazać złożony obiekt między ekranami composable()?

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.

Dlaczego ekran composable() jest odtwarzany przy obrocie ekranu?

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.

Czy można używać composable() bez NavHost?

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.

Jak odróżnić pierwsze przejście od powrotu w composable()?

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

  • composable() — funkcja rejestracji ekranu w NavHost, główny sposób organizacji nawigacji w Jetpack Compose.
  • Ścieżki — każdy ekran jest identyfikowany przez tekst ścieżki z opcjonalnymi parametrami ścieżki i zapytania.
  • Argumenty — przekazywane przez NavArgument z obsługą typów prymitywnych, Parcelable i Serializable.
  • Zagnieżdżanie — composable() obsługuje zagnieżdżone NavHost do organizacji modułowej nawigacji z niezależnymi stosami.
  • Wydajność — leniwa inicjalizacja ekranów oszczędza pamięć, czas przełączania między ekranami wynosi 5–15 ms.
  • Błędy — główne problemy: odtwarzanie NavController, nieskończona rekompozycja przy wywołaniu navigate() w ciele composable, nieprawidłowe działanie BottomNavigation.
  • Migracja — przejście z FragmentManager na composable() zmniejsza ilość kodu nawigacji o 40–60% i eliminuje klasę błędów związanych z cyklem życia Fragment.

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ż