Provider — pakiet do zarządzania stanem dla Flutter, stworzony przez Rémi Rousselet w 2019 roku jako nakładka na InheritedWidget. Provider rozwiązuje problem przesyłania danych w dół drzewa widgetów bez props drilling: każdy widget uzyskuje dostęp do stanu przez context.read<T>() lub context.watch<T>(). Według danych pub.dev, Provider jest najpopularniejszym menedżerem stanu Flutter z ponad 25 tysiącami polubień.
Najważniejsze
Provider — pakiet do zarządzania stanem i wstrzykiwania zależności we Flutter, zbudowany na bazie InheritedWidget. Provider udostępnia obiekt (stan, serwis, repozytorium) w drzewie widgetów i automatycznie przebudowuje UI przy zmianie danych. W przeciwieństwie do bezpośredniego użycia InheritedWidget, Provider eliminuje cały boilerplate: nie trzeba pisać podklasy InheritedWidget, konfigurować statycznej metody of() ani zarządzać zagnieżdżeniem.
Provider — oficjalnie zalecany przez Google sposób zarządzania stanem we Flutter (Flutter Team, 2019-2023). Pakiet wchodzi w skład Flutter Ecosystem i jest wspierany przez zespół Flutter. W momencie wydania Provider został zaproponowany jako zamiennik globalnych zmiennych i InheritedWidget: każdy obiekt jest dostępny z dowolnego miejsca bez przekazywania przez konstruktor.
Według danych Flutter Community Survey 2025, Provider jest używany w 72% aplikacji Flutter. Główne przyczyny popularności: minimalny próg wejścia, wbudowane wsparcie ChangeNotifier, zgodność z innymi architekturami (MVVM, BLoC) i brak zewnętrznych zależności.
ChangeNotifier — wbudowana klasa Flutter, implementująca wzorzec Listener. ChangeNotifier powiadamia subskrybentów o zmianie przez wywołanie notifyListeners(). W kontekście Provider ChangeNotifier jest główną klasą dla stanu: tworzy się klasę dziedziczącą po ChangeNotifier z polami i metodami wywołującymi notifyListeners() po zmianie danych.
class CounterProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}Zasady notifyListeners: wywoływać po pełnej zmianie danych — nie w środku metody, ale na końcu. Jeśli metoda wykonuje kilka zmian, wywołaj notifyListeners() raz po wszystkich zmianach, a nie po każdej. Zapobiega to wielokrotnemu przerysowywaniu w jednym logicznym kroku. W przypadku masowych aktualizacji używaj notifyListeners w połączeniu z wzorcami podobnymi do setState.
Alternatywy dla ChangeNotifier: ValueNotifier — dla pojedynczej wartości (dobry dla prymitywów), StateNotifier — z pakietu state_notifier (rzadko używany samodzielnie). Większość rozwiązań Provider używa ChangeNotifier ze względu na wbudowane wsparcie i prostotę.
Consumer — widget subskrybujący ChangeNotifier i przebudowujący się przy każdym wywołaniu notifyListeners(). Consumer przyjmuje funkcję builder z trzema parametrami: context, model, child. Child — widget niezależny od modelu, którego Consumer nie przebudowuje. To optymalizacja: jeśli wewnątrz Consumer znajduje się statyczny widget (ikona, tekst bez danych), jest on przekazywany przez child i nie jest odtwarzany.
Consumer<CounterProvider>(
builder: (context, provider, child) => Column(
children: [
child!, // nie przebudowuje się
Text('${provider.count}'),
ElevatedButton(
onPressed: () => provider.increment(),
child: Icon(Icons.add),
),
],
),
child: Text('Licznik:'),
)context.watch — metoda rozszerzenia BuildContext do subskrypcji Provider. Zwraca model i subskrybuje bieżący widget na jego zmiany. context.read — dostęp bez subskrypcji (dla handlerów onPressed, initState i dispose). context.select — subskrypcja konkretnego pola modelu bez przebudowy przy zmianie innych pól. Select — najbardziej wydajna opcja dla złożonych modeli z 10+ polami.
Kiedy używać Consumer, watch lub select: Consumer — gdy potrzebny jest child-widget do optymalizacji. watch — w metodzie build do prostego odczytu. select — gdy model ma wiele pól, ale widget zależy tylko od jednego. Provider automatycznie anuluje subskrypcję przy zniszczeniu widgetu, zapobiegając wyciekom pamięci.
MultiProvider — widget do rejestracji wielu Provider bez zagnieżdżania. Zamiast drzewa z 5 poziomami Provider → Provider → Provider, MultiProvider przyjmuje listę providerów. Każdy kolejny Provider może używać poprzednich przez konstruktor. MultiProvider — standardowy sposób organizacji głównego poziomu aplikacji.
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartProvider()),
ChangeNotifierProvider(create: (_) => AuthProvider()),
ProxyProvider<AuthProvider, OrderProvider>(
update: (_, auth, __) => OrderProvider(auth.userId),
),
],
child: MaterialApp(home: HomePage()),
)ProxyProvider — Provider zależny od innego Provider. ProxyProvider pobiera wartości z innych providerów i przekazuje je do swojego obiektu. Na przykład OrderProvider zależy od AuthProvider (potrzebuje userId). Przy zmianie AuthProvider ProxyProvider automatycznie odtwarza OrderProvider z nowym userId. ChangeNotifierProxyProvider — wersja ProxyProvider dla ChangeNotifier.
StreamProvider i FutureProvider: StreamProvider subskrybuje strumień (Firebase, WebSocket) i aktualizuje Consumer przy każdym nowym zdarzeniu. FutureProvider — do asynchronicznej inicjalizacji: uruchamia Future, pokazuje ładowanie, następnie przekazuje wynik widgetom. Oba rozwiązują typowe zadania bez ręcznego zarządzania subskrypcjami.
Provider testuje się przez owinięcie widgetu w MultiProvider z wartościami testowymi. Do testu nie jest potrzebne prawdziwe API ani baza danych — Provider jest zastępowany zamockowanym obiektem. Pakiet provider udostępnia ProviderScope do izolacji testów — każdy test tworzy własne drzewo Provider niezależne od innych.
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Counter increments on button tap',
(tester) async {
await tester.pumpWidget(
ChangeNotifierProvider(
create: (_) => CounterProvider(),
child: CounterScreen(),
),
);
await tester.tap(find.byKey(Key('increment')));
await tester.pump();
expect(find.text('1'), findsOneWidget);
},
);
}MockProvider: do testowania widgetów z Providerem zależnym od API, utwórz podklasę-zastępkę lub użyj mockito / mocktail. Provider nie wymaga specjalnych narzędzi do mockowania — każdy obiekt dziedziczący po ChangeNotifier może być przekazany przez create bez wywołania prawdziwego serwisu. Programuj Provider przez interfejsy (abstract class) dla łatwej zamiany.
Wydajność Provider opiera się na InheritedWidget: przy zmianie Provider wszystkie widgety subskrybujące przez context.watch lub Consumer są przebudowywane. Aby zapobiec niepotrzebnemu przerysowywaniu, używaj context.select (subskrypcja konkretnego pola), Consumer z parametrem child oraz const dla statycznych widgetów. Provider nie przebudowuje gałęzi drzewa, które nie są subskrybowane na zmiany.
| Metoda | Subskrypcja | Przerysowanie | Zastosowanie |
|---|---|---|---|
| context.watch | Pełny model | Każda zmiana | Proste widgety |
| Consumer | Pełny model | Każda zmiana | Z optymalizacją child |
| context.select | Konkretne pole | Tylko przy zmianie pola | Złożone modele |
| context.read | Brak | Nigdy | Handlery zdarzeń |
Ograniczenia: Provider nie obsługuje izolacji logiki biznesowej na poziomie Event (jak BLoC). Wszystkie zmiany odbywają się przez bezpośrednie wywołanie metod ChangeNotifier, co może prowadzić do niekontrolowanych łańcuchów zmian. W przypadku złożonych scenariuszy (wielokrotne operacje asynchroniczne, złożona walidacja) Provider ustępuje BLoC i Riverpod.
Migracja z Provider: Provider łatwo łączy się z innymi pakietami. Aby przejść na Riverpod, użyj ChangeNotifierProvider.adaptive — adaptera umożliwiającego użycie istniejących ChangeNotifier z Riverpod bez przepisywania. Dla BLoC — BlocProvider może być umieszczony wewnątrz drzewa Provider, stopniowo zastępując ChangeNotifier na Bloc.
Często zadawane pytania
Provider — nakładka na InheritedWidget do wstrzykiwania zależności z ChangeNotifier. BLoC — wzorzec architektoniczny z Event + Stream do izolacji logiki. Provider jest łatwiejszy do nauczenia, BLoC ściślej strukturyzuje kod. Provider nadaje się do małych aplikacji i stanu UI, BLoC — do złożonej logiki biznesowej. Według danych Flutter Community 2025, oba są często używane razem w jednym projekcie.
ChangeNotifierProvider — typ Provider dla instancji ChangeNotifier. Tworzy obiekt przez create, udostępnia go potomkom i przebudowuje Consumer przy wywołaniu notifyListeners. ChangeNotifierProvider automatycznie wywołuje dispose na ChangeNotifier po usunięciu z drzewa. Istnieją trzy sposoby tworzenia: ChangeNotifierProvider.value (dla istniejącego obiektu), ChangeNotifierProvider (dla lazy-tworzenia) i ChangeNotifierProvider.create (dla jawnego lazy).
Używaj context.select zamiast context.watch — widget przerysowuje się tylko przy zmianie wybranego pola. Dziel duże ChangeNotifier na kilka małych (jeden model — jedna odpowiedzialność). Używaj Consumer child dla statycznych części. Dla list stosuj ListView.builder z kluczami. Provider DevTools (Flutter Inspector) pokazuje, które widgety się przerysowują i dlaczego.
Tak. Provider (bez ChangeNotifier) — do wstrzykiwania niezmiennych obiektów (repozytorium, klient API, konfiguracja). ValueListenableProvider — dla ValueNotifier. StreamProvider — dla strumienia (Firebase, WebSocket). FutureProvider — dla Future (ładowanie konfiguracji przy starcie). ProxyProvider — dla Providerów zależnych od innych Providerów. ChangeNotifier jest potrzebny tylko dla stanu mutowalnego z aktualizacją UI.
ProviderNotFoundException — wyjątek czasu wykonania, występujący przy próbie uzyskania Provider, który nie został zadeklarowany wyżej w drzewie widgetów. Częste przyczyny: Provider zadeklarowany niżej niż widget próbujący go odczytać; Provider zadeklarowany w jednym routerze, a odczytywany w innym; literówka w typie. Rozwiązanie: przesuń Provider wyżej w drzewie lub użyj MultiProvider na poziomie MaterialApp dla globalnych zależności.
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ż