InheritedWidget — to specjalny widget we Flutterze, który przekazuje dane w dół drzewa widgetów bez jawnego przekazywania przez konstruktory. Widgety potomne uzyskują dostęp do danych poprzez BuildContext i automatycznie subskrybują aktualizacje. Gdy dane w InheritedWidget zmieniają się, wszystkie zależne widgety są przebudowywane. Według Flutter API Reference, 2025, InheritedWidget leży u podstaw Theme, MediaQuery, Localizations i większości bibliotek zarządzania stanem.
Najważniejsze
InheritedWidget — to widget, który udostępnia swoje dane wszystkim potomkom w Widget Tree. W przeciwieństwie do zwykłego widgetu, który przekazuje dane tylko przez konstruktor do elementów potomnych, InheritedWidget pozwala dowolnemu widgetowi w poddrzewie uzyskać dostęp do danych bez łańcucha parametrów. Rozwiązuje to problem "prop drilling" — przekazywania danych przez wiele pośrednich widgetów, które same nie korzystają z tych danych.
Flutter zawiera kilka wbudowanych InheritedWidget: Theme (schemat kolorów i style), MediaQuery (rozmiar ekranu, orientacja, gęstość pikseli), Localizations (zlokalizowane ciągi znaków), Directionality (kierunek tekstu), DefaultTextStyle (domyślny styl tekstu). Te widgety są ustawiane przez widgety główne takie jak MaterialApp i są dostępne w całej aplikacji.
InheritedWidget nie ma własnego stanu — przechowuje dane przekazane przez konstruktor. Gdy rodzic InheritedWidget przebudowuje się z nowymi danymi, wywoływana jest metoda updateShouldNotify w celu porównania starych i nowych danych. Jeśli metoda zwraca true, wszystkie zależne widgety są oznaczane do przebudowy. To prosty, ale skuteczny mechanizm reaktywnej aktualizacji.
Mechanizm przekazywania danych przez InheritedWidget opiera się na Element Tree. Gdy widget wywołuje dependOnInheritedWidgetOfExactType, odpowiedni element rejestruje zależność od InheritedElement. Przy zmianie InheritedWidget, InheritedElement powiadamia wszystkie zależne elementy, które przebudowują się w następnej klatce.
Metoda dependOnInheritedWidgetOfExactType nie tylko znajduje InheritedWidget w drzewie — subskrybuje bieżący element na powiadomienia. Gdybyś użył findAncestorWidgetOfExactType zamiast dependOn, widget otrzymałby dane, ale nie przebudowywałby się przy ich zmianie. To ważna różnica: dependOn — to subskrypcja, a findAncestor — jednorazowe wyszukiwanie.
Gdy widget żąda InheritedWidget, Flutter wznosi się po Element Tree od bieżącego elementu do korzenia, sprawdzając każdy InheritedElement pod kątem zgodności typu. Pierwszy znaleziony InheritedElement jest zwracany. Oznacza to, że najbliższy InheritedWidget w drzewie ma priorytet — możesz nadpisać dane na określonym poziomie, umieszczając InheritedWidget bliżej potomków.
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;
}
W tym przykładzie MyTheme używa statycznej metody of do udostępniania danych potomkom. Metoda dependOnInheritedWidgetOfExactType rejestruje zależność, a updateShouldNotify porównuje stare i nowe dane w celu określenia konieczności przebudowy zależnych widgetów.
Tworzenie własnego InheritedWidget składa się z dwóch kroków: zdefiniowanie klasy dziedziczącej po InheritedWidget i implementacja statycznej metody of do dostępu z potomków. Dane są przekazywane przez konstruktor, a metoda updateShouldNotify określa, kiedy zależne widgety powinny się przebudowywać.
Klasa musi dziedziczyć po InheritedWidget i przyjmować dane przez konstruktor z obowiązkowym parametrem child. Dane mogą być dowolnego typu: prymitywy, obiekty, funkcje. Główna zasada — dane muszą być niezmienne (immutable), aby można było niezawodnie porównywać stare i nowe wartości.
Statyczna metoda of przyjmuje BuildContext i zwraca dane InheritedWidget. Wewnątrz wywoływana jest dependOnInheritedWidgetOfExactType, która szuka najbliższego InheritedWidget określonego typu w drzewie. Jeśli InheritedWidget nie zostanie znaleziony, metoda rzuca wyjątek lub zwraca wartość domyślną w zależności od implementacji.
Aby uzyskać dostęp do danych, widget wywołuje MyWidget.of(context) wewnątrz metody build. Flutter automatycznie subskrybuje widget na aktualizacje. Jeśli dane się zmienią, widget przebuduje się w następnej klatce. Pozwala to tworzyć czysty i deklaratywny kod bez zbędnych parametrów.
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;
}
W tym przykładzie UserPreferences przechowuje ustawienia użytkownika. Metoda updateShouldNotify porównuje każde pole osobno, co zapobiega niepotrzebnym przebudowom przy zmianie tylko jednego parametru. Zastosuj podobne podejście dla własnych InheritedWidget z wieloma polami.
updateShouldNotify — to kluczowa metoda InheritedWidget, która określa, czy należy powiadomić zależne widgety o zmianie danych. Jeśli metoda zwraca false, zależne widgety nie przebudowują się, nawet jeśli sam InheritedWidget otrzymał nową instancję z tymi samymi danymi. Jest to krytyczne dla wydajności.
Porównuj tylko te pola, które rzeczywiście się zmieniły i wpływają na wyświetlanie. Jeśli InheritedWidget zawiera 10 pól, ale tylko jedno z nich wpływa na UI, sprawdzaj tylko to pole. Dla kolekcji używaj głębokiego porównania lub niezmiennych struktur danych. Nie używaj == dla List lub Map, ponieważ są porównywane przez referencję.
Najczęstszy błąd — zwracanie true bez porównania. Prowadzi to do przebudowy wszystkich zależnych widgetów przy każdej aktualizacji rodzica, nawet jeśli dane się nie zmieniły. Drugi błąd — zwracanie false, gdy dane się zmieniły, co prowadzi do nieaktualnego UI. Trzeci — złożone porównanie, które wykonuje się każdej klatki i spowalnia działanie.
InheritedWidget i callbacki (przekazywanie funkcji przez konstruktor) rozwiązują różne zadania. InheritedWidget nadaje się do danych, które są potrzebne wielu widgetom na różnych poziomach drzewa. Callbacki są wygodne do jednokierunkowego przekazywania zdarzeń od rodzica do konkretnego potomka lub odwrotnie. Wybór zależy od architektury aplikacji i częstotliwości zmian.
Używaj InheritedWidget, gdy dane są potrzebne wielu widgetom na różnych poziomach zagnieżdżenia: motyw aplikacji, ustawienia użytkownika, informacje o urządzeniu, dane bieżącej sesji. InheritedWidget jest szczególnie efektywny dla danych "globalnych", które zmieniają się rzadko, ale są potrzebne w różnych częściach UI.
Callbacki (funkcje zwrotne) nadają się do przekazywania zdarzeń od widgetu potomnego do rodzicielskiego: naciśnięcie przycisku, wybór elementu listy, wysłanie formularza. Callbacki jawnie wskazują, jakie działania może wykonać potomek, i nie tworzą ukrytych zależności. Do przekazywania danych w dół drzewa na niewielką liczbę poziomów również prościej użyć parametrów konstruktora.
| Kryterium | InheritedWidget | Callbacki |
|---|---|---|
| Kierunek | Z góry na dół (rodzic → potomkowie) | Z dołu do góry (potomek → rodzic) lub bezpośrednio |
| Zasięg | Całe poddrzewo | Konkretny widget |
| Przebudowa | Automatyczna przy zmianie danych | Wymaga setState ręcznie |
| Złożoność | Średnia (potrzebna klasa InheritedWidget) | Niska (zwykła funkcja) |
Provider i Riverpod — popularne biblioteki zarządzania stanem we Flutterze, zbudowane na InheritedWidget. Rozszerzają one jego możliwości: dodają obsługę ChangeNotifier, automatyczne usuwanie przy demontażu, leniwą inicjalizację i uproszczoną składnię przez generyki.
Provider używa InheritedWidget do przekazywania obiektu dowolnego typu w dół drzewa. ChangeNotifierProvider śledzi zmiany przez ChangeNotifier i wywołuje updateShouldNotify przy wywołaniu notifyListeners. To zwalnia programistę z ręcznego tworzenia InheritedWidget i implementacji updateShouldNotify.
Bezpośredni InheritedWidget daje więcej kontroli i nie wymaga zewnętrznych zależności. Provider dostarcza gotową infrastrukturę: Consumer, Selector, MultiProvider, ProxyProvider. Wybór zależy od złożoności aplikacji. Dla prostych projektów bezpośredni InheritedWidget jest wystarczający, dla dużych — Provider lub Riverpod zmniejszają kod szablonowy.
// Bezpośredni 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;
}
// Odpowiednik Providera
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
Oba podejścia w przykładzie rozwiązują to samo zadanie — przekazanie UserData w dół drzewa. Provider skraca ilość kodu, ale ukrywa mechanikę InheritedWidget. Bezpośredni InheritedWidget daje pełną kontrolę i zrozumienie procesu, co jest szczególnie ważne przy nauce Fluttera i debugowaniu złożonych problemów z przebudową.
Często zadawane pytania
InheritedWidget udostępnia dane wszystkim potomkom przez BuildContext, a zwykły widget przekazuje dane tylko przez konstruktor. InheritedWidget również subskrybuje potomków na aktualizacje danych.
Zależne widgety przebudowują się tylko wtedy, gdy updateShouldNotify zwraca true. Jeśli metoda jest poprawnie zaimplementowana, przebudowa następuje tylko przy rzeczywistej zmianie danych, a nie przy każdym rebuild rodzica.
Tak, można używać dowolnej liczby InheritedWidget w jednym drzewie. Każdy dostarcza dane określonego typu, a widgety mogą pobierać dane z wielu InheritedWidget jednocześnie.
dependOn subskrybuje widget na aktualizacje — przy zmianie danych widget przebuduje się. findAncestor wykonuje jednorazowe wyszukiwanie bez subskrypcji i widget nie dowie się o zmianach danych.
Dla prostego stanu (motyw, ustawienia) InheritedWidget jest wystarczający. Dla złożonego stanu z logiką biznesową używaj Provider, Riverpod lub BLoC — są one zbudowane na InheritedWidget i dodają niezbędną infrastrukturę.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również