NavController è il componente centrale della libreria Navigation Compose che gestisce lo stack di navigazione e lo stato del back stack nelle applicazioni Android. Tramite NavController vengono eseguite transizioni tra schermate, ritorno a pagine precedenti e trasferimento di dati tra rotte. Secondo Android Developers (2025), NavController è un elemento obbligatorio di qualsiasi applicazione Compose con più di una schermata. Il controller viene creato tramite rememberNavController(), passato a NavHost e disponibile per chiamare navigate() da qualsiasi punto della composizione. Il supporto integrato di SavedStateHandle salva automaticamente lo stato di ViewModel durante la riconfigurazione.
Punti Chiave
NavController è una classe della libreria Navigation Compose che implementa il controller di navigazione per le applicazioni Compose. NavController gestisce lo stack NavBackStackEntry, dove ogni voce contiene la rotta, gli argomenti e lo stato della schermata. Il controller supporta le operazioni di navigazione di base: transizione, ritorno, sostituzione e pulizia.
A differenza del sistema View, dove la navigazione avveniva tramite FragmentManager o Intent, NavController funziona esclusivamente nel contesto Compose. Il back stack viene memorizzato come grafo NavDestination invece di uno stack di Fragment. Ciò elimina l'overhead di creazione e distruzione dei Fragment e semplifica i test — NavController può essere mockato tramite TestNavHostController.
NavController è strettamente legato a NavHost — un contenitore che visualizza la schermata corrente dal grafo. Senza NavHost, NavController non può visualizzare funzioni composable ma mantiene la capacità di gestire lo stack. In un'architettura tipica, NavController viene creato a livello di Activity o composable principale e passato verso il basso nell'albero di composizione tramite parametri.
Secondo Google, NavController ha attraversato diverse versioni principali. La versione 2.8.0 ha aggiunto Type-Safe Navigation, la versione 2.9.0 ha aggiunto il supporto per predictive back gesture (Android 14+). Il controller è compatibile con Material3 Scaffold e BottomNavigation. Per progetti multimodulo, NavController viene passato tramite DI (Hilt/Koin) o parametri del costruttore.
NavController viene creato tramite la funzione composable rememberNavController(). La funzione restituisce un'istanza di NavHostController (una sottoclasse di NavController) legata al ciclo di vita del composable corrente. All'uscita dalla composizione, il controller viene pulito. Per preservare il controller durante la riconfigurazione, utilizzare rememberSaveable o ViewModel.
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
composable("details") { DetailsScreen(navController) }
}
}
La configurazione di NavController include: NavHostController (principale), TestNavHostController (test) e ScopedNavController (figlio per grafi annidati). Per BottomNavigation, NavController deve essere unico per l'intera applicazione — creare un nuovo controller in ogni scheda comporterà la perdita dello stack. Per passare il controller a schermate annidate, utilizzare un parametro di funzione invece di CompositionLocalProvider per mantenere la leggibilità.
Per testare la navigazione, utilizzare TestNavHostController con compose-test-rule. Il controller consente di impostare la rotta iniziale e verificare che navigate() abbia attivato la transizione prevista. Testare NavController non richiede un emulatore — funziona con i matcher Semantics di Compose Test.
Il metodo navigate(route: String) è il meccanismo principale di navigazione in NavController. Accetta una stringa di rotta, NavOptions opzionali e Navigator.Extras. NavOptions controllano il comportamento della transizione: launchSingleTop (non duplicare la rotta nello stack), popUpTo (pulire lo stack fino a una rotta), restoreState (ripristinare lo stato precedente).
NavOptions vengono impostati tramite sintassi builder: NavOptionsBuilder. Parametri principali: popUpTo (rotta + inclusive/saveState), launchSingleTop (Boolean, true — non creare duplicati), restoreState (ripristinare lo stato al ritorno). Senza popUpTo, ogni navigate() aggiunge una voce allo stack, portando all'accumulo del back stack e a un comportamento errato del pulsante Back.
navController.navigate("profile/42") {
popUpTo("main") { saveState = true }
launchSingleTop = true
restoreState = true
}
Navigator.Extras consente di passare dati aggiuntivi non facenti parte della rotta: elementi condivisi per animazioni, flag Intent, bundle Pac-Man. Gli Extras sono usati raramente — principalmente per l'integrazione con Accompanist Animation o Navigator personalizzati. Per la maggior parte degli scenari, una stringa di rotta e NavOptions sono sufficienti.
popBackStack() è il metodo per tornare alla schermata precedente. Senza argomenti, rimuove la voce in cima allo stack e restituisce true se la rimozione è riuscita. Se lo stack è vuoto, il metodo restituisce false e l'Activity si chiude (simile a super.onBackPressed()).
La versione overloaded popBackStack(route: String, inclusive: Boolean) rimuove tutte le voci fino alla rotta specificata. Se inclusive = true, anche la rotta specificata viene rimossa. Il metodo restituisce Boolean — true se le voci sono state trovate e rimosse. La versione con inclusive è utile per scenari di “uscita alla schermata radice” dopo autorizzazione o completamento dell'ordine.
| Metodo | Descrizione | Esempio |
|---|---|---|
| popBackStack() | Ritorno di una schermata indietro | navController.popBackStack() |
| popBackStack(route, false) | Pulire fino a route (route rimane) | popBackStack(“home”, false) |
| popBackStack(route, true) | Pulire fino a e incluso route | popBackStack(“home”, true) |
| navigate(route) { popUpTo(route) { inclusive = true } } | Navigare con pulizia completa | navigate(“login”) { popUpTo(0) { inclusive = true } } |
Per gestire il pulsante Back di sistema (hardware back button), utilizzare BackHandler di Compose. BackHandler accetta enabled e onBack — un callback invocato alla pressione. Per Android 14+, viene utilizzato PredictiveBackGesture, integrato tramite NavController dalla versione 2.9.0. Predictive back aggiunge un'animazione di anteprima del ritorno.
SavedStateHandle è un meccanismo per preservare lo stato di ViewModel durante la navigazione e la riconfigurazione. NavController fornisce automaticamente SavedStateHandle per ogni NavBackStackEntry. Tramite SavedStateHandle, ViewModel memorizza lo stato della schermata e lo ripristina al ritorno (restoreState = true).
In Navigation Compose, SavedStateHandle viene utilizzato insieme a ViewModel: ViewModel viene inizializzato tramite SavedStateHandle, che viene passato da backStackEntry. Quando si naviga verso un'altra schermata e si ritorna (con restoreState), ViewModel riceve lo stato salvato invece di essere creato nuovamente. Questo è critico per schermate con input di dati, filtri o scorrimento.
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userId: String = savedStateHandle.get<String>("userId") ?: ""
var searchQuery by savedStateHandle.getStateFlow("search", "")
.collectAsState()
}
SavedStateHandle supporta tipi primitivi, String, Bundle e Parcelable. Per oggetti complessi, salvare solo gli ID e caricare i dati completi dal repository. Il limite di SavedStateHandle è di circa 1 MB, superarlo causa TransactionTooLargeException. Per grandi volumi, utilizzare Room o DataStore invece di salvare nell'handle.
Importante: SavedStateHandle preserva lo stato solo quando restoreState = true viene utilizzato in NavOptions. Se restoreState non è specificato, ViewModel viene creato nuovamente con valori predefiniti al ritorno. Per il cambio di BottomNavigation con restoreState, NavController preserva lo stato di ogni scheda e lo ripristina alla riselezione.
currentBackStackEntryAsState() è una funzione che restituisce State<NavBackStackEntry?>, che si aggiorna a ogni cambiamento della rotta corrente. Questo è il meccanismo principale per la sincronizzazione dell'UI con la navigazione: BottomNavigation evidenzia l'elemento attivo, Toolbar aggiorna il titolo, Drawer si chiude alla transizione.
La funzione funziona tramite snapshotFlow e collectAsState: quando il back stack cambia, Compose ricompone gli elementi sottoscritti. Importante: currentBackStackEntryAsState() si aggiorna solo dopo il completamento dell'animazione di transizione. Per aggiornamenti immediati, utilizzare currentDestination, che cambia sincronicamente con navigate() ma non supporta lo stato.
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Text(
text = when (currentRoute) {
"home" -> "Home"
"profile" -> "Profile"
else -> ""
}
)
Per accedere agli argomenti della rotta corrente, utilizzare navBackStackEntry?.arguments. Questo è conveniente in BottomNavigation: selectedItem viene calcolato in base a currentRoute. Per il debug della navigazione, utilizzare NavController.addOnDestinationChangedListener() che registra ogni transizione. In produzione, evitare sottoscrizioni all'interno di un gran numero di composable — creare una singola fonte in ViewModel e passare State all'UI.
Domande Frequenti
Tecnicamente sì, ma non consigliato. Un singolo NavController garantisce un back stack coerente e semplifica il debug. Molteplici controller sono giustificati solo per grafi annidati con navigazione separata (ad esempio, modal bottom sheet con il proprio stack).
Passare NavController a ViewModel tramite costruttore o DI. Tuttavia, è meglio passare solo funzioni callback (onNavigate, onBack) invece di NavController stesso — questo semplifica i test. Per gli eventi, utilizzare Channel<NavEvent> in ViewModel e raccogliere nell'UI.
Il problema è il ciclo di vita: se NavController non è ancora inizializzato (NavHost non costruito), navigate() viene ignorato. Utilizzare LaunchedEffect per chiamare la navigazione dopo il caricamento dei dati, non all'interno di una coroutine con ciclo di vita arbitrario.
Chiamare navController.navigate(“target”) { popUpTo(0) { inclusive = true } }. Il parametro popUpTo(0) pulisce lo stack completamente, inclusive = true rimuove anche la voce iniziale. Il flag launchSingleTop = true previene rotte duplicate.
NavHostController è una sottoclasse di NavController con metodi aggiuntivi per NavHost (ad esempio, setOnBackStackChangedListener). NavController è la classe base che può essere utilizzata al di fuori di NavHost per la gestione programmatica dello stack. Nella maggior parte dei casi, viene utilizzato NavHostController.
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