StatefulWidget: co to jest, cykl życia i zasada działania

Autor: IT Sectr Opublikowano: 2026-07-01 Czas czytania: 9 min

StatefulWidget — widget Flutter z zmiennym stanem, pozwalający UI reagować na działania użytkownika, zdarzenia asynchroniczne i strumienie danych. Według oficjalnej dokumentacji Flutter (Flutter.dev, 2026), StatefulWidget jest używany dla wszystkich interaktywnych elementów aplikacji: formularzy, animacji, pól wyboru, przełączników i ekranładujących dane z sieci. W przeciwieństwie do StatelessWidget, tworzy on oddzielny obiekt State, który utrzymuje się przez cały cykl życia i może być przebudowany bez ponownego tworzenia samego widgetu.

Najważniejsze

  • StatefulWidget — widget, który może zmieniać swój stan podczas działania, wywołując przebudowę UI przez setState
  • Cykl życia — StatefulWidget przechodzi przez etapy createState, initState, didChangeDependencies, build, didUpdateWidget, dispose
  • State object — oddzielny obiekt przechowujący stan i istniejący niezależnie od widgetu przez cały czas jego życia
  • setState — jedyny legalny sposób powiadomienia Flutter o konieczności przebudowy widgetu po zmianie danych
  • Wydajność — nadmierne używanie StatefulWidget zwiększa zużycie pamięci i czas renderowania

Czym jest StatefulWidget?

StatefulWidget — to klasa Flutter, która może zmieniać swój stan w odpowiedzi na działania użytkownika, zdarzenia systemowe lub operacje asynchroniczne. W przeciwieństwie do StatelessWidget, StatefulWidget nie jest wyświetlany bezpośrednio — tworzy obiekt State, który odpowiada za renderowanie. Podział na dwie klasy (Widget i State) pozwala Flutter przebudowywać UI bez ponownego tworzenia samego widgetu, co daje znaczną przewagę wydajnościową przy częstych aktualizacjach.

Architektura StatefulWidget opiera się na wzorcu «rozdzielenia zmiennego i niezmiennego»: sam widget pozostaje niezmienny (jak StatelessWidget), a cały zmienny stan jest przechowywany w oddzielnym obiekcie State. Pozwala to Flutter ponownie używać widgetów, porównując je według typu i Key, a jednocześnie zachowywać aktualny stan między przebudowami.

Według Google (Flutter Architectural Overview, 2026), StatefulWidget jest optymalny dla scenariuszy, w których stan zmienia się częściej niż raz w ciągu życia widgetu: pola tekstowe, animacje, timery, strumienie danych, asynchroniczne ładowanie. Do jednorazowej inicjalizacji wystarczy StatelessWidget.

Kiedy StatefulWidget jest niezbędny

StatefulWidget jest obowiązkowy, gdy widget musi reagować na zdarzenia zewnętrzne: naciśnięcie przycisku, zakończenie zapytania HTTP, aktualizacja danych z bazy, subskrypcja WebSocket. Jest również niezbędny dla widgetów z animacją, pól tekstowych z kontrolerami i komponentów zarządzających fokusem. Jeśli widget tylko wyświetla dane i nie generuje zdarzeń — użyj StatelessWidget.

Budowa wewnętrzna

StatefulWidget składa się z dwóch klas: właściwego StatefulWidget (lekkiego, niezmiennego) i State (ciężkiego, zmiennego). Framework tworzy State za pomocą metody createState(), wywoływanej jednokrotnie przy osadzaniu w drzewie. State otrzymuje odniesienie do widgetu przez właściwość widget i może odwoływać się do jego pól w dowolnym momencie cyklu życia.

Cykl życia StatefulWidget

Cykl życia StatefulWidget składa się z sześciu głównych etapów, z których każdy udostępnia metodę do nadpisywania w celu wykonywania specyficznych zadań. Zrozumienie tych etapów jest kluczowe dla prawidłowej pracy z zasobami i unikania wycieków pamięci.

createState

createState — pierwsza metoda cyklu życia, wywoływana przy osadzaniu StatefulWidget w drzewie. Musi zwracać nową instancję State powiązaną z tym widgetem. Ta metoda jest wywoływana dokładnie raz przez cały czas życia elementu. Ważne jest, aby nie wykonywać tu ciężkich operacji — createState powinien być maksymalnie lekki.

initState

initState — wywoływany zaraz po utworzeniu State, przed pierwszym zbudowaniem UI. Tutaj wykonuje się: inicjalizację kontrolerów (TextEditingController, AnimationController), subskrypcję strumieni danych (StreamSubscription), konfigurację timerów i początkową inicjalizację pól. Według Flutter docs (Flutter.dev, 2026), w initState nie można wywoływać BuildContext.of() — drzewo nie jest jeszcze w pełni zamontowane.

didChangeDependencies

didChangeDependencies — wywoływany po initState i za każdym razem, gdy zmieniają się zależności InheritedWidget. To odpowiednie miejsce do wywołania MediaQuery.of(context) lub subskrypcji Theme — wartości, które mogą się zmienić podczas działania aplikacji. Jeśli widget używa InheritedWidget, logika inicjalizacji powinna być tutaj, a nie w initState.

build i didUpdateWidget

build — główna metoda zwracająca drzewo widgetów. Wywoływana po initState, po didChangeDependencies i po każdym setState. didUpdateWidget wywoływany, gdy rodzic przebudowuje się i przekazuje StatefulWidget z nowymi parametrami. Tutaj można porównać stare i nowe pola widgetu i, w razie potrzeby, zaktualizować stan.

dispose

dispose — końcowy etap cyklu życia. Tutaj zwalniane są wszystkie zasoby: anulowane subskrypcje strumieni, usuwane kontrolery, anulowane timery. Brak wywołania dispose prowadzi do wycieków pamięci. Po dispose State jest uważany za martwy — wywołanie setState wewnątrz niego powoduje wyjątek.

Jak działa StatefulWidget?

Mechanizm działania StatefulWidget opiera się na skoordynowanej pracy trzech jednostek: Widget (lekkiego opisu), Element (pośredniej warstwy) i State (magazynu danych). Gdy Flutter napotyka StatefulWidget w opisie, tworzy StatefulElement, który wywołuje createState i przechowuje referencję do obiektu State. Przy przebudowie rodzica Flutter porównuje nowy widget z bieżącym Element — jeśli typ i Key są zgodne, Element jest aktualizowany, a State pozostaje ten sam.

Stan zmienia się tylko przez wywołanie setState, które powiadamia framework o konieczności przebudowy. Ważne jest zrozumienie: setState nie zmienia stanu automatycznie — oznacza jedynie widget jako «brudny». Deweloper samodzielnie aktualizuje pola State w callbacku przekazanym do setState. Po zakończeniu callbacka Flutter wywołuje build i aktualizuje UI.

Według Dart/Flutter team (Dart Language Specification, 2026), taki podział gwarantuje, że wszystkie zmiany stanu następują synchronicznie przed wywołaniem build, eliminując sytuację, w której UI wyświetla częściowo zaktualizowane dane. To kluczowy mechanizm spójności interfejsu we Flutter.

Przykłady kodu w Dart

Rozważmy prosty StatefulWidget — licznik kliknięć przycisku. Pokazuje on podstawowy wzorzec: tworzenie State, inicjalizację pola w initState, zmianę przez setState:

dart
class CounterScreen extends StatefulWidget {
  const CounterScreen({super.key});

  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Liczba: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Zwiększ'),
        ),
      ],
    );
  }
}

Przykład z asynchronicznym ładowaniem danych i zarządzaniem cyklem życia. StatefulWidget ładuje dane z sieci i wyświetla stan ładowania:

dart
class UserProfilePage extends StatefulWidget {
  final String userId;
  const UserProfilePage({super.key, required this.userId});

  @override
  State<UserProfilePage> createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  UserModel? _user;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadUser();
  }

  Future<void> _loadUser() async {
    final user = await UserService.fetchUser(widget.userId);
    setState(() {
      _user = user;
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) return const CircularProgressIndicator();
    return Text('Witaj, ${_user!.name}');
  }
}

W drugim przykładzie ważne jest: initState uruchamia operację asynchroniczną, ale sama metoda nie jest asynchroniczna. Asynchroniczność jest realizowana przez async/await wewnątrz oddzielnej metody _loadUser, która aktualizuje stan przez setState po zakończeniu żądania. Takie podejście gwarantuje, że widget poprawnie wyświetli wskaźnik ładowania przed otrzymaniem danych.

StatefulWidget vs StatelessWidget

Wybór między StatefulWidget a StatelessWidget to nie tylko kwestia posiadania stanu. StatefulWidget zapewnia pełny cykl życia z metodami initState, didChangeDependencies, didUpdateWidget i dispose, co jest niezbędne do pracy z kontrolerami, animacjami i strumieniami. StatelessWidget z kolei nie ma tych metod i jest zawsze lżejszy dla frameworka.

Zalecenie Flutter team (Flutter docs, 2026) — minimalizować liczbę StatefulWidget w aplikacji, podnosząc stan w górę drzewa (State Hoisting) lub używając rozwiązań do zarządzania stanem (Riverpod, Bloc, Provider). Każdy StatefulWidget tworzy obiekt State, który żyje do usunięcia elementu — im więcej takich widgetów, tym większe obciążenie pamięci.

KryteriumStatefulWidgetStatelessWidget
StanZmiennyNiezmienny
Cykl życia6 etapówTylko build
Obiekt StateTworzony oddzielnieNie jest wymagany
setStateDostępnyNiedostępny
SubskrypcjeinitState/disposeNieobsługiwane
const konstruktorOgraniczonyW pełni obsługiwany
Zużycie pamięciWiększeMniejsze

Wydajność i optymalizacja

StatefulWidget wymaga więcej zasobów niż StatelessWidget ze względu na konieczność tworzenia i utrzymywania obiektu State. Jednak prawidłowe używanie StatefulWidget nie prowadzi do problemów z wydajnością, jeśli przestrzega się kilku zasad. Po pierwsze, unikaj głębokiego zagnieżdżania StatefulWidget — każdy poziom dodaje narzut na przechodzenie drzewa. Po drugie, dziel złożone StatefulWidget na kilka prostych, z których każdy odpowiada za swoją część stanu.

Według badań Flutter Performance (Flutter.dev, luty 2026), najczęstszą przyczyną spadków FPS jest wywołanie setState w nadrzędnym widgecie, które przebudowuje wszystkich potomków, w tym StatelessWidget, które nie zmieniły swojego wyświetlania. Rozwiązaniem jest wydzielenie zmiennej części UI do oddzielnego StatefulWidget, aby setState przebudowywał tylko minimum niezbędnych widgetów.

Używanie const wewnątrz State to kolejna ważna technika. Jeśli podrzędne widgety są zadeklarowane jako const, Flutter nie będzie ich przebudowywać przy wywołaniu setState w rodzicu. Zmniejsza to obciążenie frameworka i skraca czas renderowania klatki.

Unikaj częstych setState

Każde wywołanie setState uruchamia pełną przebudowę widgetu. Jeśli stan zmienia się z dużą częstotliwością (na przykład animacja lub strumień danych), rozważ użycie AnimatedBuilder, ValueListenableBuilder lub StreamBuilder zamiast ręcznego wywoływania setState. Te widgety optymalizują przebudowę, aktualizując tylko tę część UI, która rzeczywiście się zmieniła.

Typowe błędy

Pierwszym typowym błędem z StatefulWidget jest wywołanie setState po dispose. Gdy widget zostanie usunięty z drzewa, State jest uważany za martwy i każde wywołanie setState powoduje wyjątek «setState called after dispose». Najczęściej dzieje się tak, gdy operacja asynchroniczna kończy się po usunięciu widgetu. Rozwiązaniem jest sprawdzanie flagi mounted przed wywołaniem setState lub anulowanie operacji asynchronicznych w dispose.

