Το NavController είναι το κεντρικό συστατικό της βιβλιοθήκης Navigation Compose που διαχειρίζεται τη στοίβα πλοήγησης και την κατάσταση back stack σε εφαρμογές Android. Μέσω του NavController εκτελούνται μεταβάσεις μεταξύ οθονών, επιστροφή σε προηγούμενες σελίδες και μεταφορά δεδομένων μεταξύ διαδρομών. Σύμφωνα με το Android Developers (2025), το NavController είναι υποχρεωτικό στοιχείο κάθε εφαρμογής Compose με περισσότερες από μία οθόνη. Ο ελεγκτής δημιουργείται μέσω rememberNavController(), μεταβίβεται στο NavHost και είναι διαθέσιμος για κλήση navigate() από οποιοδήποτε σημείο της σύνθεσης. Η ενσωματωμένη υποστήριξη SavedStateHandle αποθηκεύει αυτόματα την κατάσταση ViewModel κατά τη ρευθμίση.
Βασικά σημεία
NavController — είναι μια κλάση από τη βιβλιοθήκη Navigation Compose που υλοποιεί τον ελεγκτή πλοήγησης για εφαρμογές Compose. Το NavController διαχειρίζεται τη στοίβα NavBackStackEntry, όπου κάθε εισαγωγή περιέχει διαδρομή, ορίσματα και κατάσταση οθόνης. Ο ελεγκτής υποστηρίζει βασικές λειτουργίες πλοήγησης: μετάβαση, επιστροφή, αντικατάσταση και εκκαθάριση.
Σε αντίθεση με το σύστημα View, όπου η πλοήγηση γινόταν μέσω FragmentManager ή Intent, το NavController λειτουργεί αποκλειστικά στο περιβάλλον Compose. Το back stack αποθηκεύεται ως γράφημα NavDestination, όχι ως στοίβα Fragment. Αυτό εξαλείφει την επιβάρυνση δημιουργίας και καταστροφής Fragment, και απλοποιεί τη δοκιμή — το NavController μπορεί να προσομοιωθεί μέσω TestNavHostController.
Το NavController συνδέεται στενά με το NavHost — το δοχείο που αποδίδει την τρέχουσα οθόνη από το γράφημα. Χωρίς NavHost, το NavController δεν μπορεί να εμφανίσει συναρτήσεις composable, αλλά διατηρεί την ικανότητα διαχείρισης της στοίβας. Σε τυπική αρχιτεκτονική, το NavController δημιουργείται σε επίπεδο Activity ή κύριου composable και μεταβίβεται προς τα κάτω στο δένδρο σύνθεσης μέσω παραμέτρων.
Σύμφωνα με τη Google, το NavController έχει περάσει από αρκετές κύριες εκδόσεις. Η έκδοση 2.8.0 πρόσθεσε Type-Safe Navigation, η έκδοση 2.9.0 — υποστήριξη predictive back gesture (Android 14+). Ο ελεγκτής είναι συμβατός με Material3 Scaffold και BottomNavigation. Για πολυθυρικά έργα, το NavController μεταβίβεται μέσω DI (Hilt/Koin) ή παραμέτρων κατασκευής.
Το NavController δημιουργείται μέσω της composable συνάρτησης rememberNavController(). Η συνάρτηση επιστρέφει μια εγγραφή NavHostController (απόγονος NavController) που συνδέεται με τον κύκλο ζωής του τρέχοντος composable. Κατά την έξοδο από τη σύνθεση, ο ελεγκτής εκκαθαρίζεται. Για την αποθήκευση του ελεγκτή κατά τη ρυθμίση, χρησιμοποιήστε rememberSaveable ή ViewModel.
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
composable("details") { DetailsScreen(navController) }
}
}
Η ρύθμιση NavController περιλαμβάνει: NavHostController (κύριος), TestNavHostController (δοκιμή) και ScopedNavController (θυγατρικός για εμφωλευμένα γραφήματα). Για BottomNavigation, το NavController πρέπει να είναι μοναδικό για ολόκληρη την εφαρμογή — η δημιουργία νέου ελεγκτή σε κάθε καρτέλα θα οδηγήσει σε απώλεια στοίβας. Για τη μεταβίβαση του ελεγκτή σε εμφωλευμένες οθόνες, χρησιμοποιήστε την παράμετρο συνάρτησης, όχι το CompositionLocalProvider, για να διατηρηθεί η αναγνωσιμότητα.
Για τη δοκιμή πλοήγησης, χρησιμοποιήστε TestNavHostController με compose-test-rule. Ο ελεγκτής επιτρέπει την ρύθμιση της αρχικής διαδρομής και την επαλήθευση ότι η navigate() κάλεσε την αναμενόμενη μετάβαση. Η δοκιμή NavController δεν απαιτεί εμπλαίστωτη — λειτουργεί με Semantics matchers της Compose Test.
Η μέθοδος navigate(route: String) — ο κύριος τρόπος πλοήγησης στο NavController. Δέχεται ένα string διαδρομής, προαιρετικά NavOptions και Navigator.Extras. Τα NavOptions διαχειρίζονται τη συμπεριφορά μετάβασης: launchSingleTop (μην αντιγραφήτε τη διαδρομή στη στοίβα), popUpTo (εκκαθαρίστε τη στοίβα μέχρι διαδρομή), restoreState (επαναφέρετε την προηγούμενη κατάσταση).
Τα NavOptions ορίζονται μέσω builder σύνταξης: NavOptionsBuilder. Βασικές παράμετροι: popUpTo (route + inclusive/saveState), launchSingleTop (Boolean, true — μην δημιουργείτε αντίγραφο), restoreState (επαναφέρετε κατάσταση κατά την επιστροφή). Χωρίς popUpTo, κάθε navigate() προσθέτει μια εισαγωγή στη στοίβα, που οδηγεί σε συσσώρευση back stack και λανθασμένη συμπεριφορά του κουμπιού Back.
navController.navigate("profile/42") {
popUpTo("main") { saveState = true }
launchSingleTop = true
restoreState = true
}
Το Navigator.Extras επιτρέπει τη μεταφορά προσθέτων δεδομένων που δεν αποτελούν μέρος της διαδρομής: shared element για κινηση, σημαίες Intent, Pac-Man bundle. Τα Extras χρησιμοποιούνται σπάνια — κυρίως για ενσωμάτωση με Accompanist Animation ή προσαρμοσμένο Navigator. Για τα περισσότερα σενάρια, αρκεί το string διαδρομής και τα NavOptions.
popBackStack() — μέθοδος επιστροφής στην προηγούμενη οθόνη. Χωρίς ορίσματα, αφαιρεί την κορυφαία εισαγωγή στοίβας και επιστρέφει true αν η αφαίρεση είναι επιτυχημένη. Αν η στοίβα είναι άδεια — η μέθοδος επιστρέφει false και το Activity κλείνει (παρόμοιο με super.onBackPressed()).
Η υπερφορτωμένη έκδοση popBackStack(route: String, inclusive: Boolean) αφαιρεί όλες τις εισαγωγές μέχρι την καθορισμένη διαδρομή. Αν inclusive = true — αφαιρείται και η ίδια η καθορισμένη διαδρομή. Η μέθοδος επιστρέφει Boolean — true αν βρέθηκαν και αφαιρέθηκαν εισαγωγές. Η έκδοση με inclusive είναι χρήσιμη για σενάρια „εξόδου στην αρχική οθόνη” μετά από εξουσιοδότηση ή ολοκλήρωση παραγγελίας.
| Μέθοδος | Περιγραφή | Παράδειγμα |
|---|---|---|
| popBackStack() | Επιστροφή μία οθόνη πίσω | navController.popBackStack() |
| popBackStack(route, false) | Εκκαθάριση μέχρι διαδρομή (η διαδρομή παραμένει) | popBackStack("home", false) |
| popBackStack(route, true) | Εκκαθάριση μέχρι και συμπεριλαμβανομένης της διαδρομής | popBackStack("home", true) |
| navigate(route) { popUpTo(route) { inclusive = true } } | Μετάβαση με πλήρη εκκαθάριση | navigate("login") { popUpTo(0) { inclusive = true } } |
Για το χειρισμό του συστημικού κουμπιού Back (hardware back button), χρησιμοποιήστε το BackHandler από το Compose. Το BackHandler δέχεται enabled και onBack — callback που καλείται κατά το πάτημα. Για Android 14+ χρησιμοποιείται το PredictiveBackGesture, που ενσωματώνεται μέσω NavController από την έκδοση 2.9.0. Το Predictive back προσθέτει μια προεπισκόπηση επιστροφής.
SavedStateHandle — είναι ένας μηχανισμός αποθήκευσης της κατάστασης ViewModel κατά τη ναυτική πλοήγηση και τη ρυθμίση. Το NavController παρέχει αυτόματα SavedStateHandle για κάθε NavBackStackEntry. Μέσω SavedStateHandle, το ViewModel αποθηκεύει την κατάσταση της οθόνης και την επαναφέρει κατά την επιστροφή (restoreState = true).
Στο Navigation Compose, το SavedStateHandle χρησιμοποιείται μαζί με ViewModel: το ViewModel αρχικοποιείται μέσω SavedStateHandle που μεταβίβεται από το backStackEntry. Κατά τη μετάβαση σε άλλη οθόνη και επιστροφή (με restoreState), το ViewModel λαμβάνει την αποθηκευμένη κατάσταση, δεν δημιουργείται από την αρχή. Αυτό είναι κρίσιμο για οθόνες με εισαγωγή δεδομένων, φίλτρα ή κύλιση.
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userId: String = savedStateHandle.get<String>("userId") ?: ""
var searchQuery by savedStateHandle.getStateFlow("search", "")
.collectAsState()
}
Το SavedStateHandle υποστηρίζει πρωτογενείς τύπους, String, Bundle και Parcelable. Για σύνθετα αντικείμενα, αποθηκεύστε μόνο ID και φορτώστε τα πλήρη δεδομένα από το αποθηκευτήριο. Το όριο του SavedStateHandle είναι περίπου 1 MB, η υπέρβαση προκαλεί TransactionTooLargeException. Για μεγάλους όγκους, χρησιμοποιήστε Room ή DataStore αντί για αποθήκευση στο handle.
Σημαντικό: Το SavedStateHandle αποθηκεύει κατάσταση μόνο όταν χρησιμοποιείται restoreState = true στα NavOptions. Αν δεν καθοριστεί restoreState, κατά την επιστροφή το ViewModel δημιουργείται εκ νέου με προκαθορισμένες τιμές. Για εναλλαγή BottomNavigation με restoreState, το NavController αποθηκεύει την κατάσταση κάθε καρτέλας και την επαναφέρει κατά την επαναληπτική επιλογή.
currentBackStackEntryAsState() — συνάρτηση που επιστρέφει State<NavBackStackEntry?>, που ενημερώνεται σε κάθε αλλαγή της τρέχουσας διαδρομής. Αυτός είναι ο κύριος μηχανισμός συγχρονισμού UI με την πλοήγηση: το BottomNavigation τονίζει το ενεργό στοιχείο, το Toolbar ενημερώνει τον τίτλο, το Drawer κλείνει κατά τη μετάβαση.
Η συνάρτηση λειτουργεί μέσω snapshotFlow και collectAsState: κατά την αλλαγή back stack, το Compose ανασυνθέτει τα εγγεγραμμένα στοιχεία. Σημαντικό: η currentBackStackEntryAsState() ενημερώνεται μόνο μετά την ολοκλήρωση της κινηση μετάβασης. Για άμεση ενημέρωση, χρησιμοποιήστε το currentDestination, που αλλάζει σύγχρονα με τη navigate(), αλλά δεν υποστηρίζει κατάσταση.
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Text(
text = when (currentRoute) {
"home" -> "Home"
"profile" -> "Profile"
else -> ""
}
)
Για πρόσβαση στα ορίσματα της τρέχουσας διαδρομής, χρησιμοποιήστε το navBackStackEntry?.arguments. Αυτό είναι βολικό στο BottomNavigation: το selectedItem υπολογίζεται βάσει του currentRoute. Για εκσφαλμάτωση πλοήγησης, χρησιμοποιήστε το NavController.addOnDestinationChangedListener(), που καταγράφει κάθε μετάβαση. Στην παραγωγή, αποφύγετε εγγραφές μέσα σε μεγάλο αριθμό composable — δημιουργήστε μία πηγή στο ViewModel και μεταβίστε το State στο UI.
Συχνές Ερωτήσεις
Τεχνικά ναι, αλλά δεν συνιστάται. Ένα μοναδικό NavController εξασφαλίζει συνεπή back stack και απλοποιεί την εκσφαλμάτωση. Πολλαπλοί ελεγκτές δικαιολογούνται μόνο για εμφωλευμένα γραφήματα με ξεχωριστή πλοήγηση (π.χ. modal bottom sheet με δική του στοίβα).
Μεταβίστε το NavController στο ViewModel μέσω κατασκευή ή DI. Ωστόσο, είναι καλύτερο να μεταβίβετε μόνο συναρτήσεις callback (onNavigate, onBack), όχι το ίδιο το NavController — αυτό απλοποιεί τη δοκιμή. Για συμβάντα, χρησιμοποιήστε Channel<NavEvent> στο ViewModel και συλλέξτε στο UI.
Το πρόβλημα είναι στον κύκλο ζωής: αν το NavController δεν έχει ακόμα αρχικοποιηθεί (το NavHost δεν έχει κατασκευαστεί), η navigate() αγνοείται. Χρησιμοποιήστε LaunchedEffect για να καλέσετε την πλοήγηση μετά τη φόρτωση δεδομένων, όχι μέσα σε coroutine με αυθαίρετο lifecycle.
Καλέστε navController.navigate("target") { popUpTo(0) { inclusive = true } }. Η παράμετρος popUpTo(0) εκκαθαρίζει πλήρως τη στοίβα, inclusive = true αφαιρεί και την αρχική εισαγωγή. Η σημαία launchSingleTop = true αποτρέπει την αντιγραφή της νέας διαδρομής.
NavHostController — απόγονος NavController με επιπλέον μεθόδους για το NavHost (π.χ. setOnBackStackChangedListener). NavController — η βασική κλάση που μπορεί να χρησιμοποιηθεί εκτός NavHost για προγραμματική διαχείριση στοίβας. Στις περισσότερες περιπτώσεις, χρησιμοποιείται το NavHostController.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης