InheritedWidget — είναι ένα ειδικό widget στο Flutter που μεταφέρει δεδομένα προς τα κάτω στο δέντρο widget χωρίς ρητή μεταβίβαση μέσω κατασκευαστών. Τα θυγατρικά widget αποκτούν πρόσβαση στα δεδομένα μέσω του BuildContext και εγγράφονται αυτόματα για ενημερώσεις. Όταν τα δεδομένα στο InheritedWidget αλλάζουν, όλα τα εξαρτώμενα widget ανακατασκευάζονται. Σύμφωνα με το Flutter API Reference, 2025, το InheritedWidget βρίσκεται στη βάση των Theme, MediaQuery, Localizations και των περισσότερων βιβλιοθηκών διαχείρισης κατάστασης.
Κύρια σημεία
InheritedWidget — είναι ένα widget που καθιστά τα δεδομένα του προσβάσιμα σε όλους τους απογόνους στο Widget Tree. Σε αντίθεση με ένα συνηθισμένο widget, που μεταφέρει δεδομένα μόνο μέσω κατασκευαστή σε θυγατρικά στοιχεία, το InheritedWidget επιτρέπει σε οποιοδήποτε widget στο υποδέντρο να έχει πρόσβαση στα δεδομένα χωρίς αλυσίδα παραμέτρων. Αυτό λύνει το πρόβλημα του "prop drilling" — μεταφορά δεδομένων μέσω πολλαπλών ενδιάμεσων widget που τα ίδια δεν χρησιμοποιούν αυτά τα δεδομένα.
Το Flutter περιλαμβάνει πολλά ενσωματωμένα InheritedWidget: Theme (χρωματικό σχήμα και στυλ), MediaQuery (μέγεθος οθόνης, προσανατολισμός, πυκνότητα pixel), Localizations (τοπικοποιημένες συμβολοσειρές), Directionality (κατεύθυνση κειμένου), DefaultTextStyle (προεπιλεγμένο στυλ κειμένου). Αυτά τα widget ορίζονται από ριζικά widget όπως το MaterialApp και είναι διαθέσιμα σε ολόκληρη την εφαρμογή.
InheritedWidget δεν έχει δική του κατάσταση — αποθηκεύει δεδομένα που μεταφέρονται μέσω κατασκευαστή. Όταν ο γονέας του InheritedWidget ανακατασκευάζεται με νέα δεδομένα, καλείται η μέθοδος updateShouldNotify για σύγκριση παλαιών και νέων δεδομένων. Αν η μέθοδος επιστρέψει true, όλα τα εξαρτώμενα widget επισημαίνονται για ανακατασκευή. Αυτός είναι ένας απλός αλλά αποτελεσματικός μηχανισμός αντιδραστικής ενημέρωσης.
Ο μηχανισμός μεταφοράς δεδομένων μέσω InheritedWidget βασίζεται στο Element Tree. Όταν ένα widget καλεί dependOnInheritedWidgetOfExactType, το αντίστοιχο στοιχείο καταγράφει μια εξάρτηση από το InheritedElement. Όταν το InheritedWidget αλλάζει, το InheritedElement ειδοποιεί όλα τα εξαρτώμενα στοιχεία, τα οποία ανακατασκευάζονται στο επόμενο καρέ.
Η μέθοδος dependOnInheritedWidgetOfExactType όχι μόνο βρίσκει το InheritedWidget στο δέντρο — εγγράφει το τρέχον στοιχείο για ειδοποιήσεις. Αν χρησιμοποιούσατε το findAncestorWidgetOfExactType αντί για dependOn, το widget θα λάμβανε δεδομένα αλλά δεν θα ανακατασκευαζόταν όταν αυτά άλλαζαν. Αυτή είναι μια σημαντική διαφορά: το dependOn είναι εγγραφή, το findAncestor είναι εφάπαξ αναζήτηση.
Όταν ένα widget ζητά InheritedWidget, το Flutter ανεβαίνει στο Element Tree από το τρέχον στοιχείο προς τη ρίζα, ελέγχοντας κάθε InheritedElement για ταύτιση τύπου. Το πρώτο InheritedElement που βρίσκεται επιστρέφεται. Αυτό σημαίνει ότι το πλησιέστερο InheritedWidget στο δέντρο έχει προτεραιότητα — μπορείτε να παρακάμψετε δεδομένα σε ένα συγκεκριμένο επίπεδο τοποθετώντας το InheritedWidget πιο κοντά στους απογόνους.
class ThemeData {
final Color primaryColor;
final TextTheme textTheme;
const ThemeData({required this.primaryColor, required this.textTheme});
}
class MyTheme extends InheritedWidget {
final ThemeData data;
const MyTheme({required this.data, required Widget child}) : super(child: child);
static MyTheme of(BuildContext context) {
final widget = context.dependOnInheritedWidgetOfExactType<MyTheme>();
assert(widget != null, "MyTheme not found in tree");
return widget!;
}
@override
bool updateShouldNotify(MyTheme oldWidget) => oldWidget.data != data;
}
Σε αυτό το παράδειγμα, το MyTheme χρησιμοποιεί τη στατική μέθοδο of για να παρέχει δεδομένα στους απογόνους. Η μέθοδος dependOnInheritedWidgetOfExactType καταγράφει την εξάρτηση και η updateShouldNotify συγκρίνει παλαιά και νέα δεδομένα για να καθορίσει την ανάγκη ανακατασκευής των εξαρτώμενων widget.
Η δημιουργία δικού σας InheritedWidget αποτελείται από δύο βήματα: ορισμός της κλάσης που κληρονομεί το InheritedWidget και υλοποίηση της στατικής μεθόδου of για πρόσβαση από απογόνους. Τα δεδομένα μεταφέρονται μέσω κατασκευαστή και η μέθοδος updateShouldNotify καθορίζει πότε πρέπει να ανακατασκευαστούν τα εξαρτώμενα widget.
Η κλάση πρέπει να κληρονομεί το InheritedWidget και να λαμβάνει δεδομένα μέσω κατασκευαστή με την υποχρεωτική παράμετρο child. Τα δεδομένα μπορεί να είναι οποιουδήποτε τύπου: πρωτότυπα, αντικείμενα, συναρτήσεις. Ο κύριος κανόνας — τα δεδομένα πρέπει να είναι αμετάβλητα (immutable) για να μπορούν να συγκριθούν αξιόπιστα οι παλαιές και νέες τιμές.
Η στατική μέθοδος of λαμβάνει ένα BuildContext και επιστρέφει τα δεδομένα του InheritedWidget. Εσωτερικά, καλείται η dependOnInheritedWidgetOfExactType, η οποία αναζητά το πλησιέστερο InheritedWidget του καθορισμένου τύπου στο δέντρο. Αν δεν βρεθεί InheritedWidget, η μέθοδος είτε ρίχνει εξαίρεση είτε επιστρέφει προεπιλεγμένη τιμή ανάλογα με την υλοποίηση.
Για πρόσβαση στα δεδομένα, το widget καλεί MyWidget.of(context) μέσα στη μέθοδο build. Το Flutter εγγράφει αυτόματα το widget για ενημερώσεις. Αν τα δεδομένα αλλάξουν, το widget θα ανακατασκευαστεί στο επόμενο καρέ. Αυτό επιτρέπει τη δημιουργία καθαρού και δηλωτικού κώδικα χωρίς περιττές παραμέτρους.
class UserPreferences extends InheritedWidget {
final String languageCode;
final bool darkMode;
const UserPreferences({
required this.languageCode,
required this.darkMode,
required Widget child,
}) : super(child: child);
static UserPreferences of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<UserPreferences>()!;
}
@override
bool updateShouldNotify(UserPreferences oldWidget) =>
oldWidget.languageCode != languageCode || oldWidget.darkMode != darkMode;
}
Σε αυτό το παράδειγμα, το UserPreferences αποθηκεύει προτιμήσεις χρήστη. Η μέθοδος updateShouldNotify συγκρίνει κάθε πεδίο ξεχωριστά, αποτρέποντας περιττές ανακατασκευές όταν αλλάζει μόνο μία παράμετρος. Χρησιμοποιήστε παρόμοια προσέγγιση για τα δικά σας InheritedWidget με πολλαπλά πεδία.
updateShouldNotify — είναι η βασική μέθοδος του InheritedWidget που καθορίζει αν πρέπει να ειδοποιηθούν τα εξαρτώμενα widget για αλλαγή δεδομένων. Αν η μέθοδος επιστρέψει false, τα εξαρτώμενα widget δεν ανακατασκευάζονται, ακόμα κι αν το ίδιο το InheritedWidget έλαβε νέο στιγμιότυπο με τα ίδια δεδομένα. Αυτό είναι κρίσιμο για την απόδοση.
Συγκρίνετε μόνο τα πεδία που πραγματικά άλλαξαν και επηρεάζουν την εμφάνιση. Αν το InheritedWidget περιέχει 10 πεδία αλλά μόνο ένα επηρεάζει το UI, ελέγξτε μόνο αυτό το πεδίο. Για συλλογές, χρησιμοποιήστε βαθιά σύγκριση ή αμετάβλητες δομές δεδομένων. Μη χρησιμοποιείτε == για List ή Map, καθώς συγκρίνονται κατά αναφορά.
Το πιο συνηθισμένο σφάλμα — επιστροφή true χωρίς σύγκριση. Αυτό οδηγεί σε ανακατασκευή όλων των εξαρτώμενων widget σε κάθε ενημέρωση του γονέα, ακόμα κι αν τα δεδομένα δεν άλλαξαν. Το δεύτερο σφάλμα — επιστροφή false όταν τα δεδομένα άλλαξαν, οδηγώντας σε παρωχημένο UI. Το τρίτο — σύνθετη σύγκριση που εκτελείται κάθε καρέ και επιβραδύνει τη λειτουργία.
InheritedWidget και callback (μεταβίβαση συναρτήσεων μέσω κατασκευαστή) λύνουν διαφορετικές εργασίες. Το InheritedWidget είναι κατάλληλο για δεδομένα που χρειάζονται πολλά widget σε διαφορετικά επίπεδα του δέντρου. Τα callback είναι βολικά για μονόδρομη μεταφορά γεγονότων από τον γονέα σε ένα συγκεκριμένο παιδί ή αντίστροφα. Η επιλογή εξαρτάται από την αρχιτεκτονική της εφαρμογής και τη συχνότητα αλλαγών.
Χρησιμοποιήστε InheritedWidget όταν τα δεδομένα χρειάζονται σε πολλά widget σε διαφορετικά επίπεδα φωλιάσματος: θέμα εφαρμογής, προτιμήσεις χρήστη, πληροφορίες συσκευής, δεδομένα τρέχουσας συνεδρίας. Το InheritedWidget είναι ιδιαίτερα αποτελεσματικό για "καθολικά" δεδομένα που αλλάζουν σπάνια αλλά χρειάζονται σε διάφορα μέρη του UI.
Callback (συναρτήσεις επιστροφής) είναι κατάλληλα για μεταφορά γεγονότων από το θυγατρικό widget στο γονικό: πάτημα κουμπιού, επιλογή στοιχείου λίστας, υποβολή φόρμας. Τα callback υποδεικνύουν ρητά ποιες ενέργειες μπορεί να εκτελέσει το παιδί και δεν δημιουργούν κρυφές εξαρτήσεις. Για μεταφορά δεδομένων προς τα κάτω στο δέντρο σε μικρό αριθμό επιπέδων, είναι επίσης απλούστερο να χρησιμοποιείτε παραμέτρους κατασκευαστή.
| Κριτήριο | InheritedWidget | Callback |
|---|---|---|
| Κατεύθυνση | Από πάνω προς τα κάτω (γονέας → απόγονοι) | Από κάτω προς τα πάνω (παιδί → γονέας) ή άμεσα |
| Πεδίο | Ολόκληρο το υποδέντρο | Συγκεκριμένο widget |
| Ανακατασκευή | Αυτόματη κατά την αλλαγή δεδομένων | Απαιτεί χειροκίνητο setState |
| Πολυπλοκότητα | Μέτρια (απαιτείται κλάση InheritedWidget) | Χαμηλή (απλή συνάρτηση) |
Provider και Riverpod — δημοφιλείς βιβλιοθήκες διαχείρισης κατάστασης στο Flutter, χτισμένες πάνω στο InheritedWidget. Επεκτείνουν τις δυνατότητές του: προσθέτουν υποστήριξη ChangeNotifier, αυτόματη αφαίρεση κατά την αποσυναρμολόγηση, τεμπέλικη αρχικοποίηση και απλοποιημένη σύνταξη μέσω generics.
Provider χρησιμοποιεί το InheritedWidget για να μεταφέρει ένα αντικείμενο οποιουδήποτε τύπου προς τα κάτω στο δέντρο. Το ChangeNotifierProvider παρακολουθεί αλλαγές μέσω ChangeNotifier και καλεί updateShouldNotify όταν καλείται το notifyListeners. Αυτό απαλλάσσει τον προγραμματιστή από τη μη αυτόματη δημιουργία InheritedWidget και την υλοποίηση updateShouldNotify.
Το άμεσο InheritedWidget δίνει περισσότερο έλεγχο και δεν απαιτεί εξωτερικές εξαρτήσεις. Το Provider παρέχει έτοιμη υποδομή: Consumer, Selector, MultiProvider, ProxyProvider. Η επιλογή εξαρτάται από την πολυπλοκότητα της εφαρμογής. Για απλά έργα, το άμεσο InheritedWidget είναι αρκετό· για μεγάλα έργα, το Provider ή το Riverpod μειώνουν τον κώδικα προτύπου.
// Άμεσο InheritedWidget
class UserProvider extends InheritedWidget {
final UserData userData;
const UserProvider({required this.userData, required Widget child}) : super(child: child);
static UserData of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<UserProvider>()!.userData;
@override
bool updateShouldNotify(UserProvider old) => old.userData != userData;
}
// Αντίστοιχο Provider
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
Και οι δύο προσεγγίσεις στο παράδειγμα λύνουν την ίδια εργασία — μεταφορά UserData προς τα κάτω στο δέντρο. Provider μειώνει την ποσότητα κώδικα αλλά κρύβει τη μηχανική του InheritedWidget. Το άμεσο InheritedWidget παρέχει πλήρη έλεγχο και κατανόηση του τι συμβαίνει, κάτι που είναι ιδιαίτερα σημαντικό κατά την εκμάθηση Flutter και τον εντοπισμό σφαλμάτων σε σύνθετα προβλήματα ανακατασκευής.
Συχνές Ερωτήσεις
InheritedWidget καθιστά τα δεδομένα προσβάσιμα σε όλους τους απογόνους μέσω BuildContext, ενώ ένα συνηθισμένο widget μεταφέρει δεδομένα μόνο μέσω κατασκευαστή. Το InheritedWidget επίσης εγγράφει τους απογόνους για ενημερώσεις δεδομένων.
Τα εξαρτώμενα widget ανακατασκευάζονται μόνο όταν το updateShouldNotify επιστρέφει true. Αν η μέθοδος υλοποιηθεί σωστά, η ανακατασκευή γίνεται μόνο κατά την πραγματική αλλαγή δεδομένων, όχι σε κάθε ανακατασκευή του γονέα.
Ναι, μπορείτε να χρησιμοποιήσετε οποιονδήποτε αριθμό InheritedWidget στο ίδιο δέντρο. Κάθε ένα παρέχει δεδομένα συγκεκριμένου τύπου και τα widget μπορούν να λαμβάνουν δεδομένα από πολλά InheritedWidget ταυτόχρονα.
dependOn εγγράφει το widget για ενημερώσεις — όταν αλλάζουν τα δεδομένα, το widget ανακατασκευάζεται. findAncestor εκτελεί εφάπαξ αναζήτηση χωρίς εγγραφή και το widget δεν θα ενημερωθεί για αλλαγές δεδομένων.
Για απλή κατάσταση (θέμα, ρυθμίσεις) το InheritedWidget είναι αρκετό. Για σύνθετη κατάσταση με επιχειρηματική λογική, χρησιμοποιήστε Provider, Riverpod ή BLoC — είναι χτισμένα πάνω στο InheritedWidget και προσθέτουν την απαραίτητη υποδομή.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης