← Назад к вопросам

Что выберешь Provider или inheritedwidget?

2.0 Middle🔥 241 комментариев
#State Management

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Provider vs InheritedWidget

Это классический вопрос в Flutter. Оба решения работают для передачи данных вниз по дереву виджетов, но выбор зависит от контекста и требований проекта.

Коротко: что выберу я

Я выберу Provider практически всегда. Вот почему: Provider построен на InheritedWidget и предоставляет удобный API, лучшую производительность, переиспользуемость и ecosystem пакетов.

Но давайте разберёмся подробнее.

InheritedWidget — низкоуровневый инструмент

InheritedWidget — это базовый механизм Flutter для передачи данных:

class MyData extends InheritedWidget {
  final String value;

  MyData({
    required this.value,
    required Widget child,
  }) : super(child: child);

  @override
  bool updateShouldNotify(MyData oldWidget) {
    return oldWidget.value != value;
  }

  static MyData? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyData>();
  }
}

// Использование
MyData.of(context)?.value

Плюсы:

  • Минимум зависимостей (встроено в Flutter)
  • Полный контроль над деталями
  • Легко разобраться на примерах

Минусы:

  • Verbose и повторяющийся код
  • Нужно самому писать логику обновления
  • Сложнее с несколькими слоями данных
  • Нет встроенной поддержки для async операций
  • Легко сделать ошибку с updateShouldNotify

Provider — удобное обёртывание

Provider — это популярный пакет от Remi Rousselet, который оборачивает InheritedWidget:

final nameProvider = StateProvider<String>((ref) => 'John');

// В UI
Consumer(
  builder: (context, ref, child) {
    final name = ref.watch(nameProvider);
    return Text(name);
  },
)

Или классический Provider (если используешь старую версию):

class MyModel extends ChangeNotifier {
  String _value = 'John';
  
  String get value => _value;
  
  void setValue(String newValue) {
    _value = newValue;
    notifyListeners();
  }
}

// В UI
Provider(
  create: (_) => MyModel(),
  child: MyApp(),
)

// Доступ
final model = Provider.of<MyModel>(context);

Плюсы:

  • Декларативный API
  • Встроенная поддержка async (FutureProvider, StreamProvider)
  • Автоматическое управление зависимостями
  • Переиспользуемые provider'ы
  • Отличная поддержка DevTools
  • Огромное сообщество и пакеты
  • Легче писать тесты

Минусы:

  • Ещё одна зависимость в проекте
  • Новичкам может быть сложновато разобраться
  • Небольшой overhead производительности (незначительный)

Когда что использовать

Используй InheritedWidget, когда:

  1. Очень простое состояние — например, передача theme или locale
class ThemeData extends InheritedWidget {
  final bool isDarkMode;
  // ...
}
  1. Нет зависимостей проекта — ты изначально не хотел добавлять пакеты

  2. Контролируемая архитектура — ты точно знаешь, как будет расти проект

  3. Образовательный проект — хочешь разобраться с низкоуровневыми деталями

Используй Provider, когда:

  1. Комплексное состояние — много моделей, зависимостей, async операций (это 99% реальных проектов)

  2. Работа с асинхронностью — загрузка данных с сервера

final userProvider = FutureProvider<User>((ref) async {
  return await api.getUser();
});

// В UI
final asyncUser = ref.watch(userProvider);
asyncUser.when(
  data: (user) => Text(user.name),
  loading: () => CircularProgressIndicator(),
  error: (err, _) => Text('Error: $err'),
);
  1. Организация кода — нужна чистая архитектура и разделение ответственности

  2. Тестируемость — нужно легко мокировать состояние

test('increments counter', () {
  final container = ProviderContainer(
    overrides: [
      counterProvider.overrideWithValue(10),
    ],
  );
  expect(container.read(counterProvider), 10);
});

Практический пример: выбор решения

Сценарий 1: Простое приложение для TODO

// Хватит InheritedWidget
class TodoData extends InheritedWidget {
  final List<String> todos;
  // ...
}

Сценарий 2: Финтех приложение с API, авторизацией, кэшированием

// Нужен Provider (или Riverpod, что ещё лучше)
final authProvider = FutureProvider<User>((ref) async {
  return await api.authenticate();
});

final accountsProvider = FutureProvider<List<Account>>((ref) async {
  final user = await ref.watch(authProvider);
  return await api.getAccounts(user.id);
});

final balanceProvider = FutureProvider<double>((ref) async {
  final accounts = await ref.watch(accountsProvider);
  return accounts.fold(0, (sum, acc) => sum + acc.balance);
});

Моя рекомендация

Начни с Provider (или лучше Riverpod). Вот почему:

  1. Provider легче учиться — есть тонна туториалов и примеров
  2. Масштабируется лучше — когда проект растёт, не нужно переделывать архитектуру
  3. DevTools поддержка — можно инспектировать состояние в реальном времени
  4. Эко-система — множество пакетов (get_it для DI, go_router для навигации)
  5. Индустрия — большинство компаний используют Provider/Riverpod

Если проект совсем маленький (личный гаджет, обучение) — можешь использовать InheritedWidget напрямую для опыта.

Вывод

Provider — это модернизированное решение, которое скрывает сложность InheritedWidget и предоставляет удобный API. Это как разница между использованием raw сокетов в сетях vs высокоуровневой библиотеки. Выбор Provider — это не лень, а pragmatism: ты используешь инструменты, которые экономят время и снижают ошибки.

Что выберешь Provider или inheritedwidget? | PrepBro