Provider — пакет управления состоянием для Flutter, созданный Реми Русле в 2019 году как обёртка над InheritedWidget. Provider решает проблему передачи данных вниз по дереву виджетов без props drilling: любой виджет получает доступ к состоянию через context.read<T>() или context.watch<T>(). По данным pub.dev, Provider — самый популярный менеджер состояния Flutter с более чем 25 тысячами лайков.
Главное
Provider — пакет для управления состоянием и внедрения зависимостей во Flutter, построенный поверх InheritedWidget. Provider предоставляет объект (состояние, сервис, репозиторий) в дереве виджетов и автоматически перестраивает UI при изменении данных. В отличие от прямого использования InheritedWidget, Provider убирает весь boilerplate: не нужно писать подкласс InheritedWidget, настраивать статический метод of() и управлять вложенностью.
Provider — официально рекомендованный Google способ управления состоянием во Flutter (Flutter Team, 2019-2023). Пакет входит в состав Flutter Ecosystem и поддерживается командой Flutter. На момент выхода Provider был предложен как замена глобальным переменным и InheritedWidget: любой объект доступен из любого места без передачи через конструктор.
По данным Flutter Community Survey 2025, Provider используется в 72% Flutter-приложений. Основные причины популярности: минимальный порог входа, встроенная поддержка ChangeNotifier, совместимость с другими архитектурами (MVVM, BLoC) и отсутствие внешних зависимостей.
ChangeNotifier — встроенный класс Flutter, реализующий паттерн Listener. ChangeNotifier уведомляет подписчиков о изменении через вызов notifyListeners(). В контексте Provider ChangeNotifier — основной класс для состояния: создаётся класс-наследник ChangeNotifier с полями и методами, вызывающими notifyListeners() после изменения данных.
class CounterProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}Правила notifyListeners: вызывать после полного изменения данных — не в середине метода, а в конце. Если метод выполняет несколько изменений, вызывать notifyListeners() один раз после всех изменений, а не после каждого. Это предотвращает множественные перерисовки за один логический шаг. Для массовых обновлений используйте notifyListeners в связке с setState-подобными паттернами.
Альтернативы ChangeNotifier: ValueNotifier — для одного значения (хорош для примитивов), StateNotifier — из пакета state_notifier (редко используется отдельно). Большинство Provider-решений используют ChangeNotifier из-за встроенной поддержки и простоты.
Consumer — виджет, подписывающийся на ChangeNotifier и перестраивающийся при каждом вызове notifyListeners(). Consumer принимает builder-функцию с тремя параметрами: context, модель, child. Child — виджет, не зависящий от модели, который Consumer не перестраивает. Это оптимизация: если внутри Consumer есть статический виджет (иконка, текст без данных), он передаётся через child и не пересоздаётся.
Consumer<CounterProvider>(
builder: (context, provider, child) => Column(
children: [
child!, // не перестраивается
Text('${provider.count}'),
ElevatedButton(
onPressed: () => provider.increment(),
child: Icon(Icons.add),
),
],
),
child: Text('Счётчик:'),
)context.watch — метод расширения BuildContext для подписки на Provider. Возвращает модель и подписывает текущий виджет на её изменения. context.read — доступ без подписки (для обработчиков onPressed, initState и dispose). context.select — подписка на отдельное поле модели без перестройки при изменении других полей. Select — самая производительная опция для сложных моделей с 10+ полями.
Когда использовать Consumer, watch или select: Consumer — когда нужен child-виджет для оптимизации. watch — в build методе для простого чтения. select — когда модель имеет несколько полей, но виджет зависит только от одного. Provider автоматически отписывается при уничтожении виджета, предотвращая утечки памяти.
MultiProvider — виджет для регистрации нескольких Provider без вложенности. Вместо Tree с 5 уровнями Provider → Provider → Provider, MultiProvider принимает список провайдеров. Каждый последующий Provider может использовать предыдущие через конструктор. MultiProvider — стандартный способ организации корневого уровня приложения.
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartProvider()),
ChangeNotifierProvider(create: (_) => AuthProvider()),
ProxyProvider<AuthProvider, OrderProvider>(
update: (_, auth, __) => OrderProvider(auth.userId),
),
],
child: MaterialApp(home: HomePage()),
)ProxyProvider — Provider, который зависит от другого Provider. ProxyProvider получает значения из других provider-ов и передаёт их в свой объект. Например, OrderProvider зависит от AuthProvider (нужен userId). При изменении AuthProvider ProxyProvider автоматически пересоздаёт OrderProvider с новым userId. ChangeNotifierProxyProvider — версия ProxyProvider для ChangeNotifier.
StreamProvider и FutureProvider: StreamProvider подписывается на Stream (Firebase, WebSocket) и обновляет Consumer при каждом новом событии. FutureProvider — для асинхронной инициализации: запускает Future, показывает loading, затем передаёт результат виджетам. Оба решают типовые задачи без ручного управления подписками.
Provider тестируется через оборачивание виджета в MultiProvider с тестовыми значениями. Для теста не нужен настоящий API или база данных — Provider подменяется замоканным объектом. Пакет provider предоставляет ProviderScope для изоляции тестов — каждый тест создаёт своё дерево Provider независимо от других.
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: для тестирования виджетов с Provider, зависящим от API, создайте подкласс-заглушку или используйте mockito / mocktail. Provider не требует специальных мок-инструментов — любой объект, наследующий ChangeNotifier, может быть передан через create без вызова настоящего сервиса. Программируйте Provider через интерфейсы (abstract class) для лёгкой подмены.
Производительность Provider основана на InheritedWidget: при изменении Provider все виджеты, подписанные через context.watch или Consumer, перестраиваются. Для предотвращения лишних перерисовок используйте context.select (подписка на конкретное поле), Consumer с child параметром и const для статических виджетов. Provider не перестраивает ветки дерева, не подписанные на изменения.
| Метод | Подписка | Перерисовка | Использование |
|---|---|---|---|
| context.watch | Полная модель | Любое изменение | Простые виджеты |
| Consumer | Полная модель | Любое изменение | С child-оптимизацией |
| context.select | Конкретное поле | Только при изменении поля | Сложные модели |
| context.read | Нет | Никогда | Обработчики событий |
Ограничения: Provider не поддерживает изоляцию бизнес-логики на уровне Event (как BLoC). Все изменения происходят через прямой вызов методов ChangeNotifier, что может привести к неконтролируемым цепочкам изменений. Для сложных сценариев (множественные асинхронные операции, сложная валидация) Provider уступает BLoC и Riverpod.
Миграция с Provider: Provider легко комбинируется с другими пакетами. Для перехода на Riverpod используйте ChangeNotifierProvider.adaptive — адаптер, позволяющий использовать существующие ChangeNotifier с Riverpod без переписывания. Для BLoC — BlocProvider может быть размещён внутри Provider-дерева, постепенно заменяя ChangeNotifier на Bloc.
Часто задаваемые вопросы
Provider — обёртка над InheritedWidget для внедрения зависимостей с ChangeNotifier. BLoC — архитектурный паттерн с Event + Stream для изоляции логики. Provider проще в изучении, BLoC строже структурирует код. Provider подходит для небольших приложений и UI-состояния, BLoC — для сложной бизнес-логики. По данным Flutter Community 2025, оба часто используются вместе в одном проекте.
ChangeNotifierProvider — тип Provider для экземпляров ChangeNotifier. Создаёт объект через create, предоставляет его потомкам и перестраивает Consumer при вызове notifyListeners. ChangeNotifierProvider автоматически вызывает dispose на ChangeNotifier при удалении из дерева. Существует три способа создания: ChangeNotifierProvider.value (для существующего объекта), ChangeNotifierProvider (для lazy-создания) и ChangeNotifierProvider.create (для явного lazy).
Используйте context.select вместо context.watch — виджет перерисовывается только при изменении выбранного поля. Разделяйте крупные ChangeNotifier на несколько маленьких (одна модель — одна ответственность). Используйте Consumer child для статических частей. Для списков применяйте ListView.builder с ключами. Provider DevTools (Flutter Inspector) показывает, какие виджеты перерисовываются и почему.
Да. Provider (без ChangeNotifier) — для внедрения неизменяемых объектов (репозиторий, API-клиент, конфигурация). ValueListenableProvider — для ValueNotifier. StreamProvider — для Stream (Firebase, WebSocket). FutureProvider — для Future (загрузка конфигурации при старте). ProxyProvider — для Provider, зависящих от других Provider. ChangeNotifier нужен только для мутабельного состояния с UI-обновлением.
ProviderNotFoundException — runtime-исключение, возникающее при попытке получить Provider, который не был объявлен выше в дереве виджетов. Частые причины: Provider объявлен ниже, чем виджет, пытающийся его прочитать; Provider объявлен в одном роуте, а читается в другом; опечатка в типе. Решение: поднять Provider выше по дереву или использовать MultiProvider на уровне MaterialApp для глобальных зависимостей.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также