Что выберешь Provider или inheritedwidget?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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, когда:
- Очень простое состояние — например, передача theme или locale
class ThemeData extends InheritedWidget {
final bool isDarkMode;
// ...
}
-
Нет зависимостей проекта — ты изначально не хотел добавлять пакеты
-
Контролируемая архитектура — ты точно знаешь, как будет расти проект
-
Образовательный проект — хочешь разобраться с низкоуровневыми деталями
Используй Provider, когда:
-
Комплексное состояние — много моделей, зависимостей, async операций (это 99% реальных проектов)
-
Работа с асинхронностью — загрузка данных с сервера
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'),
);
-
Организация кода — нужна чистая архитектура и разделение ответственности
-
Тестируемость — нужно легко мокировать состояние
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). Вот почему:
- Provider легче учиться — есть тонна туториалов и примеров
- Масштабируется лучше — когда проект растёт, не нужно переделывать архитектуру
- DevTools поддержка — можно инспектировать состояние в реальном времени
- Эко-система — множество пакетов (get_it для DI, go_router для навигации)
- Индустрия — большинство компаний используют Provider/Riverpod
Если проект совсем маленький (личный гаджет, обучение) — можешь использовать InheritedWidget напрямую для опыта.
Вывод
Provider — это модернизированное решение, которое скрывает сложность InheritedWidget и предоставляет удобный API. Это как разница между использованием raw сокетов в сетях vs высокоуровневой библиотеки. Выбор Provider — это не лень, а pragmatism: ты используешь инструменты, которые экономят время и снижают ошибки.