composable() — είναι μια συνάρτηση της βιβλιοθήκης Navigation Compose που καταχωρεί μια οθόνη στο NavHost και συνδέει μια διαδρομή URL με τη διάταξη Compose. Όταν η πλοήγηση μεταβαίνει σε μια καθορισμένη διαδρομή, το Jetpack Compose καλεί την αντίστοιχη composable συνάρτηση και την εμφανίζει ως τρέχουσα οθόνη. Σε αντίθεση με το FragmentManager ή την πλοήγηση βάσει Intent, το composable() λειτουργεί σε επίπεδο μιας Activity και διαχειρίζεται πλήρως μέσω Kotlin DSL. Σύμφωνα με στοιχεία των Android Developers (2025), πάνω από το 73% των σύγχρονων εφαρμογών Android που βασίζονται στο Jetpack Compose χρησιμοποιούν ακριβώς το Navigation Compose για την οργάνωση της εναλλαγής οθονών.
Κύρια σημεία
composable() — είναι μια συνάρτηση επέκτασης (extension function) του αντικειμένου NavHost. Το Kotlin DSL επιτρέπει την κλήση της μέσα στο μπλοκ NavHost για δηλωτική περιγραφή όλων των οθονών της εφαρμογής. Κάθε κλήση δημιουργεί μια εγγραφή στο γράφημα πλοήγησης, συνδέοντας μια συμβολοσειρά διαδρομής με μια composable συνάρτηση. Όταν ο χρήστης πλοηγείται σε μια συγκεκριμένη διαδρομή, το NavHost εμφανίζει το αντίστοιχο composable ως τρέχουσα οθόνη, κρύβοντας την προηγούμενη.
Η βιβλιοθήκη Navigation Compose παρουσιάστηκε από την Google το 2021 ως εναλλακτική λύση στην πλοήγηση βάσει Fragment για το Jetpack Compose. Το κύριο πλεονέκτημα — πλήρης συμβατότητα με το παράδειγμα Compose: το composable() λειτουργεί στον ίδιο κύκλο ζωής με τα άλλα στοιχεία Compose, χωρίς την ανάγκη για FragmentManager ή συναλλαγές. Αυτό εξαλείφει μια κατηγορία σφαλμάτων που σχετίζονται με την ασυμφωνία του κύκλου ζωής Fragment και Compose.
Κάθε composable() δέχεται μια συμβολοσειρά διαδρομής (route) και μια lambda συνάρτηση που λαμβάνει ένα αντικείμενο NavBackStackEntry και επιστρέφει ένα Composable UI. Μέσα στη lambda, μπορεί κανείς να έχει πρόσβαση στο NavController μέσω της κλήσης navController από το πεδίο εφαρμογής, που επιτρέπει την οργάνωση μεταβάσεων σε άλλες οθόνες. Αυτή η αρχιτεκτονική καθιστά την πλοήγηση ρητή και προβλέψιμη.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile")
}
)
}
composable("profile") {
ProfileScreen(
onBack = { navController.popBackStack() }
)
}
}
}
Κάθε κλήση composable() δημιουργεί στο εσωτερικό γράφημα του NavHost έναν κόμβο με μοναδικό αναγνωριστικό διαδρομής. Όταν το NavController εκτελεί το navigate(), η βιβλιοθήκη συγκρίνει τη ζητούμενη διαδρομή με όλους τους καταχωρημένους composable κόμβους και βρίσκει τον αντίστοιχο. Μετά την αντιστοίχιση, δημιουργείται ένα NavBackStackEntry που τοποθετείται στη στοίβα πλοήγησης και ξεκινά η σύνθεση UI.
Η εσωτερική υλοποίηση του composable() χρησιμοποιεί τον μηχανισμό τεμπέλικης αρχικοποίησης: η σύνθεση της οθόνης γίνεται μόνο τη στιγμή της πρώτης μετάβασης σε αυτή τη διαδρομή. Αυτό σημαίνει ότι οι οθόνες στις οποίες ο χρήστης δεν έχει πλοηγηθεί ποτέ, δεν καταλαμβάνουν μνήμη και δεν εκτελούν κανέναν κώδικα. Αυτή η προσέγγιση βελτιώνει σημαντικά την απόδοση εφαρμογών με μεγάλο αριθμό οθονών.
Η παράμετρος key στο composable() επιτρέπει τη διαχείριση της αναδημιουργίας της οθόνης. Από προεπιλογή, το composable δεν αναδημιουργείται κατά την επαναλαμβανόμενη μετάβαση στην ίδια διαδρομή — το NavHost χρησιμοποιεί την υπάρχουσα εγγραφή στη στοίβα. Ωστόσο, αν το key μεταβιβαστεί και αλλάξει, το NavHost δημιουργεί ένα νέο στιγμιότυπο της composable συνάρτησης. Αυτό είναι χρήσιμο για οθόνες με δυναμικά δεδομένα όπου χρειάζεται αναγκαστική ενημέρωση της κατάστασης κατά το άνοιγμα.
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() υποστηρίζει ένα ευέλικτο σύστημα ορισμάτων μέσω της παραμέτρου arguments. Κάθε όρισμα περιγράφεται από ένα αντικείμενο NavArgument που καθορίζει τον τύπο, την προεπιλεγμένη τιμή και την υποχρεωτικότητα. Τα ορίσματα μεταβιβάζονται στη διαδρομή ως παράμετροι διαδρομής (μέσω αγκυλών) ή παράμετροι ερωτήματος (μέσω ερωτηματικού).
Οι παράμετροι διαδρομής υποδεικνύονται απευθείας στο πρότυπο διαδρομής: "profile/{userId}". Κατά τη μετάβαση στη διαδρομή "profile/42", το NavHost εξάγει αυτόματα την τιμή 42 και την καθιστά διαθέσιμη μέσω του backStackEntry.arguments. Οι παράμετροι ερωτήματος προστίθενται μετά το ερωτηματικό: "search?query={text}" και επίσης αναλύονται αυτόματα από τη βιβλιοθήκη.
Κατά την εξαγωγή ορισμάτων, είναι σημαντικό να ελέγχεται η υποχρεωτικότητα της παραμέτρου μέσω NavType.isNullableAllowed και να παρέχονται προεπιλεγμένες τιμές μέσω NavArgument defaultValue. Εάν λείπει μια υποχρεωτική παράμετρος, το Navigation Compose δημιουργεί μια εξαίρεση IllegalArgumentException, αποτρέποντας αόρατα σφάλματα με εσφαλμένες διαδρομές.
| Τύπος ορίσματος | NavType | Παράδειγμα στη διαδρομή |
|---|---|---|
| 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}" |
Για τη μεταβίβαση σύνθετων αντικειμένων, συνιστάται η χρήση NavType.ParcelableType ή NavType.SerializableType. Ωστόσο, η Google συμβουλεύει να ελαχιστοποιείται το μέγεθος των μεταβιβαζόμενων δεδομένων — είναι καλύτερο να μεταβιβάζεται ένα αναγνωριστικό και να φορτώνεται το αντικείμενο με βάση το αναγνωριστικό μέσα στην οθόνη. Αυτό αποτρέπει προβλήματα με μεγάλα σειριοποιημένα δεδομένα και απλοποιεί τον χειρισμό αλλαγών διαμόρφωσης.
data class Profile(val id: Int, val name: String) : Parcelable
// Πλοήγηση με ελάχιστα δεδομένα
navController.navigate("profile/42")
// Ανάκτηση ορισμάτων στην οθόνη
composable(
route = "profile/{userId}",
arguments = listOf(
NavArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
ProfileDetailScreen(userId = userId)
}
Σε πραγματικές εφαρμογές, συχνά απαιτείται η οργάνωση ένθετων γραφημάτων πλοήγησης — για παράδειγμα, μια ξεχωριστή στοίβα οθονών μέσα σε μια καρτέλα BottomNavigation. composable() υποστηρίζει την ένθεση μέσω του μηχανισμού ένθετων NavHost: μέσα σε μια composable οθόνη μπορεί να δηλωθεί ένα δικό της NavHost με ανεξάρτητη στοίβα διαδρομών.
Κάθε ένθετο NavHost έχει το δικό του NavController και back stack. Αυτό σημαίνει ότι η πλοήγηση μέσα σε μια καρτέλα δεν επηρεάζει την πλοήγηση σε άλλες καρτέλες — ο χρήστης μπορεί ελεύθερα να εναλλάσσεται μεταξύ καρτελών χωρίς να χάνει το ιστορικό μεταβάσεων μέσα σε κάθε μία. Αυτή η αρχιτεκτονική ονομάζεται Scoped Navigation και συνιστάται από την Google για εφαρμογές με σύνθετη πολυεπίπεδη πλοήγηση.
Κατά την υλοποίηση ένθετης πλοήγησης, είναι σημαντικό να διαχειρίζεται σωστά η κατάσταση του NavController: κάθε ένθετο NavHost πρέπει να αποθηκεύει το δικό του rememberNavController μέσα στο πεδίο εφαρμογής της composable συνάρτησης. Σύμφωνα με στοιχεία του Android Developer Summit 2024, πάνω από το 40% των εφαρμογών Jetpack Compose με τρεις ή περισσότερες καρτέλες χρησιμοποιούν αρχιτεκτονική ένθετων NavHost για απομόνωση της πλοήγησης μεταξύ μονάδων.
// Κύριο NavHost με καρτέλες
composable("tabs") {
MainTabsScreen { tab ->
when (tab) {
Tab.Home -> HomeNavGraph()
Tab.Search -> SearchNavGraph()
}
}
}
// Ένθετο γράφημα στην καρτέλα Home
@Composable
fun HomeNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_feed"
) {
composable("home_feed") { FeedScreen() }
composable("home_detail/{postId}") { PostDetailScreen() }
}
}
Πριν από το Jetpack Compose, η τυπική μέθοδος πλοήγησης στο Android χρησιμοποιούσε Intent και FragmentManager. Intent — είναι ένα μήνυμα συστήματος που εκκινεί μια νέα Activity, η οποία συνεπάγεται αναδημιουργία ολόκληρου του δέντρου View. Σε αντίθεση με αυτό, το composable() λειτουργεί μέσα σε μία Activity και απλά αντικαθιστά μέρος του δέντρου Compose, το οποίο είναι σημαντικά ταχύτερο και πιο αποδοτικό στη μνήμη.
Κύριες διαφορές μεταξύ composable() και πλοήγησης βάσει Intent:
| Χαρακτηριστικό | composable() | Intent / Fragment |
|---|---|---|
| Αρχιτεκτονική | Single Activity, δέντρο Compose | Multi Activity, στοίβες Fragment |
| Μεταφορά δεδομένων | παράμετροι διαδρομής/ερωτήματος, κοινό ViewModel | Intent extras, Bundle, SharedPreferences |
| Βαθιοί σύνδεσμοι | Ενσωματωμένη υποστήριξη navDeepLink | intent-filter στο manifest |
| Back stack | Αυτόματη διαχείριση popBackStack | FragmentManager.popBackStack() |
| Χρόνος εναλλαγής | 5–15 ms (εντός διεργασίας) | 50–200 ms (με αναδημιουργία) |
Η μετάβαση από Intent σε composable() — δεν είναι απλώς αλλαγή API, αλλά αλλαγή αρχιτεκτονικού παραδείγματος. Αντί να καθορίζεται ρητά ποια Activity πρέπει να ανοίξει, ο προγραμματιστής περιγράφει δηλωτικά όλες τις πιθανές διαδρομές σε ένα μέρος, βελτιώνοντας την αναγνωσιμότητα του κώδικα και απλοποιώντας τη δοκιμή πλοήγησης. Σύμφωνα με στοιχεία του Google I/O 2024, το Jetpack Compose με Navigation Compose μειώνει τον όγκο κώδικα πλοήγησης κατά 40–60% σε σύγκριση με το FragmentManager.
Ένα από τα πιο συνηθισμένα λάθη — αναδημιουργία του NavController κατά την ανασύνθεση. Εάν το NavController δημιουργείται μέσω του rememberNavController() στο επίπεδο του γονικού composable που μπορεί να αναδημιουργηθεί κατά την αλλαγή κατάστασης, η πλοήγηση χαλάει — το ιστορικό μεταβάσεων χάνεται. Η σωστή λύση είναι η ανύψωση του NavController σε ένα σταθερό επίπεδο composable, για παράδειγμα, στο επίπεδο της Activity ή του ριζικού composable της εφαρμογής.
Το δεύτερο συνηθισμένο πρόβλημα — ατελείωτη ανασύνθεση κατά την πλοήγηση. Αυτό συμβαίνει όταν η κλήση navController.navigate() τοποθετείται απευθείας στο σώμα της composable συνάρτησης. Δεδομένου ότι η πλοήγηση αλλάζει την κατάσταση του NavHost, αυτό προκαλεί ανασύνθεση που καλεί ξανά το navigate(), δημιουργώντας έναν κύκλο. Όλες οι κλήσεις πλοήγησης πρέπει να είναι τυλιγμένες σε χειριστές lambda (onClick, onButtonPressed) και όχι να εκτελούνται στη σύνθεση.
Το τρίτο λάθος — εσφαλμένη διαχείριση της back stack κατά τη χρήση BottomNavigation. Η απλή πλοήγηση μέσω navigate() σε κάθε εναλλαγή καρτέλας προσθέτει μια νέα εγγραφή στη στοίβα, αντί να επιστρέφει στην υπάρχουσα. Για το BottomNavigation, πρέπει να χρησιμοποιείται navController.navigate() με restoreState = true και launchSingleTop = true, που εξασφαλίζει σωστή επαναφορά της κατάστασης κατά την εναλλαγή μεταξύ καρτελών.
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
Συχνές Ερωτήσεις
composable() — δεν είναι σχολιασμός, αλλά συνάρτηση επέκτασης του NavHost που συνδέει μια διαδρομή με το UI. Μια κανονική συνάρτηση @Composable απλά περιγράφει τη διάταξη, ενώ το composable() καταχωρεί αυτή τη διάταξη στο γράφημα πλοήγησης με την καθορισμένη διαδρομή, καθιστώντας την διαθέσιμη για πλοήγηση μέσω του NavController.
Συνιστάται η μεταβίβαση μόνο ενός αναγνωριστικού (ID) μέσω παραμέτρου διαδρομής και η φόρτωση του ίδιου του αντικειμένου στην οθόνη με βάση το ID μέσω αποθετηρίου ή ViewModel. Εάν το αντικείμενο πρέπει ωστόσο να μεταβιβαστεί, χρησιμοποιήστε το NavType.ParcelableType, αλλά αποφύγετε τη μεταβίβαση αντικειμένων μεγαλύτερων από 1 KB — αυτό μπορεί να οδηγήσει σε TransactionTooLargeException.
Η περιστροφή της οθόνης προκαλεί αλλαγή διαμόρφωσης που από προεπιλογή αναδημιουργεί την Activity. Για να διατηρηθεί η κατάσταση των composable οθονών, χρησιμοποιήστε το rememberSaveable για απλά δεδομένα ή ViewModel με το πεδίο εφαρμογής της συγκεκριμένης οθόνης. Το Navigation Compose επαναφέρει το back stack μετά την αναδημιουργία, αλλά η κατάσταση μέσα στις composable() συναρτήσεις επαναφέρεται χωρίς rememberSaveable.
Όχι, το composable() — είναι συνάρτηση επέκτασης του NavGraphBuilder που είναι διαθέσιμη μόνο μέσα στο μπλοκ NavHost. Για απλή αντικατάσταση μέρους του UI χωρίς πλοήγηση, χρησιμοποιήστε υπό συνθήκη απόδοση (when, if) ή AnimatedContent. Το composable() προορίζεται ακριβώς για δρομολόγηση με υποστήριξη back stack και βαθιών συνδέσμων.
Χρησιμοποιήστε το SavedStateHandle μέσα στο ViewModel: κατά την πρώτη μετάβαση, το handle.get("initialized") θα επιστρέψει null, κατά την επιστροφή — την αποθηκευμένη τιμή. Εναλλακτικά, αναλύστε την τρέχουσα θέση στο back stack μέσω του navController.previousBackStackEntry — εάν είναι null, αυτή είναι η πρώτη οθόνη στη στοίβα πλοήγησης.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης