FutureBuilder — to widget we Flutter, który automatycznie przebudowuje swój interfejs na podstawie bieżącego stanu AsyncSnapshot, otrzymywanego z przekazanego Future. W przeciwieństwie do ręcznego wywoływania setState po await, FutureBuilder zapewnia deklaratywne podejście: subskrybuje Future przy pierwszym renderowaniu i wywołuje funkcję builder przy każdej zmianie stanu — ładowanie, błąd lub gotowe dane. Według Flutter API Reference (2026), FutureBuilder jest szczególnie przydatny do ładowania danych z sieci, odczytu z bazy danych i wszelkich operacji asynchronicznych, gdzie UI musi wyświetlać wskaźnik ładowania, komunikat o błędzie lub gotową treść.
Najważniejsze
FutureBuilder — to wbudowany widget Flutter z pakietu widgets, który przyjmuje Future
W przeciwieństwie do StreamBuilder, który pracuje ze strumieniami danych (Stream), FutureBuilder jest przeznaczony do jednorazowych operacji asynchronicznych: zapytanie HTTP, odczyt z pliku, zapytanie do bazy danych. FutureBuilder sam zarządza subskrypcją Future: przy pierwszym budowaniu uruchamia Future i śledzi jego zakończenie. Przy zniszczeniu widgetu FutureBuilder nie anuluje Future — to odpowiedzialność programisty.
Według Flutter Cookbook (2026), FutureBuilder jest zalecany w przypadkach, gdy operacja asynchroniczna uruchamiana jest raz przy inicjalizacji ekranu. Dla powtarzających się operacji lub strumieni danych używaj StreamBuilder. Oba widgety kierują się tym samym wzorcem Reactive UI, ale FutureBuilder jest zoptymalizowany pod kątem jednorazowych zapytań.
Wewnętrzna implementacja FutureBuilder subskrybuje Future za pomocą Future.then i catchError. Przy starcie FutureBuilder ustawia connectionState na ConnectionState.waiting i wywołuje builder z pustymi danymi. Po pomyślnym zakończeniu connectionState zmienia się na ConnectionState.done z danymi. Przy błędzie snapshot.error wypełniany jest obiektem błędu. Każda zmiana wyzwala przebudowę widgetu.
AsyncSnapshot — to obiekt-kontener, który FutureBuilder przekazuje do funkcji builder przy każdej zmianie stanu. Zawiera wszystkie informacje o bieżącym statusie operacji asynchronicznej: czy trwa ładowanie, jakie dane zostały otrzymane, czy wystąpił błąd. Zrozumienie AsyncSnapshot to klucz do prawidłowego budowania UI z FutureBuilder.
| Właściwość | Typ | Opis |
|---|---|---|
| connectionState | ConnectionState | Bieżący stan połączenia (none, waiting, active, done) |
| data | T? | Dane otrzymane z Future (null do zakończenia lub przy błędzie) |
| error | Object? | Obiekt błędu, jeśli Future zakończyła się wyjątkiem |
| hasData | bool | true, jeśli data nie jest null i stan to ConnectionState.done |
| hasError | bool | true, jeśli Future zakończyła się błędem |
Enum ConnectionState określa etap operacji asynchronicznej. None — stan początkowy, gdy Future nie został jeszcze uruchomiony (używany rzadko, zwykle przy pierwszym budowaniu bez initialData). Waiting — Future jest wykonywany, dane nie zostały jeszcze otrzymane. Active — używany tylko przez StreamBuilder dla strumieni z częściowymi danymi. Done — Future zakończony, dane dostępne przez snapshot.data lub błąd przez snapshot.error.
Prawidłowa obsługa wszystkich stanów AsyncSnapshot w funkcji builder — obowiązkowy wymóg dla kodu produkcyjnego. Jeśli nie obsłużysz stanu waiting, użytkownik zobaczy pusty ekran podczas ładowania. Jeśli nie obsłużysz hasError, użytkownik otrzyma Exception bez wyjaśnienia. Zalecany wzorzec: sprawdzenie hasError → sprawdzenie hasData → domyślnie pokazanie ładowania.
FutureBuilder można używać w kilku standardowych wzorcach, z których każdy rozwiązuje konkretne zadanie. Rozważmy główne scenariusze: ładowanie danych przy inicjalizacji, ładowanie z buforowaniem, równoległe zapytania i obsługa błędów z ponowieniem.
Najczęstszy wzorzec — FutureBuilder w metodzie build StatefulWidget lub StatelessWidget. Future jest przekazywany z initState lub tworzony bezpośrednio w build. Ważne, aby nie tworzyć Future w metodzie build przy każdym przebudowaniu — doprowadzi to do ponownych zapytań. Używaj Future zapisanego w polu State.
Aby zapobiec ponownym zapytaniom, FutureBuilder można łączyć z CachedNetworkImage lub lokalnym buforem. Po pierwszym załadowaniu dane są zapisywane w pamięci lub SharedPreferences, a FutureBuilder wyświetla buforowane dane natychmiast, równolegle aktualizując je z sieci. Poprawia to UX dzięki natychmiastowej odpowiedzi.
Według pub.dev (2026), buforowanie jest szczególnie istotne dla obrazów i list danych. FutureBuilder z CachedNetworkImageProvider automatycznie wyświetla buforowany obraz, a w przypadku jego braku — wskaźnik ładowania z następczym wyświetleniem pobranego pliku.
FutureBuilder i ręczne zarządzanie stanem przez setState — dwa podejścia do asynchronicznego UI we Flutter. Każde ma swoje zalety i ograniczenia. Wybór zależy od złożoności ekranu i liczby operacji asynchronicznych.
FutureBuilder wygrywa prostotą: nie trzeba deklarować pól dla stanu ładowania, danych i błędu — wszystko jest zarządzane przez AsyncSnapshot. Jest idealny dla prostych ekranów z jedną operacją asynchroniczną (jedno zapytanie HTTP, odczyt z bazy). Jednak przy 5+ operacjach asynchronicznych na jednym ekranie FutureBuilder tworzy nadmierne zagnieżdżenie — powstaje „piramida“ zagnieżdżonych FutureBuilder.
setState z ręcznymi flagami stanu daje więcej kontroli i czytelności przy złożonej logice. Dla ekranów z wieloma zależnymi zapytaniami (załadowanie użytkownika → załadowanie jego zamówień → załadowanie szczegółów zamówienia) lepiej użyć setState z ChangeNotifier lub Bloc. Według Flutter State Management Guide (2026), dla złożonych scenariuszy zaleca się używanie Riverpod lub Bloc zamiast FutureBuilder, ponieważ zapewniają one lepsze oddzielenie logiki od prezentacji.
Rozważmy praktyczny przykład FutureBuilder do ładowania listy użytkowników z REST API. Kod demonstruje prawidłową obsługę wszystkich trzech stanów AsyncSnapshot: ładowanie, błąd i gotowe dane.
class UserListPage extends StatefulWidget {
const UserListPage({super.key});
@override
State<UserListPage> createState() => _UserListPageState();
}
class _UserListPageState extends State<UserListPage> {
final Future<List<User>> usersFuture = UserRepository().fetchUsers();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Użytkownicy')),
body: FutureBuilder<List<User>>(
future: usersFuture,
builder: (context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text('Błąd: ${snapshot.error}'),
],
),
);
}
if (snapshot.hasData) {
final users = snapshot.data!;
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(backgroundImage: NetworkImage(users[index].avatarUrl)),
title: Text(users[index].name),
subtitle: Text(users[index].email),
);
},
);
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}
W przykładzie FutureBuilder obsługuje wszystkie trzy stany. Przy błędzie wyświetlana jest ikona z komunikatem o błędzie. Przy pomyślnym załadowaniu — ListView z awatarami i nazwami. Podczas ładowania — CircularProgressIndicator. Future jest zadeklarowane jako pole klasy, co zapobiega ponownemu wywołaniu przy przebudowie. Taki wzorzec pokrywa 90% scenariuszy użycia FutureBuilder w aplikacjach mobilnych.
Często zadawane pytania
FutureBuilder wywołuje builder przy każdej zmianie stanu Future: pierwszy raz przy utworzeniu (connectionState: none lub waiting), drugi raz przy zakończeniu (connectionState: done). Jeśli rodzicielski widget się przebudowuje, FutureBuilder również się przebudowuje. Aby zapobiec ponownym wywołaniom, upewnij się, że Future jest tworzone poza metodą build — w przeciwnym razie każde wywołanie build tworzy nowe Future.
Zapisz Future w polu StatefulWidget (w initState) lub użyj memoizacji. Jeśli Future jest tworzone wewnątrz metody build, każde wywołanie build będzie tworzyć nowe Future, a FutureBuilder uruchomi ponownie operację asynchroniczną. Dla StatelessWidget użyj pakietu cached_future lub widgetów keep-alive, aby Future wykonało się raz niezależnie od przebudowy.
FutureBuilder jest przeznaczony do jednorazowych operacji asynchronicznych (jedno zapytanie HTTP, jeden odczyt z bazy). StreamBuilder pracuje ze strumieniami danych, które mogą emitować wiele wartości w czasie (czat, aktualizacje ceny, geolokalizacja). StreamBuilder obsługuje ConnectionState.active dla danych częściowych, a FutureBuilder — tylko waiting i done.
Dla kilku równoległych Future użyj Future.wait i przekaż wynik do jednego FutureBuilder. Future.wait przyjmuje listę Future i zwraca Future — gdy wszystkie Future zostaną zakończone, builder otrzymuje tablicę wyników. Dla sekwencyjnych zapytań użyj łańcucha Future.then wewnątrz jednego Future lub zagnieżdżonych FutureBuilder (mniej czytelne). Alternatywą jest pakiet riverpod z AsyncValue dla wielokrotnych stanów asynchronicznych.
FutureBuilder nie anuluje Future automatycznie. Do anulowania użyj CancelableOperation z pakietu async lub własnego mechanizmu przez flagę cancelled w State. W dispose() ustaw flagę, a po zakończeniu Future sprawdź ją przed wywołaniem setState. Alternatywnie użyj pakietu riverpod z AutoDispose, który automatycznie anuluje operacje asynchroniczne przy wyjściu z ekranu.
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ż