composable() — este o funcție a bibliotecii Navigation Compose care înregistrează un ecran în NavHost și leagă ruta URL de aspectul Compose. Când navigarea trece pe o rută specificată, Jetpack Compose apelează funcția composable corespunzătoare și o afișează ca ecran curent. Spre deosebire de FragmentManager sau navigarea bazată pe Intent, composable() funcționează la nivelul unei singure Activity și este gestionată complet prin Kotlin DSL. Conform datelor Android Developers (2025), peste 73% din aplicațiile Android moderne construite pe Jetpack Compose folosesc exact Navigation Compose pentru organizarea schimbării ecranelor.
Principalele puncte
composable() — este o funcție de extensie (extension function) a obiectului NavHost. Kotlin DSL permite apelarea acesteia în interiorul blocului NavHost pentru descrierea declarativă a tuturor ecranelor aplicației. Fiecare apel creează o intrare în graful de navigare, legând o rută text de o funcție composable. Când utilizatorul navighează pe o rută specificată, NavHost afișează composable-ul corespunzător ca ecran curent, ascunzându-l pe cel anterior.
Biblioteca Navigation Compose a fost prezentată de Google în 2021 ca alternativă la navigarea bazată pe Fragment pentru Jetpack Compose. Principalul avantaj — compatibilitatea completă cu paradigma Compose: composable() funcționează în același ciclu de viață ca și celelalte componente Compose, fără necesitatea FragmentManager sau a tranzacțiilor. Aceasta elimină o clasă de erori legate de nepotrivirea ciclurilor de viață dintre Fragment și Compose.
Fiecare composable() primește o rută text (route) și o funcție lambda care primește un obiect NavBackStackEntry și returnează un UI Composable. În interiorul lambda se poate accesa NavController prin apelul navController din scope, ceea ce permite organizarea navigărilor către alte ecrane. Această arhitectură face navigarea explicită și predictibilă.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile")
}
)
}
composable("profile") {
ProfileScreen(
onBack = { navController.popBackStack() }
)
}
}
}
Fiecare apel composable() creează în graful intern al NavHost un nod cu un identificator unic de rută. Când NavController execută navigate(), biblioteca compară ruta solicitată cu toate nodurile composable înregistrate și găsește nodul corespunzător. După potrivire, se creează un NavBackStackEntry care este plasat pe stiva de navigare și se inițiază compoziția UI.
Implementarea internă a composable() utilizează mecanismul de inițializare lazy: compoziția ecranului are loc doar în momentul primei navigări pe această rută. Aceasta înseamnă că ecranele pe care utilizatorul nu a navigat niciodată nu ocupă memorie și nu execută niciun cod. O astfel de abordare îmbunătățește semnificativ performanța aplicațiilor cu un număr mare de ecrane.
Parametrul key din composable() permite gestionarea recreării ecranului. În mod implicit, composable nu este recreat la o navigare repetată pe aceeași rută — NavHost folosește intrarea existentă din back stack. Totuși, dacă key este transmis și se modifică, NavHost creează o nouă instanță a funcției composable. Acest lucru este util pentru ecrane cu date dinamice, unde este necesară forțarea actualizării stării la redeschidere.
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() suportă un sistem flexibil de argumente prin parametrul arguments. Fiecare argument este descris de un obiect NavArgument care definește tipul, valoarea implicită și obligativitatea. Argumentele sunt transmise în rută ca parametri de cale (prin acolade) sau parametri de query (prin semnul întrebării).
Parametrii de cale sunt indicați direct în șablonul rutei: "profile/{userId}". La navigarea pe ruta "profile/42", NavHost extrage automat valoarea 42 și o face disponibilă prin backStackEntry.arguments. Parametrii de query sunt adăugați după semnul întrebării: "search?query={text}" și sunt, de asemenea, analizați automat de bibliotecă.
La extragerea argumentelor este important să se verifice obligativitatea parametrului prin NavType.isNullableAllowed și să se furnizeze valori implicite prin NavArgument defaultValue. Dacă un parametru obligatoriu lipsește, Navigation Compose generează o excepție IllegalArgumentException, prevenind bug-urile invizibile cu rute incorecte.
| Tipul argumentului | NavType | Exemplu în rută |
|---|---|---|
| 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}" |
Pentru transmiterea obiectelor complexe, se recomandă utilizarea NavType.ParcelableType sau NavType.SerializableType. Cu toate acestea, Google sfătuiește să se minimizeze dimensiunea datelor transmise — este mai bine să se transmită un identificator și să se încarce obiectul după identificator în interiorul ecranului. Aceasta previne problemele cu date serializate mari și simplifică gestionarea schimbărilor de configurare.
data class Profile(val id: Int, val name: String) : Parcelable
// Navighează cu date minime
navController.navigate("profile/42")
// Preia argumentele pe ecran
composable(
route = "profile/{userId}",
arguments = listOf(
NavArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
ProfileDetailScreen(userId = userId)
}
În aplicațiile reale este adesea necesară organizarea unor grafuri de navigare încuibite — de exemplu, o stivă separată de ecrane în interiorul unei file BottomNavigation. composable() suportă încuibarea prin mecanismul NavHost-urilor încuibite: în interiorul unui ecran composable se poate declara un NavHost propriu cu o stivă independentă de rute.
Fiecare NavHost încubit are propriul său NavController și back stack. Aceasta înseamnă că navigarea în interiorul unei file nu afectează navigarea în alte file — utilizatorul poate comuta liber între file fără a pierde istoricul navigărilor în fiecare dintre ele. O astfel de arhitectură se numește Scoped Navigation și este recomandată de Google pentru aplicații cu navigare complexă pe mai multe niveluri.
La implementarea navigării încuibite, este important să se gestioneze corect starea NavController: fiecare NavHost încubit trebuie să-și stocheze propriul rememberNavController în scope-ul funcției composable. Conform datelor Android Developer Summit 2024, peste 40% din aplicațiile Jetpack Compose cu trei sau mai multe file utilizează arhitectura NavHost-urilor încuibite pentru izolarea navigării între module.
// NavHost principal cu file
composable("tabs") {
MainTabsScreen { tab ->
when (tab) {
Tab.Home -> HomeNavGraph()
Tab.Search -> SearchNavGraph()
}
}
}
// Graf încubit în fila Home
@Composable
fun HomeNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_feed"
) {
composable("home_feed") { FeedScreen() }
composable("home_detail/{postId}") { PostDetailScreen() }
}
}
Înainte de Jetpack Compose, metoda standard de navigare în Android folosea Intent și FragmentManager. Intent — este un mesaj de sistem care lansează o nouă Activity, ceea ce implică recrearea întregului arbore View. Spre deosebire de aceasta, composable() funcționează în interiorul unei singure Activity și pur și simplu înlocuiește o parte din arborele Compose, ceea ce este semnificativ mai rapid și mai eficient din punct de vedere al memoriei.
Principalele diferențe între composable() și navigarea bazată pe Intent:
| Caracteristica | composable() | Intent / Fragment |
|---|---|---|
| Arhitectura | Single Activity, arbore Compose | Multi Activity, stive Fragment |
| Transmiterea datelor | parametri cale/query, ViewModel partajat | Intent extras, Bundle, SharedPreferences |
| Linkuri profunde | Suport încorporat navDeepLink | intent-filter în manifest |
| Stiva înapoi | Gestionare automată popBackStack | FragmentManager.popBackStack() |
| Timpul de comutare | 5–15 ms (în interiorul procesului) | 50–200 ms (cu recreere) |
Trecerea de la Intent la composable() — nu este doar o schimbare de API, ci o schimbare de paradigmă arhitecturală. În loc să se indice explicit care Activity trebuie deschisă, dezvoltatorul descrie declarativ toate rutele posibile într-un singur loc, ceea ce îmbunătățește lizibilitatea codului și simplifică testarea navigării. Conform datelor Google I/O 2024, Jetpack Compose cu Navigation Compose reduce cantitatea de cod pentru navigare cu 40–60% în comparație cu FragmentManager.
Una dintre cele mai frecvente erori — recrearea NavController la recompoziție. Dacă NavController este creat prin rememberNavController() la nivelul composable-ului părinte care poate fi recreat la schimbarea stării, navigarea se strică — istoricul navigărilor se pierde. Soluția corectă este ridicarea NavController la nivelul unui composable stabil, de exemplu, la nivelul Activity sau al composable-ului rădăcină al aplicației.
A doua problemă frecventă — recompoziția infinită la navigare. Aceasta apare atunci când apelul navController.navigate() este plasat direct în corpul funcției composable. Deoarece navigarea modifică starea NavHost, aceasta declanșează o recompoziție care apelează din nou navigate(), creând un ciclu. Toate apelurile de navigare trebuie să fie înfășurate în lambda-handlere (onClick, onButtonPressed), nu executate în compoziție.
A treia eroare — gestionarea incorectă a back stack-ului la utilizarea BottomNavigation. Navigarea simplă prin navigate() la fiecare comutare a filei adaugă o nouă intrare în stivă, în loc să revină la cea existentă. Pentru BottomNavigation trebuie utilizat navController.navigate() cu restoreState = true și launchSingleTop = true, ceea ce asigură restaurarea corectă a stării la comutarea între file.
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
Întrebări frecvente
composable() — nu este o adnotare, ci o funcție de extensie a NavHost care leagă o rută de UI. O funcție obișnuită @Composable descrie pur și simplu aspectul, iar composable() înregistrează acest aspect în graful de navigare cu ruta specificată, făcându-l disponibil pentru navigare prin NavController.
Se recomandă transmiterea doar a identificatorului (ID) prin parametrul de cale, iar obiectul să fie încărcat pe ecran după ID printr-un repository sau ViewModel. Dacă obiectul trebuie totuși transmis, utilizați NavType.ParcelableType, dar evitați transmiterea obiectelor mai mari de 1 KB — acest lucru poate duce la TransactionTooLargeException.
Rotirea ecranului provoacă o schimbare de configurare care, în mod implicit, recrează Activity. Pentru a păstra starea ecranelor composable, utilizați rememberSaveable pentru date simple sau ViewModel cu scope-ul ecranului respectiv. Navigation Compose restaurează back stack-ul după recreare, dar starea din interiorul funcțiilor composable() se resetează fără rememberSaveable.
Nu, composable() — este o funcție de extensie a NavGraphBuilder care este disponibilă doar în interiorul blocului NavHost. Pentru înlocuirea simplă a unei părți de UI fără navigare, utilizați randare condiționată (when, if) sau AnimatedContent. composable() este destinat tocmai pentru rutare cu suport pentru back stack și linkuri profunde.
Utilizați SavedStateHandle în interiorul ViewModel: la prima navigare, handle.get("initialized") va returna null, la revenire — valoarea salvată. Alternativ, analizați poziția curentă în back stack prin navController.previousBackStackEntry — dacă este null, înseamnă că este primul ecran în stiva de navigare.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și