composable() è una funzione della libreria Navigation Compose che registra una schermata in NavHost e collega una route URL al layout Compose. Quando la navigazione si sposta su una route definita, Jetpack Compose chiama la funzione composable corrispondente e la visualizza come schermata corrente. A differenza di FragmentManager o della navigazione basata su Intent, composable() funziona a livello di una singola Activity ed è completamente gestito tramite Kotlin DSL. Secondo Android Developers (2025), più del 73% delle applicazioni Android moderne costruite con Jetpack Compose utilizzano Navigation Compose per organizzare le transizioni tra schermate.
Punti chiave
composable() è una funzione di estensione dell'oggetto NavHost. Kotlin DSL permette di chiamarla all'interno del blocco NavHost per descrivere in modo dichiarativo tutte le schermate dell'applicazione. Ogni chiamata crea una voce nel grafo di navigazione, collegando una route stringa a una funzione composable. Quando un utente naviga verso una route specifica, NavHost mostra il composable corrispondente come schermata corrente, nascondendo la precedente.
La libreria Navigation Compose è stata presentata da Google nel 2021 come alternativa alla navigazione basata su Fragment per Jetpack Compose. Il vantaggio principale è la piena compatibilità con il paradigma Compose: composable() funziona nello stesso ciclo di vita degli altri componenti Compose, senza bisogno di FragmentManager o transazioni. Questo elimina una classe di bug legati alla mancata corrispondenza dei cicli di vita tra Fragment e Compose.
Ogni composable() accetta una route stringa e una funzione lambda che riceve un oggetto NavBackStackEntry e restituisce UI Composable. All'interno della lambda, è possibile accedere a NavController tramite navController dall'ambito, consentendo la navigazione verso altre schermate. Questa architettura rende la navigazione esplicita e prevedibile.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile")
}
)
}
composable("profile") {
ProfileScreen(
onBack = { navController.popBackStack() }
)
}
}
}
Ogni chiamata a composable() crea un vertice con un identificatore di route unico nel grafo interno di NavHost. Quando NavController esegue navigate(), la libreria confronta la route richiesta con tutti i vertici composable registrati e trova una corrispondenza. Dopo la corrispondenza, viene creato un NavBackStackEntry, posizionato nello stack di navigazione e inizia la composizione dell'UI.
L'implementazione interna di composable() utilizza un meccanismo di inizializzazione pigra: la composizione della schermata avviene solo al primo passaggio verso quella route. Ciò significa che le schermate verso cui l'utente non ha mai navigato non occupano memoria e non eseguono alcun codice. Questo approccio migliora significativamente le prestazioni nelle applicazioni con molte schermate.
Il parametro key in composable() permette di gestire la ricreazione della schermata. Per impostazione predefinita, composable non viene ricreato in caso di navigazione ripetuta verso la stessa route — NavHost utilizza la voce esistente nello stack. Tuttavia, se viene passata una key e questa cambia, NavHost creerà una nuova istanza della funzione composable. Questo è utile per schermate con dati dinamici dove è necessario forzare l'aggiornamento dello stato alla riapertura.
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() supporta un sistema flessibile di argomenti tramite il parametro arguments. Ogni argomento è descritto da un oggetto NavArgument che definisce il tipo, il valore predefinito e l'obbligatorietà. Gli argomenti vengono passati nella route come parametri di percorso (tramite parentesi graffe) o parametri di query (tramite punto interrogativo).
I parametri di percorso sono specificati direttamente nel modello di route: "profile/{userId}". Durante la navigazione verso "profile/42", NavHost estrae automaticamente il valore 42 e lo rende accessibile tramite backStackEntry.arguments. I parametri di query vengono aggiunti dopo il punto interrogativo: "search?query={text}" e sono anch'essi analizzati automaticamente dalla libreria.
Nell'estrarre gli argomenti, è importante verificare l'obbligatorietà del parametro tramite NavType.isNullableAllowed e fornire valori predefiniti tramite NavArgument defaultValue. Se un parametro obbligatorio è mancante, Navigation Compose lancia una IllegalArgumentException, prevenendo bug sottili con route errate.
| Tipo argomento | NavType | Esempio in route |
|---|---|---|
| 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}" |
Per passare oggetti complessi, si consiglia di utilizzare NavType.ParcelableType o NavType.SerializableType. Tuttavia, Google consiglia di minimizzare la dimensione dei dati trasferiti — è meglio passare un identificatore e caricare l'oggetto per ID all'interno della schermata. Questo previene problemi con grandi dati serializzati e semplifica la gestione dei cambiamenti di configurazione.
data class Profile(val id: Int, val name: String) : Parcelable
// Navigare con dati minimi
navController.navigate("profile/42")
// Recuperare argomenti sullo schermo
composable(
route = "profile/{userId}",
arguments = listOf(
NavArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
ProfileDetailScreen(userId = userId)
}
Nelle applicazioni reali, è spesso necessario organizzare grafi di navigazione annidati — ad esempio, uno stack di schermate separato all'interno di una scheda BottomNavigation. composable() supporta l'annidamento tramite NavHost annidati: all'interno di una schermata composable, è possibile dichiarare il proprio NavHost con uno stack di route indipendente.
Ogni NavHost annidato ha il proprio NavController e back stack. Ciò significa che la navigazione all'interno di una scheda non influisce sulla navigazione in altre schede — l'utente può passare liberamente da una scheda all'altra senza perdere la cronologia di navigazione all'interno di ciascuna. Questa architettura è chiamata Scoped Navigation ed è raccomandata da Google per applicazioni con navigazione complessa a più livelli.
Nell'implementare la navigazione annidata, è importante gestire correttamente lo stato di NavController: ogni NavHost annidato dovrebbe memorizzare il proprio rememberNavController all'interno dell'ambito della funzione composable. Secondo Android Developer Summit 2024, più del 40% delle applicazioni Jetpack Compose con tre o più schede utilizzano l'architettura NavHost annidata per isolare la navigazione tra i moduli.
// NavHost principale con schede
composable("tabs") {
MainTabsScreen { tab ->
when (tab) {
Tab.Home -> HomeNavGraph()
Tab.Search -> SearchNavGraph()
}
}
}
// Grafo annidato nella scheda Home
@Composable
fun HomeNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_feed"
) {
composable("home_feed") { FeedScreen() }
composable("home_detail/{postId}") { PostDetailScreen() }
}
}
Prima di Jetpack Compose, il metodo standard di navigazione in Android utilizzava Intent e FragmentManager. Intent è un messaggio di sistema che avvia una nuova Activity, il che implica la ricreazione dell'intero albero View. Al contrario, composable() funziona all'interno di una singola Activity e sostituisce semplicemente una parte dell'albero Compose, risultando significativamente più veloce ed efficiente in termini di memoria.
Principali differenze tra composable() e la navigazione basata su Intent:
| Caratteristica | composable() | Intent / Fragment |
|---|---|---|
| Architettura | Single Activity, albero Compose | Multi Activity, stack Fragment |
| Trasferimento dati | parametri path/query, ViewModel condiviso | Intent extras, Bundle, SharedPreferences |
| Link profondi | Supporto integrato navDeepLink | intent-filter nel manifest |
| Back stack | Gestione automatica popBackStack | FragmentManager.popBackStack() |
| Tempo di cambio | 5–15 ms (in-process) | 50–200 ms (con ricreazione) |
Passare da Intent a composable() non è solo una sostituzione di API, ma un cambiamento di paradigma architetturale. Invece di specificare esplicitamente quale Activity deve aprirsi, lo sviluppatore descrive dichiarativamente tutte le possibili route in un unico punto, migliorando la leggibilità del codice e semplificando il test della navigazione. Secondo Google I/O 2024, Jetpack Compose con Navigation Compose riduce il codice di navigazione del 40–60% rispetto a FragmentManager.
Uno degli errori più comuni è la ricreazione del NavController durante la ricomposizione. Se NavController viene creato tramite rememberNavController() a livello del composable genitore, che può essere ricreato al cambiamento dello stato, la navigazione si rompe — la cronologia viene persa. La soluzione corretta è sollevare NavController a un livello composable stabile, come il livello dell'Activity o il composable radice dell'applicazione.
Il secondo problema comune è la ricomposizione infinita durante la navigazione. Questo accade quando navController.navigate() viene inserito direttamente nel corpo di una funzione composable. Poiché la navigazione modifica lo stato di NavHost, innesca una ricomposizione, che chiama nuovamente navigate(), creando un ciclo. Tutte le chiamate di navigazione devono essere racchiuse in lambda handler (onClick, onButtonPressed), non eseguite nella composizione.
Il terzo errore è la gestione errata del back stack quando si utilizza BottomNavigation. La navigazione semplice tramite navigate() a ogni cambio di scheda aggiunge una nuova voce allo stack invece di tornare a quella esistente. Per BottomNavigation, si dovrebbe utilizzare navController.navigate() con restoreState = true e launchSingleTop = true, che garantisce il corretto ripristino dello stato al cambio di scheda.
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
Domande frequenti
composable() non è un'annotazione, ma una funzione di estensione di NavHost che lega una route all'UI. Una normale funzione @Composable descrive semplicemente il layout, mentre composable() registra quel layout nel grafo di navigazione con una route specificata, rendendolo accessibile per la navigazione tramite NavController.
Si consiglia di passare solo un identificatore (ID) tramite il parametro di percorso, e caricare l'oggetto sulla schermata per ID tramite un repository o ViewModel. Se è comunque necessario passare l'oggetto, utilizzare NavType.ParcelableType, ma evitare di passare oggetti più grandi di 1 KB — ciò può portare a TransactionTooLargeException.
La rotazione dello schermo provoca un cambiamento di configurazione, che per impostazione predefinita ricrea l'Activity. Per preservare lo stato delle schermate composable, utilizzare rememberSaveable per dati semplici o ViewModel con l'ambito di quella schermata. Navigation Compose ripristina il back stack dopo la ricreazione, ma lo stato all'interno delle funzioni composable() viene resettato senza rememberSaveable.
No, composable() è una funzione di estensione di NavGraphBuilder, disponibile solo all'interno del blocco NavHost. Per una semplice sostituzione dell'UI senza navigazione, utilizzare il rendering condizionale (when, if) o AnimatedContent. composable() è progettato specificamente per il routing con supporto di back stack e link profondi.
Utilizzare SavedStateHandle all'interno del ViewModel: al primo passaggio, handle.get("initialized") restituisce null; al passaggio indietro, restituisce il valore salvato. In alternativa, analizzare la posizione corrente nel back stack tramite navController.previousBackStackEntry — se è null, questa è la prima schermata nello stack di navigazione.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche