Что такое InheritedWidget и как он связан с Provider?
InheritedWidget — базовый механизм Flutter для передачи данных вниз по дереву через dependOnInheritedWidgetOfExactType. Provider оборачивает его, добавляя управление жизненным циклом и API (watch/read/select) для удобной работы с изменяемыми данными.
InheritedWidget и его связь с Provider
InheritedWidget — это базовый механизм Flutter для передачи данных вниз по дереву виджетов без явного проброса через конструкторы. Provider строится поверх этого механизма, добавляя удобный API и управление жизненным циклом.
Как работает InheritedWidget
При вызове context.dependOnInheritedWidgetOfExactType<T>() Flutter регистрирует зависимость текущего BuildContext от ближайшего виджета типа T. Когда виджет уведомляет своих потомков, все зависимые BuildContext перестраиваются.
// Создание собственного InheritedWidget
class AppThemeData extends InheritedWidget {
const AppThemeData({
super.key,
required this.primaryColor,
required super.child,
});
final Color primaryColor;
// Статический метод для удобного доступа
static AppThemeData of(BuildContext context) {
final result = context.dependOnInheritedWidgetOfExactType<AppThemeData>();
assert(result != null, 'No AppThemeData found in context');
return result!;
}
// Определяет, нужно ли уведомлять потомков при замене виджета
@override
bool updateShouldNotify(AppThemeData oldWidget) {
return primaryColor != oldWidget.primaryColor;
}
}
// Использование
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = AppThemeData.of(context); // регистрирует зависимость
return Container(color: theme.primaryColor);
}
}
// Размещение в дереве
AppThemeData(
primaryColor: Colors.blue,
child: MaterialApp(home: MyWidget()),
)
Ключевой метод updateShouldNotify контролирует, при каких изменениях потомки будут перестроены. Возврат false блокирует уведомление даже если виджет заменён.
Ограничения InheritedWidget
- Не управляет жизненным циклом данных (нет dispose для стримов, контроллеров).
- Для изменяемых данных нужна связка с
StatefulWidget— код разрастается. - Нет встроенного механизма для ленивой инициализации.
Provider как надстройка
Пакет provider оборачивает InheritedWidget, добавляя управление жизненным циклом и удобный API. Под капотом Provider<T> создаёт InheritedWidget с вашими данными.
dependencies:
provider: ^6.1.0
import 'package:provider/provider.dart';
// Модель с ChangeNotifier
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // вызывает updateShouldNotify у внутреннего InheritedWidget
}
}
// Размещение в дереве
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MaterialApp(home: CounterPage()),
)
// Чтение без подписки (только один раз)
final counter = context.read<CounterModel>();
// Чтение с подпиской (rebuild при изменении)
final count = context.watch<CounterModel>().count;
// Гранулярная подписка через select
final count = context.select<CounterModel, int>((m) => m.count);
// Перестраивается только если count изменился, игнорирует другие поля
MultiProvider для нескольких зависимостей
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthModel()),
ChangeNotifierProvider(create: (_) => CartModel()),
Provider<ApiClient>(create: (_) => ApiClient()),
],
child: MyApp(),
)
Подводные камни
context.readвнутриbuild()не регистрирует зависимость — виджет не перестроится при изменении. Используйтеwatchилиselectвbuild().- Вызов
context.watchвнеbuild()(например вinitState) — исключение. ДляinitStateиспользуйтеcontext.readилиProvider.of(context, listen: false). updateShouldNotify, всегда возвращающийtrue, вызывает перестройку всех потомков при любом rebuild родителя — может привести к каскадным лишним rebuild.- Утечка ресурсов:
ChangeNotifierProviderвызываетdispose()автоматически, ноChangeNotifierProvider.value— нет (владелец должен вызвать dispose сам). - Доступ к Provider выше его размещения в дереве бросает
ProviderNotFoundException— проверьте порядок в дереве. notifyListeners()во время build-фазы вызывает assertion error — выносите мутации в микрозадачи или обработчики событий.
Common mistakes
- Сводить «
InheritedWidgetи как он связан с Provider» к синтаксису и не объяснять render tree. - Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии flutter-12.
- Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.
What the interviewer is testing
- Формулирует точную модель для «
InheritedWidgetи как он связан с Provider» и подтверждает ее корректным примером. - Умеет связать ответ с frame scheduling, тестированием и отладкой на устройстве.
- Называет ограничения подхода flutter-12, включая производительность, память и сопровождение.