Provider: что это, менеджер состояния Flutter приложений

Автор: IT Sectr Опубликовано: 2026-02-19 Время чтения: 7 мин

Provider — пакет управления состоянием для Flutter, созданный Реми Русле в 2019 году как обёртка над InheritedWidget. Provider решает проблему передачи данных вниз по дереву виджетов без props drilling: любой виджет получает доступ к состоянию через context.read<T>() или context.watch<T>(). По данным pub.dev, Provider — самый популярный менеджер состояния Flutter с более чем 25 тысячами лайков.

Главное

  • ChangeNotifier — класс с notifyListeners для уведомления подписчиков об изменении
  • Consumer — виджет, перестраивающийся при изменении ChangeNotifier
  • MultiProvider — композиция нескольких Provider без вложенности
  • ProxyProvider — Provider, зависящий от другого Provider
  • context.read / context.watch — методы чтения состояния без и с подпиской

Что такое Provider?

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 и notifyListeners

ChangeNotifier — встроенный класс Flutter, реализующий паттерн Listener. ChangeNotifier уведомляет подписчиков о изменении через вызов notifyListeners(). В контексте Provider ChangeNotifier — основной класс для состояния: создаётся класс-наследник ChangeNotifier с полями и методами, вызывающими notifyListeners() после изменения данных.

Dart
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 и context.watch

Consumer — виджет, подписывающийся на ChangeNotifier и перестраивающийся при каждом вызове notifyListeners(). Consumer принимает builder-функцию с тремя параметрами: context, модель, child. Child — виджет, не зависящий от модели, который Consumer не перестраивает. Это оптимизация: если внутри Consumer есть статический виджет (иконка, текст без данных), он передаётся через child и не пересоздаётся.

Dart
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 и ProxyProvider

MultiProvider — виджет для регистрации нескольких Provider без вложенности. Вместо Tree с 5 уровнями Provider → Provider → Provider, MultiProvider принимает список провайдеров. Каждый последующий Provider может использовать предыдущие через конструктор. MultiProvider — стандартный способ организации корневого уровня приложения.

Dart
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

Provider тестируется через оборачивание виджета в MultiProvider с тестовыми значениями. Для теста не нужен настоящий API или база данных — Provider подменяется замоканным объектом. Пакет provider предоставляет ProviderScope для изоляции тестов — каждый тест создаёт своё дерево Provider независимо от других.

Dart
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: производительность и ограничения

Производительность 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 отличается от BLoC?

Provider — обёртка над InheritedWidget для внедрения зависимостей с ChangeNotifier. BLoC — архитектурный паттерн с Event + Stream для изоляции логики. Provider проще в изучении, BLoC строже структурирует код. Provider подходит для небольших приложений и UI-состояния, BLoC — для сложной бизнес-логики. По данным Flutter Community 2025, оба часто используются вместе в одном проекте.

Что такое ChangeNotifierProvider?

ChangeNotifierProvider — тип Provider для экземпляров ChangeNotifier. Создаёт объект через create, предоставляет его потомкам и перестраивает Consumer при вызове notifyListeners. ChangeNotifierProvider автоматически вызывает dispose на ChangeNotifier при удалении из дерева. Существует три способа создания: ChangeNotifierProvider.value (для существующего объекта), ChangeNotifierProvider (для lazy-создания) и ChangeNotifierProvider.create (для явного lazy).

Как избежать лишних перерисовок с Provider?

Используйте context.select вместо context.watch — виджет перерисовывается только при изменении выбранного поля. Разделяйте крупные ChangeNotifier на несколько маленьких (одна модель — одна ответственность). Используйте Consumer child для статических частей. Для списков применяйте ListView.builder с ключами. Provider DevTools (Flutter Inspector) показывает, какие виджеты перерисовываются и почему.

Можно ли использовать Provider без ChangeNotifier?

Да. Provider (без ChangeNotifier) — для внедрения неизменяемых объектов (репозиторий, API-клиент, конфигурация). ValueListenableProvider — для ValueNotifier. StreamProvider — для Stream (Firebase, WebSocket). FutureProvider — для Future (загрузка конфигурации при старте). ProxyProvider — для Provider, зависящих от других Provider. ChangeNotifier нужен только для мутабельного состояния с UI-обновлением.

Что такое ProviderNotFoundException?

ProviderNotFoundException — runtime-исключение, возникающее при попытке получить Provider, который не был объявлен выше в дереве виджетов. Частые причины: Provider объявлен ниже, чем виджет, пытающийся его прочитать; Provider объявлен в одном роуте, а читается в другом; опечатка в типе. Решение: поднять Provider выше по дереву или использовать MultiProvider на уровне MaterialApp для глобальных зависимостей.

Итоги

  • Provider — обёртка над InheritedWidget для управления состоянием и DI во Flutter
  • ChangeNotifier — класс с notifyListeners для реактивного обновления UI
  • Consumer и context.watch — подписка на изменения с автоматической перерисовкой
  • MultiProvider — композиция провайдеров без вложенности дерева
  • ProxyProvider — цепочка зависимостей между Provider
  • context.select — точечная подписка на конкретное поле модели
  • StreamProvider и FutureProvider — встроенная поддержка реактивных и асинхронных данных

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также