Router (δρομολογητής) — αρχιτεκτονικό πρότυπο που συγκεντρώνει τη λογική πλοήγησης μεταξύ των οθονών της εφαρμογής. Το Router καθορίζει πού και πώς να μεταβεί όταν συμβεί ένα συμβάν. Στο iOS, το Router αφαιρεί το UINavigationController και το segue, στο Android — το FragmentManager και το NavController από το Jetpack Navigation. Το Router μπορεί να λειτουργήσει ως ανεξάρτητο πρότυπο ή ως συστατικό Coordinator (Router-per-Coordinator) και VIPER (Router-per-module). Περισσότερα — στο Android Navigation Component Guide.
Κύρια σημεία
Router — πρότυπο που ενσωματώνει τη λογική μετάβασης μεταξύ οθονών. Βασική ιδέα: ο ViewController (ή Presenter/ViewModel) δεν καλεί απευθείας το navigationController.pushViewController, αλλά ενημερώνει το Router για την πρόθεση πλοήγησης. Το Router αποφασίζει ποιο ViewController να δημιουργήσει και πώς να το εμφανίσει. Το Router μπορεί να είναι απλό (συλλογή μεθόδων πλοήγησης) ή σύνθετο (με δρομολόγηση βάσει σχημάτων URL, deep links και push ειδοποιήσεων).
Τρεις προσεγγίσεις στο Router — Router ως υπηρεσία (καθολικό Navigator-singleton), Router ως συστατικό ενότητας (προσέγγιση VIPER), Router ως πρωτόκολλο (αφαίρεση για Coordinator). Καθολικό Router — απλότητα (μία παρουσία ανά εφαρμογή) — δημοφιλές σε μικρά έργα. Αρθρωτό Router — κάθε λειτουργία έχει το δικό της Router — πρότυπο VIPER και Clean Architecture. Πρωτοκολλικό Router — χρησιμοποιείται στο Coordinator: RouterProtocol με μεθόδους push/present/pop/dismiss.
| Προσέγγιση | Δομή | Πότε να χρησιμοποιηθεί |
|---|---|---|
| Καθολικό Navigator | Singleton με μεθόδους μετάβασης | Μικρά έργα, πρωτότυπα |
| Αρθρωτό Router | Router εντός κάθε ενότητας | VIPER, Clean Architecture |
| Πρωτοκολλικό Router | Protocol + υλοποίηση για Coordinator | MVVM-C, Coordinator pattern |
Ιστορία του προτύπου — Το Router εμφανίστηκε σε web frameworks (URL Router — Rails Routes, Express.js) και προσαρμόστηκε για κινητή ανάπτυξη. Στο iOS, το Router έγινε δημοφιλές με την άνοδο του VIPER (2015-2017). Στο Android, το Router — μέρος του Jetpack Navigation (2018), της τυπικής λύσης της Google για πλοήγηση. Τώρα το Router είναι αναπόσπαστο μέρος κάθε αρθρωτής αρχιτεκτονικής εφαρμογών κινητών.
iOS Router — πρωτόκολλο με μεθόδους για διαφορετικούς τύπους πλοήγησης. Η βασική υλοποίηση περιβάλλει το UINavigationController. Το Router μπορεί να διαχειριστεί modal παρουσιάσεις (present/dismiss), push/pop στη στοίβα πλοήγησης, showDetail για Split View Controller και παρουσιάσεις πλήρους οθόνης (iOS 13+). Το Router δεν δημιουργεί ViewController — λαμβάνει μια έτοιμη παρουσία και την εμφανίζει. Η δημιουργία ViewController γίνεται από εργοστάσιο (Assembly, DI-container) ή Coordinator.
// RouterProtocol για iOS
protocol RouterProtocol {
func push(_ viewController: UIViewController, animated: Bool)
func pop(animated: Bool)
func popToRoot(animated: Bool)
func present(_ viewController: UIViewController, animated: Bool)
func dismiss(animated: Bool)
func setViewControllers(_: [UIViewController], animated: Bool)
}
// Υλοποίηση Router μέσω UINavigationController
class NavigationRouter: RouterProtocol {
private let navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func push(_ vc: UIViewController, animated: Bool) {
if navigationController.presentedViewController {
navigationController.dismiss(animated: false)
}
navigationController.pushViewController(vc, animated: animated)
}
func pop(animated: Bool) {
navigationController.popViewController(animated: animated)
}
func popToRoot(animated: Bool) {
navigationController.popToRootViewController(animated: animated)
}
func present(_ vc: UIViewController, animated: Bool) {
navigationController.present(vc, animated: animated)
}
func dismiss(animated: Bool) {
navigationController.dismiss(animated: animated)
}
func setViewControllers(_ vcs: [UIViewController], animated: Bool) {
navigationController.setViewControllers(vcs, animated: animated)
}
}
// Assembly — δημιουργία ενότητας με Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router στο VIPER — κάθε ενότητα VIPER έχει το δικό της Router που γνωρίζει ποιες οθόνες να ανοίξει από την τρέχουσα ενότητα. Το Router δημιουργεί το ViewController της επόμενης ενότητας μέσω εργοστασίου και μεταφέρει τον έλεγχο. Router στο VIPER — το μόνο συστατικό που περιέχει import UIKit (εκτός από το View). Αυτό απλοποιεί τη δοκιμή Presenter και Interactor: δεν εξαρτώνται από το UIKit.
Jetpack Navigation Component — τυπικός router για Android, προτεινόμενος από την Google από το 2018. Η πλοήγηση ορίζεται σε ένα γράφημα XML (nav_graph.xml): κόμβοι (fragments, activities, destinations) και ακμές (actions). Το NavController διαχειρίζεται τις μεταβάσεις: καλεί FragmentManager.replace/add και διαχειρίζεται τη στοίβα επιστροφής (back stack). Το Navigation Component υποστηρίζει deep links, ορίσματα, κινούμενα σχέδια μετάβασης και Safe Args για ασφαλή μεταφορά δεδομένων τύπων.
// nav_graph.xml — γράφημα πλοήγησης
<!-- @xml/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/loginFragment">
<fragment android:id="@+id/loginFragment"
android:name=".ui.login.LoginFragment">
<action android:id="@+id/to_home"
app:destination="@id/homeFragment" />
</fragment>
<fragment android:id="@+id/homeFragment"
android:name=".ui.home.HomeFragment">
<argument android:name="userId"
android:defaultValue="0"
app:argType="integer" />
<deepLink app:uri="myapp://home/{userId}" />
</fragment>
</navigation>
// Kotlin — πλοήγηση μέσω NavController
class LoginFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.loginButton.setOnClickListener {
val action = LoginFragmentDirections.toHome(userId = 42)
findNavController().navigate(action)
}
}
}
// Safe Args — ασφαλή ορίσματα τύπων
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — ενσωμάτωση με Toolbar, BottomNav, Drawer
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navHost = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHost.navController
NavigationUI.setupActionBarWithNavController(this, navController)
}
}
Deep links στο Android Navigation — Το Navigation Component υποστηρίζει ρητά deep links (PendingIntent + NavDeepLinkBuilder) και άρρητα (μέσω intent-filter στο manifest). Το deep link μπορεί να οδηγήσει σε οποιονδήποτε κόμβο του nav_graph. Επεξεργασία push ειδοποιήσεων: η Notification δημιουργεί ένα PendingIntent με NavDeepLinkBuilder που επαναφέρει τη στοίβα επιστροφής και ανοίγει την επιθυμητή οθόνη. Το Navigation Component διαχειρίζεται αυτόματα τα κουμπιά Up/Back και επιστρέφει τον χρήστη στην προηγούμενη οθόνη.
URL Routing — δρομολόγηση βάσει σχημάτων URL, deep links και universal links. Η εφαρμογή καταχωρεί ένα σχήμα (myapp://profile/42) ή ένα universal link (https://example.com/profile/42). Το Router αναλύει το URL, εξάγει παραμέτρους (userId=42) και ανοίγει την αντίστοιχη οθόνη. Το URL Routing επιτρέπει το άνοιγμα της εφαρμογής από έξω: μέσω συνδέσμου από ιστότοπο, email, push ειδοποίησης, κωδικού QR. Το iOS χρησιμοποιεί NSUserActivity + universal link, το Android — intent-filter + deep link.
// URL Router — ανάλυση deep link και πλοήγηση
protocol URLRoute {
var pattern: String { get }
func navigate(parameters: [String: String], router: RouterProtocol)
}
struct ProfileRoute: URLRoute {
let pattern = "myapp://profile/{userId}"
func navigate(parameters: [String: String], router: RouterProtocol) {
guard let userId = parameters["userId"] else { return }
let profileVC = ProfileViewController(userId: userId)
router.push(profileVC, animated: true)
}
}
// URL Router — αντιστοίχιση URL με διαδρομές
class URLRouter {
private var routes: [URLRoute] = []
func handle(_ url: URL, router: RouterProtocol) {
for route in routes {
if let params = matchPattern(route.pattern, url: url) {
route.navigate(parameters: params, router: router)
return
}
}
}
}
Deep link routing στην πράξη — η εφαρμογή πρέπει να επεξεργαστεί το deep link σε τρεις καταστάσεις: δεν έχει ξεκινήσει (εκκίνηση με deep link), στο παρασκήνιο (επαναφορά από παρασκήνιο), ενεργή (onNewIntent/SceneDelegate). Το URL Router πρέπει να επαναφέρει σωστά τη στοίβα επιστροφής: σε deep link στην οθόνη προφίλ, ο χρήστης πρέπει να μπορεί να πατήσει Back και να επιστρέψει στην προηγούμενη οθόνη. Το iOS και το Android διαχειρίζονται διαφορετικά τη στοίβα επιστροφής — το Router πρέπει να λαμβάνει υπόψη τα χαρακτηριστικά πλατφόρμας.
Router vs Coordinator — Το Router απαντά στην ερώτηση "πώς να εμφανίσουμε την οθόνη;" (push/present), το Coordinator — "ποια οθόνη να εμφανίσουμε;" (ποιο flow να ξεκινήσουμε). Router — εργαλείο πλοήγησης, Coordinator — διοργανωτής flow. Το Router χρησιμοποιείται από τον Coordinator. Στο VIPER, το Router εκτελεί και τις δύο λειτουργίες: αποφασίζει πού να πάει και πώς να εμφανίσει. Στο MVVM-C, το Router — ξεχωριστό πρωτόκολλο που χρησιμοποιείται από τον Coordinator. Τα πρότυπα δεν αλληλοαποκλείονται, αλλά αλληλοσυμπληρώνονται.
| Χαρακτηριστικό | Router | Coordinator | Navigator |
|---|---|---|---|
| Εστίαση | Μηχανική πλοήγησης | Flow πλοήγησης | Καθολική πρόσβαση |
| Επίπεδο | UI-framework (push/present) | Επιχειρηματική λογική (flow) | Υποδομή |
| Δημιουργία οθονών | Λαμβάνει έτοιμο VC | Δημιουργεί VC μέσω εργοστασίου | Δημιουργεί VC |
| Κύκλος ζωής | Ένα ανά πλοήγηση | childCoordinators | Singleton |
Router vs Navigator — Navigator — παλιά ονομασία του Router στην κοινότητα iOS. Σε πρώιμες υλοποιήσεις, το Navigator ήταν μια κλάση Singleton με μεθόδους μετάβασης. Navigator — ένα καθολικό Router-singleton. Το Navigator είναι απλούστερο, αλλά δημιουργεί κρυφές εξαρτήσεις. Το Router σε πρωτόκολλα — πιο δοκιμάσιμο και αρθρωτό. Το Navigator είναι κατάλληλο για έργα χωρίς αυστηρή αρχιτεκτονική, το Router — για Clean Architecture και VIPER.
Συχνές ερωτήσεις
Router — αφαίρεση πάνω από UINavigationController (push/present/pop). Coordinator — στρώμα πάνω από το Router που διαχειρίζεται το flow (ποια οθόνη θα εμφανιστεί επόμενη). Το Router είναι υπεύθυνο για τη μηχανική, ο Coordinator — για τη λογική πλοήγησης. Ο Coordinator χρησιμοποιεί το Router για την εκτέλεση μεταβάσεων. Στο VIPER, το Router συνδυάζει και τις δύο λειτουργίες, στο MVVM-C — είναι διαχωρισμένες.
Όχι. Για εφαρμογές με 3-5 οθόνες, οι άμεσες κλήσεις navigationController.pushViewController είναι μια φυσιολογική λύση. Το Router γίνεται χρήσιμο σε 10+ οθόνες, deep links, push ειδοποιήσεις και αρθρωτή αρχιτεκτονική. Το Router απλοποιεί τη δοκιμή πλοήγησης και επιτρέπει την κεντρική επεξεργασία μεταβάσεων από οποιαδήποτε πηγή (κουμπί, deep link, push).
URL Router — επέκταση του Router που αναλύει deep links και universal links. Το URL Router καθορίζει την αντιστοιχία μεταξύ URL και οθόνης εφαρμογής. Για παράδειγμα, route myapp://profile/42 → ProfileViewController(userId: 42). Το URL Router συγκεντρώνει την επεξεργασία deep links από διαφορετικές πηγές: push ειδοποιήσεις, κωδικοί QR, σύνδεσμοι email, καθολικοί σύνδεσμοι.
Ναι. Το Router μπορεί να λειτουργήσει ανεξάρτητα — ως καθολικό Navigator ή ως συστατικό ενότητας VIPER. Router χωρίς Coordinator — απλώς ενσωμάτωση του UINavigationController πίσω από ένα πρωτόκολλο. Coordinator χωρίς Router — πιο δύσκολο (ο ViewController καλεί ο ίδιος push/present), αλλά δυνατό. Βέλτιστη πρακτική — Router + Coordinator μαζί.
Λειτουργικά — ναι, και τα δύο συγκεντρώνουν την πλοήγηση. Η υλοποίηση διαφέρει: iOS Router — πρωτόκολλο + κλάση γύρω από το UINavigationController; Android — Jetpack Navigation Component με γράφημα XML και NavController. Το Android Router είναι ενσωματωμένο στο framework (Navigation Component), το iOS Router — εξωτερική υλοποίηση. Το Android Router υποστηρίζει Safe Args, το iOS Router — όχι (χρειάζονται generics ή Codable).
Συμπέρασμα
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης