InheritedWidget — je speciální widget ve Flutteru, který předává data dolů stromem widgetů bez explicitního předávání přes konstruktory. Dětské widgety získávají přístup k datům přes BuildContext a automaticky se přihlašují k odběru aktualizací. Když se data v InheritedWidget změní, všechny závislé widgety se znovu postaví. Podle Flutter API Reference, 2025 je InheritedWidget základem Theme, MediaQuery, Localizations a většiny knihoven pro správu stavu.
Hlavní body
InheritedWidget — je widget, který zpřístupňuje svá data všem potomkům ve Widget Tree. Na rozdíl od běžného widgetu, který předává data pouze přes konstruktor dětským prvkům, InheritedWidget umožňuje libovolnému widgetu v podstromě přistupovat k datům bez řetězce parametrů. To řeší problém "prop drilling" — předávání dat přes mnoho zprostředkujících widgetů, které samy tato data nepoužívají.
Flutter obsahuje několik vestavěných InheritedWidget: Theme (barevné schéma a styly), MediaQuery (velikost obrazovky, orientace, hustota pixelů), Localizations (lokalizované řetězce), Directionality (směr textu), DefaultTextStyle (výchozí styl textu). Tyto widgety jsou nastaveny kořenovými widgety jako MaterialApp a jsou dostupné v celé aplikaci.
InheritedWidget nemá vlastní stav — ukládá data předaná přes konstruktor. Když se rodič InheritedWidget znovu postaví s novými daty, je volána metoda updateShouldNotify pro porovnání starých a nových dat. Pokud metoda vrátí true, všechny závislé widgety jsou označeny k přestavbě. To je jednoduchý, ale účinný mechanismus reaktivní aktualizace.
Mechanismus předávání dat přes InheritedWidget je založen na Element Tree. Když widget zavolá dependOnInheritedWidgetOfExactType, odpovídající prvek zaregistruje závislost na InheritedElement. Při změně InheritedWidget, InheritedElement informuje všechny závislé prvky, které se znovu postaví v příštím snímku.
Metoda dependOnInheritedWidgetOfExactType nejen najde InheritedWidget ve stromě — přihlásí aktuální prvek k odběru oznámení. Pokud byste použili findAncestorWidgetOfExactType místo dependOn, widget by data obdržel, ale nepřestavěl by se při jejich změně. To je důležitý rozdíl: dependOn je přihlášení, findAncestor je jednorázové vyhledávání.
Když widget požaduje InheritedWidget, Flutter stoupá po Element Tree od aktuálního prvku ke kořeni, kontroluje každý InheritedElement na shodu typu. První nalezený InheritedElement je vrácen. To znamená, že nejbližší InheritedWidget ve stromě má prioritu — můžete přepsat data na určité úrovni umístěním InheritedWidget blíže k potomkům.
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;
}
V tomto příkladu MyTheme používá statickou metodu of k poskytování dat potomkům. Metoda dependOnInheritedWidgetOfExactType registruje závislost a updateShouldNotify porovnává stará a nová data k určení potřeby přestavby závislých widgetů.
Vytvoření vlastního InheritedWidget se skládá ze dvou kroků: definice třídy dědící z InheritedWidget a implementace statické metody of pro přístup z potomků. Data jsou předávána přes konstruktor a metoda updateShouldNotify určuje, kdy by se měly závislé widgety znovu postavit.
Třída musí dědit z InheritedWidget a přijímat data přes konstruktor s povinným parametrem child. Data mohou být libovolného typu: primitiva, objekty, funkce. Hlavní pravidlo — data musí být neměnná (immutable), aby bylo možné spolehlivě porovnávat staré a nové hodnoty.
Statická metoda of přijímá BuildContext a vrací data InheritedWidget. Uvnitř je volána dependOnInheritedWidgetOfExactType, která hledá nejbližší InheritedWidget zadaného typu ve stromě. Pokud InheritedWidget není nalezen, metoda v závislosti na implementaci buď vyhodí výjimku, nebo vrátí výchozí hodnotu.
Pro přístup k datům widget volá MyWidget.of(context) uvnitř metody build. Flutter automaticky přihlásí widget k odběru aktualizací. Pokud se data změní, widget se znovu postaví v příštím snímku. To umožňuje vytvářet čistý a deklarativní kód bez zbytečných parametrů.
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;
}
V tomto příkladu UserPreferences ukládá uživatelské preference. Metoda updateShouldNotify porovnává každé pole zvlášť, což zabraňuje zbytečným přestavbám při změně pouze jednoho parametru. Použijte podobný přístup pro vlastní InheritedWidget s mnoha poli.
updateShouldNotify — je klíčová metoda InheritedWidget, která určuje, zda by měly být závislé widgety informovány o změně dat. Pokud metoda vrátí false, závislé widgety se nepřestaví, i když samotný InheritedWidget obdržel novou instanci se stejnými daty. To je kriticky důležité pro výkon.
Porovnávejte pouze pole, která se skutečně změnila a ovlivňují zobrazení. Pokud InheritedWidget obsahuje 10 polí, ale pouze jedno z nich ovlivňuje UI, kontrolujte jen toto pole. Pro kolekce používejte hluboké porovnání nebo neměnné datové struktury. Nepoužívejte == pro List nebo Map, protože se porovnávají podle reference.
Nejčastější chyba — vracení true bez porovnání. To vede k přestavbě všech závislých widgetů při každé aktualizaci rodiče, i když se data nezměnila. Druhá chyba — vracení false, když se data změnila, což vede k zastaralému UI. Třetí — složité porovnání, které se provádí každý snímek a zpomaluje práci.
InheritedWidget a callbacky (předávání funkcí přes konstruktor) řeší různé úkoly. InheritedWidget je vhodný pro data, která potřebuje mnoho widgetů na různých úrovních stromu. Callbacky jsou vhodné pro jednosměrné předávání událostí od rodiče ke konkrétnímu potomkovi nebo naopak. Výběr závisí na architektuře aplikace a frekvenci změn.
Použijte InheritedWidget, když data potřebuje mnoho widgetů na různých úrovních vnoření: téma aplikace, uživatelské preference, informace o zařízení, data aktuální relace. InheritedWidget je zvláště účinný pro "globální" data, která se mění zřídka, ale jsou potřebná v různých částech UI.
Callbacky (zpětné funkce) jsou vhodné pro předávání událostí z dětského widgetu rodičovskému: stisk tlačítka, výběr položky seznamu, odeslání formuláře. Callbacky explicitně ukazují, jaké akce může potomek provádět, a nevytvářejí skryté závislosti. Pro předávání dat dolů stromem na malý počet úrovní je také jednodušší použít parametry konstruktoru.
| Kritérium | InheritedWidget | Callbacky |
|---|---|---|
| Směr | Shora dolů (rodič → potomci) | Zespoda nahoru (potomek → rodič) nebo přímo |
| Oblast | Celý podstrom | Konkrétní widget |
| Přestavba | Automatická při změně dat | Vyžaduje ruční setState |
| Složitost | Střední (nutná třída InheritedWidget) | Nízká (jednoduchá funkce) |
Provider a Riverpod — populární knihovny pro správu stavu ve Flutteru, postavené na InheritedWidget. Rozšiřují jeho možnosti: přidávají podporu ChangeNotifier, automatické odstranění při demontáži, línou inicializaci a zjednodušenou syntaxi přes generika.
Provider používá InheritedWidget k předávání objektu libovolného typu dolů stromem. ChangeNotifierProvider sleduje změny přes ChangeNotifier a volá updateShouldNotify při volání notifyListeners. To zbavuje vývojáře ručního vytváření InheritedWidget a implementace updateShouldNotify.
Přímý InheritedWidget dává více kontroly a nevyžaduje externí závislosti. Provider poskytuje hotovou infrastrukturu: Consumer, Selector, MultiProvider, ProxyProvider. Výběr závisí na složitosti aplikace. Pro jednoduché projekty stačí přímý InheritedWidget, pro velké projekty Provider nebo Riverpod snižují šablonový kód.
// Přímý 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;
}
// Ekvivalent Provider
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
Oba přístupy v příkladu řeší stejný úkol — předávání UserData dolů stromem. Provider snižuje množství kódu, ale skrývá mechaniku InheritedWidget. Přímý InheritedWidget poskytuje plnou kontrolu a pochopení dění, což je zvláště důležité při učení Flutteru a ladění složitých problémů s přestavbou.
Často kladené otázky
InheritedWidget zpřístupňuje data všem potomkům přes BuildContext, zatímco běžný widget předává data pouze přes konstruktor. InheritedWidget také přihlašuje potomky k odběru aktualizací dat.
Závislé widgety se přestavují pouze když updateShouldNotify vrátí true. Pokud je metoda správně implementována, přestavba nastává pouze při skutečné změně dat, ne při každém rebuildu rodiče.
Ano, můžete použít libovolný počet InheritedWidget v jednom stromě. Každý poskytuje data určitého typu a widgety mohou přijímat data z více InheritedWidget současně.
dependOn přihlásí widget k odběru aktualizací — při změně dat se widget přestaví. findAncestor provádí jednorázové vyhledávání bez přihlášení a widget se o změnách dat nedozví.
Pro jednoduchý stav (téma, nastavení) InheritedWidget stačí. Pro složitý stav s obchodní logikou použijte Provider, Riverpod nebo BLoC — jsou postaveny na InheritedWidget a přidávají potřebnou infrastrukturu.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také