InheritedWidget — este un widget special în Flutter care transmite date în jos pe arborele de widgeturi fără transmiterea explicită prin constructori. Widgeturile copil obțin acces la date prin BuildContext și se abonează automat la actualizări. Când datele din InheritedWidget se modifică, toate widgeturile dependente se reconstruiesc. Conform Flutter API Reference, 2025, InheritedWidget stă la baza Theme, MediaQuery, Localizations și a majorității bibliotecilor de gestionare a stării.
Principalele puncte
InheritedWidget — este un widget care face datele sale accesibile tuturor descendenților în Widget Tree. Spre deosebire de un widget obișnuit, care transmite date doar prin constructor către elementele copil, InheritedWidget permite oricărui widget din subarbore să acceseze datele fără un lanț de parametri. Aceasta rezolvă problema "prop drilling" — transmiterea datelor prin mai multe widgeturi intermediare care nu folosesc ele însele aceste date.
Flutter include mai multe InheritedWidgeturi încorporate: Theme (schema de culori și stiluri), MediaQuery (dimensiunea ecranului, orientarea, densitatea pixelilor), Localizations (șiruri localizate), Directionality (direcția textului), DefaultTextStyle (stilul implicit al textului). Aceste widgeturi sunt stabilite de widgeturile rădăcină precum MaterialApp și sunt disponibile în întreaga aplicație.
InheritedWidget nu are stare proprie — stochează datele transmise prin constructor. Când părintele InheritedWidget se reconstruiește cu date noi, metoda updateShouldNotify este apelată pentru a compara datele vechi și noi. Dacă metoda returnează true, toate widgeturile dependente sunt marcate pentru reconstruire. Acesta este un mecanism simplu, dar eficient de actualizare reactivă.
Mecanismul de transmitere a datelor prin InheritedWidget se bazează pe Element Tree. Când un widget apelează dependOnInheritedWidgetOfExactType, elementul corespunzător înregistrează o dependență de InheritedElement. La modificarea InheritedWidget, InheritedElement notifică toate elementele dependente, care se reconstruiesc în următorul cadru.
Metoda dependOnInheritedWidgetOfExactType nu doar găsește InheritedWidget în arbore — ea abonează elementul curent la notificări. Dacă ați folosi findAncestorWidgetOfExactType în loc de dependOn, widgetul ar primi datele, dar nu s-ar reconstrui la modificarea lor. Aceasta este o diferență importantă: dependOn este o abonare, iar findAncestor este o căutare unică.
Când un widget solicită InheritedWidget, Flutter urcă pe Element Tree de la elementul curent până la rădăcină, verificând fiecare InheritedElement pentru potrivirea tipului. Primul InheritedElement găsit este returnat. Aceasta înseamnă că cel mai apropiat InheritedWidget din arbore are prioritate — puteți suprascrie datele la un anumit nivel plasând InheritedWidget mai aproape de descendenți.
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;
}
În acest exemplu, MyTheme folosește metoda statică of pentru a furniza date descendenților. Metoda dependOnInheritedWidgetOfExactType înregistrează dependența, iar updateShouldNotify compară datele vechi și noi pentru a determina necesitatea reconstruirii widgeturilor dependente.
Crearea propriului InheritedWidget constă în doi pași: definirea clasei care moștenește InheritedWidget și implementarea metodei statice of pentru accesul din descendenți. Datele sunt transmise prin constructor, iar metoda updateShouldNotify determină când widgeturile dependente trebuie să se reconstruiască.
Clasa trebuie să moștenească InheritedWidget și să primească date prin constructor cu parametrul obligatoriu child. Datele pot fi de orice tip: primitive, obiecte, funcții. Regula principală — datele trebuie să fie imutabile (immutable) pentru a putea compara fiabil valorile vechi și noi.
Metoda statică of primește un BuildContext și returnează datele InheritedWidget. În interior, se apelează dependOnInheritedWidgetOfExactType, care caută cel mai apropiat InheritedWidget de tipul specificat în arbore. Dacă InheritedWidget nu este găsit, metoda aruncă o excepție sau returnează o valoare implicită în funcție de implementare.
Pentru a accesa datele, widgetul apelează MyWidget.of(context) în interiorul metodei build. Flutter abonează automat widgetul la actualizări. Dacă datele se modifică, widgetul se va reconstrui în următorul cadru. Acest lucru permite crearea unui cod curat și declarativ fără parametri inutili.
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;
}
În acest exemplu, UserPreferences stochează preferințele utilizatorului. Metoda updateShouldNotify compară fiecare câmp separat, ceea ce previne reconstruirile inutile la modificarea unui singur parametru. Folosiți o abordare similară pentru propriile InheritedWidgeturi cu mai multe câmpuri.
updateShouldNotify — este metoda cheie a InheritedWidget care determină dacă widgeturile dependente trebuie notificate despre modificarea datelor. Dacă metoda returnează false, widgeturile dependente nu se reconstruiesc, chiar dacă InheritedWidget a primit o nouă instanță cu aceleași date. Acest lucru este critic pentru performanță.
Comparați doar câmpurile care s-au modificat efectiv și afectează afișarea. Dacă InheritedWidget conține 10 câmpuri, dar doar unul dintre ele afectează UI, verificați doar acel câmp. Pentru colecții, folosiți comparație profundă sau structuri de date imutabile. Nu folosiți == pentru List sau Map, deoarece acestea sunt comparate prin referință.
Cea mai frecventă eroare — returnarea true fără comparație. Aceasta duce la reconstruirea tuturor widgeturilor dependente la fiecare actualizare a părintelui, chiar dacă datele nu s-au modificat. A doua eroare — returnarea false când datele s-au modificat, ceea ce duce la un UI învechit. A treia — o comparație complexă care se execută în fiecare cadru și încetinește lucrul.
InheritedWidget și callbackurile (transmiterea funcțiilor prin constructor) rezolvă sarcini diferite. InheritedWidget este potrivit pentru datele de care au nevoie multe widgeturi la diferite niveluri ale arborelui. Callbackurile sunt convenabile pentru transmiterea unidirecțională a evenimentelor de la părinte la un anumit copil sau invers. Alegerea depinde de arhitectura aplicației și de frecvența modificărilor.
Folosiți InheritedWidget când datele sunt necesare multor widgeturi la diferite niveluri de imbricare: tema aplicației, preferințele utilizatorului, informații despre dispozitiv, datele sesiunii curente. InheritedWidget este deosebit de eficient pentru datele "globale" care se modifică rar, dar sunt necesare în diferite părți ale UI.
Callbackurile (funcții de revenire) sunt potrivite pentru transmiterea evenimentelor de la widgetul copil la cel părinte: apăsarea butonului, selectarea unui element din listă, trimiterea unui formular. Callbackurile indică explicit ce acțiuni poate efectua copilul și nu creează dependențe ascunse. Pentru transmiterea datelor în jos pe arbore pe un număr mic de niveluri, este mai simplu să folosiți parametrii constructorului.
| Criteriu | InheritedWidget | Callbackuri |
|---|---|---|
| Direcție | De sus în jos (părinte → descendenți) | De jos în sus (copil → părinte) sau direct |
| Domeniu | Întregul subarbore | Widget specific |
| Reconstruire | Automată la modificarea datelor | Necesită setState manual |
| Complexitate | Medie (necesită clasă InheritedWidget) | Scăzută (doar o funcție) |
Provider și Riverpod — biblioteci populare de gestionare a stării în Flutter, construite pe InheritedWidget. Ele îi extind capacitățile: adaugă suport pentru ChangeNotifier, ștergere automată la demontare, inițializare lentă și sintaxă simplificată prin generice.
Provider folosește InheritedWidget pentru a transmite un obiect de orice tip în jos pe arbore. ChangeNotifierProvider urmărește modificările prin ChangeNotifier și apelează updateShouldNotify la apelarea notifyListeners. Acest lucru scutește dezvoltatorul de crearea manuală a InheritedWidget și implementarea updateShouldNotify.
InheritedWidget direct oferă mai mult control și nu necesită dependențe externe. Provider oferă infrastructură gata făcută: Consumer, Selector, MultiProvider, ProxyProvider. Alegerea depinde de complexitatea aplicației. Pentru proiecte simple, InheritedWidget direct este suficient; pentru proiecte mari, Provider sau Riverpod reduc codul șablon.
// InheritedWidget direct
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;
}
// Echivalent Provider
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
Ambele abordări în exemplu rezolvă aceeași sarcină — transmiterea UserData în jos pe arbore. Provider reduce cantitatea de cod, dar ascunde mecanica InheritedWidget. InheritedWidget direct oferă control complet și înțelegere a ceea ce se întâmplă, ceea ce este deosebit de important la învățarea Flutter și depanarea problemelor complexe de reconstruire.
Întrebări frecvente
InheritedWidget face datele accesibile tuturor descendenților prin BuildContext, iar un widget obișnuit transmite date doar prin constructor. InheritedWidget, de asemenea, abonează descendenții la actualizări de date.
Widgeturile dependente se reconstruiesc doar când updateShouldNotify returnează true. Dacă metoda este implementată corect, reconstruirea are loc doar la modificarea efectivă a datelor, nu la fiecare reconstruire a părintelui.
Da, puteți folosi orice număr de InheritedWidgeturi în același arbore. Fiecare furnizează date de un anumit tip, iar widgeturile pot primi date din mai multe InheritedWidgeturi simultan.
dependOn abonează widgetul la actualizări — la modificarea datelor, widgetul se va reconstrui. findAncestor efectuează o căutare unică fără abonare, iar widgetul nu va afla despre modificările datelor.
Pentru stare simplă (temă, setări) InheritedWidget este suficient. Pentru stare complexă cu logică de afaceri, folosiți Provider, Riverpod sau BLoC — sunt construite pe InheritedWidget și adaugă infrastructura necesară.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și