BuildContext — fundamentalny obiekt Flutter, reprezentujący pozycję konkretnego widżetu w drzewie elementów i zapewniający dostęp do jego otoczenia. Według oficjalnej dokumentacji Flutter (Flutter.dev, 2026), BuildContext jest mostem między widżetem a frameworkiem: przez niego widżet otrzymuje motyw (Theme), zapytania medialne (MediaQuery), lokalizację (Localizations) i dane z InheritedWidget. Każdy widżet ma swój własny BuildContext, przekazywany do metody build jako pierwszy argument.
Najważniejsze
BuildContext — to interfejs implementowany przez klasę Element, który dostarcza widżetowi informacji o jego położeniu w hierarchii UI. Każda instancja BuildContext jest unikalna dla konkretnej pozycji w drzewie i nie może być przeniesiona w inne miejsce. Jeśli widżet zmienia swojego rodzica (np. zostaje przeniesiony do innego kontenera), otrzymuje nowy BuildContext.
Głównym przeznaczeniem BuildContext jest dostęp do InheritedWidget. Poprzez kontekst widżet znajduje najbliższą instancję Theme, MediaQuery, Navigator lub Directionality, wznosząc się w górę drzewa. Ten mechanizm leży u podstaw całego systemu motywów, nawigacji i responsywnego układu we Flutter. Bez BuildContext żaden widżet nie może uzyskać tych danych.
Według Flutter architectural docs (Google, 2026), BuildContext jest również używany do wyszukiwania obiektu RenderObject powiązanego z widżetem, do pomiaru rozmiarów i pozycjonowania. Metody takie jak findRenderObject() i size są dostępne właśnie przez kontekst. Kontekst zapewnia również dostęp do lokalizacji przez Localizations.of(context).
Ważne zrozumienie architektoniczne: BuildContext to interfejs, który implementuje Element, a nie Widget. Element to „klej“ między Widget (konfiguracją) a RenderObject (rzeczywistym wyświetlaniem). Gdy w dokumentacji mówi się „kontekst widżetu“ — chodzi o element zarządzający tym widżetem. Metoda build otrzymuje właśnie taki kontekst — kontekst tworzonego widżetu, a nie zwracanych widżetów potomnych.
Mechanizm działania BuildContext opiera się na przeszukiwaniu drzewa elementów od dołu do góry. Gdy widżet wywołuje Theme.of(context), kontekst rozpoczyna wyszukiwanie od bieżącego elementu i przesuwa się w górę do korzenia, sprawdzając każdy element pod kątem InheritedWidget z typem Theme. Pierwszy znaleziony InheritedWidget jest zwracany — gwarantuje to, że widżet otrzymuje motyw z najbliższej definicji.
Każdy BuildContext przechowuje referencję do kontekstu rodzica (parent) i do kontekstów potomnych. Jest to dwukierunkowe połączenie, umożliwiające poruszanie się po drzewie zarówno w górę (do rodziców), jak i w dół (do potomków). We Flutter do wyszukiwania InheritedWidget używany jest tylko ruch w górę — widżet może uzyskać dane tylko od przodków, nie od potomków. Jest to fundamentalne ograniczenie architektoniczne.
Według Flutter source code (Flutter SDK, 2026), BuildContext zawiera metody: visitAncestorElements, visitChildElements, findAncestorWidgetOfExactType, dependOnInheritedWidgetOfExactType i getRenderObject. Dwie ostatnie są najczęściej używane: dependOnInheritedWidgetOfExactType nie tylko znajduje InheritedWidget, ale także subskrybuje jego zmiany (widżet zostanie przebudowany przy zmianie InheritedWidget).
dependOnInheritedWidgetOfExactType — kluczowa metoda BuildContext zapewniająca reaktywność. Gdy widżet wywołuje Theme.of(context), nie tylko pobiera motyw — subskrybuje jego zmiany. Jeśli Theme się zmienia (np. przy przełączaniu ciemnego/jasnego motywu), wszystkie subskrybujące widżety są automatycznie przebudowywane. To właśnie mechanizm reaktywności we Flutter.
BuildContext to interfejs, a Element — jego implementacja. W kodzie Flutter zawsze pracujesz przez interfejs BuildContext, nie znając konkretnego typu elementu (StatelessElement, StatefulElement, ProxyElement itd.). Jest to celowe: programista nie musi znać szczegółów implementacji elementu — wystarczy interfejs do dostępu do otoczenia.
Różne typy elementów różnie implementują BuildContext: StatelessElement po prostu przekazuje wywołania build, StatefulElement zarządza State, a InheritedElement śledzi subskrypcje przez dependOnInheritedWidgetOfExactType. Jednak z punktu widzenia programisty wszystkie są BuildContext z jednolitym API.
| Aspekt | BuildContext | Element |
|---|---|---|
| Typ | Interfejs (abstract class) | Klasa implementująca |
| Użycie | Przez programistę w build | Wewnętrzny mechanizm Flutter |
| Metody wyszukiwania | of(), findAncestor...() | mount, update, unmount |
| Publiczność | Publiczne API | package-internal |
| Związek z widżetem | Przez pole widget | Posiada widget i state |
Podstawowe użycie BuildContext do dostępu do motywu i zapytań medialnych:
class ThemedText extends StatelessWidget {
const ThemedText({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final media = MediaQuery.of(context);
return Container(
padding: EdgeInsets.all(media.size.width * 0.02),
child: Text(
'Styled Text',
style: theme.textTheme.headlineMedium,
),
);
}
}
Przykład z nawigacją przez BuildContext. Navigator.of(context) używa kontekstu do wyszukania najbliższego Navigator w górę drzewa:
class _NavigateButtonState extends State<NavigateButton> {
void _navigate() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const DetailsScreen(),
),
);
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _navigate,
child: const Text('Go to Details'),
);
}
}
Przykład pobierania rozmiaru widżetu przez BuildContext. Metoda findRenderObject() zwraca RenderObject, z którego można uzyskać rozmiar:
void _printSize(BuildContext context) {
final renderBox = context.findRenderObject() as RenderBox?;
if (renderBox != null) {
print('Widget size: ${renderBox.size}');
}
}
Ważne: findRenderObject() zwraca null, jeśli widżet nie jest jeszcze zamontowany lub już został odmontowany. Zawsze sprawdzaj wynik na null przed użyciem. Wywołanie tej metody wewnątrz build przed zakończeniem budowania również może zwrócić null.
InheritedWidget — specjalny widżet, który efektywnie rozpowszechnia dane w dół drzewa przez BuildContext. Gdy widżet potomny wywołuje MyInheritedWidget.of(context), BuildContext wznosi się po drzewie, znajduje najbliższy InheritedWidget odpowiedniego typu i zwraca jego dane. Przy tym kontekst subskrybuje zmiany: jeśli InheritedWidget się zmienia, wszystkie subskrybujące widżety są automatycznie przebudowywane.
Połączenie BuildContext + InheritedWidget zastępuje zmienne globalne i prop-drilling (przekazywanie danych przez łańcuch konstruktorów). Zamiast przekazywać motyw przez 10 poziomów widżetów, każdy widżet może go pobrać bezpośrednio przez Theme.of(context). To czyni kod czystszym i zmniejsza liczbę przekazywanych parametrów.
Według Flutter Team (Google, kwiecień 2026), InheritedWidget jest na tyle efektywnym mechanizmem, że na jego podstawie zbudowano wszystkie oficjalne rozwiązania do zarządzania stanem: Provider opakowuje InheritedWidget, Riverpod używa go jako jednej z warstw, a sam Flutter SDK (Theme, MediaQuery, Navigator, Localizations) jest w pełni oparty na tej architekturze.
Stworzenie własnego InheritedWidget pozwala rozpowszechniać dane bez zewnętrznych zależności. Klasa rozszerza InheritedWidget i udostępnia statyczną metodę of(BuildContext context). Jest to minimalistyczna alternatywa dla Provider w prostych scenariuszach:
class AppConfig extends InheritedWidget {
final String apiUrl;
final bool useDarkMode;
const AppConfig({
super.key,
required this.apiUrl,
required this.useDarkMode,
required super.child,
});
static AppConfig of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AppConfig>()!;
}
@override
bool updateShouldNotify(AppConfig oldWidget) {
return apiUrl != oldWidget.apiUrl || useDarkMode != oldWidget.useDarkMode;
}
}
Teraz każdy widżet niżej w drzewie może uzyskać dostęp do konfiguracji: final config = AppConfig.of(context);. Jeśli konfiguracja się zmieni, wszystkie subskrybujące widżety zostaną automatycznie przebudowane.
Pierwszy typowy błąd — przechowywanie BuildContext po dispose lub używanie go w asynchronicznym callbacku bez sprawdzenia mounted. BuildContext jest powiązany z elementem, a element może być zniszczony (przy usunięciu widżetu z drzewa). Użycie kontekstu po zniszczeniu elementu prowadzi do wyjątku. Rozwiązanie — używać context.mounted (dostępne w nowych wersjach Flutter) lub sprawdzać mounted w State.
Drugi błąd — wywołanie Theme.of(context) w initState. Na etapie initState kontekst nie jest jeszcze w pełni zamontowany w drzewie. Wyszukiwanie InheritedWidget w initState może zwrócić null lub rzucić wyjątek. Wszystkie wywołania of(context) powinny być wykonywane w build lub didChangeDependencies, gdzie kontekst jest gwarantowanie w drzewie.
Trzeci błąd — używanie BuildContext z jednego widżetu do manipulacji innym widżetem. BuildContext nie jest przeznaczony do międzywidżetowej interakcji poza hierarchią „rodzic-potomok“. Jeśli trzeba zarządzać stanem innego widżetu — używaj callbacków, kontrolerów lub narzędzi do zarządzania stanem.
Czwarty błąd — przekazywanie BuildContext do funkcji asynchronicznej, która przeżywa dispose widżetu. Typowy scenariusz: Navigator.of(context) zapisany w zmiennej i używany po tym, jak użytkownik opuścił ekran. Rozwiązanie — nie przechowywać kontekstu w statycznych lub długożyjących obiektach.
Wzorzec bezpieczeństwa do pracy z BuildContext w operacjach asynchronicznych: zawsze sprawdzać mounted przed użyciem kontekstu i nie przechowywać kontekstu w domknięciach, które mogą przeżyć widżet:
Future<void> _safeNavigation(BuildContext context) async {
await Future.delayed(const Duration(seconds: 2));
if (!context.mounted) return;
Navigator.of(context).push(MaterialPageRoute(...));
}
Praca z BuildContext wymaga zrozumienia jego cyklu życia i ograniczeń. Pierwsza zasada: używaj kontekstu tylko wewnątrz metod, które otrzymują go jako parametr (build, didChangeDependencies). Nie przechowuj kontekstu w polach klasy lub zmiennych statycznych — to prawie zawsze prowadzi do błędów.
Druga zasada: do dostępu do danych z InheritedWidget preferuj didChangeDependencies zamiast build. Jeśli dane są potrzebne tylko do inicjalizacji, a nie do renderowania, didChangeDependencies jest odpowiednim miejscem. Pozwala to oddzielić logikę inicjalizacji od budowania UI i uniknąć wielokrotnych wywołań przy każdej aktualizacji.
Trzecia zasada: przy pracy z operacjami asynchronicznymi używaj callbacków, które nie zależą od kontekstu, lub sprawdzaj mounted. Jeśli operacja asynchroniczna wymaga nawigacji lub dostępu do motywu, pobierz te dane wcześniej (w synchronicznym kontekście build lub initState) i przechowaj w zmiennych lokalnych, a nie w kontekście.
Często zadawane pytania
BuildContext — interfejs reprezentujący pozycję widżetu w drzewie elementów. Przez niego widżet uzyskuje dostęp do otoczenia: motywu, zapytań medialnych, nawigatora i danych z InheritedWidget. Każdy widżet ma swój własny unikalny kontekst.
BuildContext przeszukuje drzewo od bieżącego elementu w górę do korzenia, znajdując najbliższy InheritedWidget żądanego typu. Metoda dependOnInheritedWidgetOfExactType nie tylko znajduje dane, ale także subskrybuje widżet na ich zmiany — przy aktualizacji InheritedWidget widżet jest automatycznie przebudowywany.
BuildContext jest powiązany z elementem w drzewie, a element może zostać zniszczony (widżet usunięty). Użycie zachowanego kontekstu po usunięciu widżetu prowadzi do wyjątku. Jeśli kontekst jest potrzebny w asynchronicznym callbacku — sprawdzaj mounted przed użyciem.
BuildContext to interfejs, Element — implementacja. Programista pracuje przez BuildContext, nie znając konkretnego typu elementu. Element — wewnętrzny mechanizm Flutter, łączący Widget z RenderObject i zarządzający cyklem życia.
Bezpośredniego dostępu do kontekstu innego widżetu nie ma. Do rodzicielskiego kontekstu używaj context.findAncestorStateOfType dla State lub klucze (GlobalKey). Do potomnego — przekaż callback. BuildContext nie jest przeznaczony do międzywidżetowego dostępu poza hierarchią.
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ż