composable(): cosa sono, NavHost e routing in Jetpack Compose

Autore: IT Sectr Pubblicato: 2026-06-30 Tempo di lettura: 9 min

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 per registrare una schermata in NavHost della libreria Navigation Compose.
  • Route — ogni schermata è identificata da una route stringa passata come primo argomento.
  • Parametri — composable() supporta argomenti tramite NavArgument, inclusi obbligatori e opzionali.
  • Annidamento — la navigazione annidata è supportata tramite NavHost annidati con grafi di route separati.
  • Prestazioni — composable() utilizza inizializzazione pigra: la schermata viene creata solo al primo passaggio.

Cosa è composable() in NavHost

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.

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

Come funziona composable(): chiavi e parametri

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.

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

Passare argomenti tramite composable()

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 argomentoNavTypeEsempio in route
IntNavType.IntType"item/{id}"
StringNavType.StringType"user/{name}"
BooleanNavType.BoolType"filter?enabled={value}"
FloatNavType.FloatType"map/{lat}/{lon}"
LongNavType.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.

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

Navigazione annidata con composable()

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.

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

composable() vs navigazione Intent

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:

  • Velocità — composable() cambia schermata in millisecondi senza ricreare l'Activity; Intent richiede il riavvio dell'Activity.
  • Animazioni — in Navigation Compose, le animazioni di transizione sono definite dichiarativamente tramite AnimatedNavHost, senza bisogno di overridePendingTransition.
  • Stato condiviso — composable() funziona in un ambito ViewModel condiviso, semplificando il trasferimento di dati tra schermate senza Intent extras.
Caratteristicacomposable()Intent / Fragment
ArchitetturaSingle Activity, albero ComposeMulti Activity, stack Fragment
Trasferimento datiparametri path/query, ViewModel condivisoIntent extras, Bundle, SharedPreferences
Link profondiSupporto integrato navDeepLinkintent-filter nel manifest
Back stackGestione automatica popBackStackFragmentManager.popBackStack()
Tempo di cambio5–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.

Errori comuni con composable()

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.

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

Domande frequenti

Qual è la differenza tra composable() e una normale funzione @Composable?

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.

Come passare un oggetto complesso tra schermate composable()?

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.

Perché la schermata composable() viene ricreata alla rotazione dello schermo?

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.

Si può usare composable() senza NavHost?

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.

Come distinguere il primo passaggio da un passaggio indietro in composable()?

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

  • composable() è una funzione di registrazione di schermata in NavHost, il modo principale per organizzare la navigazione in Jetpack Compose.
  • Route — ogni schermata è identificata da una stringa di route con parametri di percorso e query opzionali.
  • Argomenti — vengono passati tramite NavArgument con supporto per tipi primitivi, Parcelable e Serializable.
  • Annidamento — composable() supporta NavHost annidati per organizzare la navigazione modulare con stack indipendenti.
  • Prestazioni — l'inizializzazione pigra delle schermate risparmia memoria, la velocità di cambio schermata è di 5–15 ms.
  • Errori — problemi principali: ricreazione del NavController, ricomposizione infinita con navigate() nel corpo del composable, gestione errata di BottomNavigation.
  • Migrazione — passare da FragmentManager a composable() riduce il volume di codice di navigazione del 40–60% ed elimina una classe di bug legati al ciclo di vita di Fragment.

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.

Discuti il progetto

Leggi anche