Drugim błędem jest wykonywanie ciężkich obliczeń w metodzie build. Ponieważ build jest wywoływany przy każdym setState i przy każdej przebudowie rodzica, wszystkie obliczenia powinny być maksymalnie lekkie. Jeśli potrzebujesz wykonać operację zasobożerną — wynieś ją do oddzielnego izolatu (Isolate) lub zapisz wynik w polu State.

Trzecim błędem jest brak wywołania super.initState() i super.dispose(). Nadpisując te metody, programista ma obowiązek wywołać implementację rodzica. Jeśli tego nie zrobi, framework nie będzie mógł poprawnie zarządzać stanem Element, co doprowadzi do trudnych do wykrycia błędów.

Zalecenia dotyczące unikania błędów

  • Zawsze sprawdzaj mounted przed setState w callbackach asynchronicznych
  • Nie zapominaj wywoływać super.initState() i super.dispose()
  • Nie wykonuj zapytań HTTP bezpośrednio w build — użyj initState
  • Anuluj wszystkie subskrypcje w dispose
  • Używaj minimalnej liczby StatefulWidget w projekcie

Często zadawane pytania

Czym StatefulWidget różni się od StatelessWidget?

StatefulWidget może zmieniać swój stan przez setState, ma cykl życia (initState, dispose) i tworzy oddzielny obiekt State. StatelessWidget nie może zmieniać stanu i nie ma metod cyklu życia — po prostu wyświetla przekazane dane.

Ile razy wywoływane jest createState?

createState jest wywoływane dokładnie raz dla każdej instancji StatefulElement. Nawet jeśli rodzic przebudowuje się wielokrotnie, dopóki typ i Key widgetu się nie zmieniają, createState nie jest wywoływane — używany jest istniejący obiekt State.

Co się stanie, jeśli nie wywołać dispose?

Zasoby nie zostaną zwolnione: kontrolery będą działać w tle, subskrypcje strumieni pozostaną aktywne, timery nie zostaną anulowane. Prowadzi to do wycieków pamięci i może powodować wywołanie setState po dispose, co powoduje wyjątek.

Czy StatefulWidget może być const?

Tak, konstruktor StatefulWidget może być const. Nie daje to jednak takiej samej korzyści jak dla StatelessWidget — obiekt State i tak zostanie utworzony przy pierwszym osadzeniu. const wpływa tylko na sam widget (lekką otoczkę), nie na State.

Po co jest metoda didUpdateWidget?

didUpdateWidget jest wywoływany, gdy rodzic przekazuje StatefulWidget z nowymi parametrami. Jest to potrzebne do synchronizacji stanu z nowymi danymi — na przykład, jeśli zmienił się userId w parametrach, należy załadować profil nowego użytkownika.

Podsumowanie

  • StatefulWidget — widget ze zmiennym stanem, używający oddzielnego obiektu State do przechowywania danych i zarządzania cyklem życia
  • Cykl życia składa się z createState, initState, didChangeDependencies, build, didUpdateWidget i dispose, z których każdy ma swoje przeznaczenie
  • setState — jedyny legalny sposób powiadomienia frameworka o zmianie stanu, po którym automatycznie wywoływane jest build
  • mounted — flaga, którą należy sprawdzać przed wywołaniem setState w operacjach asynchronicznych, aby uniknąć wyjątku po dispose
  • Wydajność — StatefulWidget wymaga więcej zasobów niż StatelessWidget; zaleca się minimalizować ich liczbę poprzez wyniesienie stanu do zewnętrznych warstw
  • const widgety potomne wewnątrz State pozwalają zmniejszyć zakres przebudowy przy wywołaniu setState, poprawiając wydajność
  • Właściwy wybór — używaj StatefulWidget tylko wtedy, gdy widget musi zarządzać zmiennymi danymi lub operacjami asynchronicznymi

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.

Omów projekt

Przeczytaj również