InheritedWidget — це спеціальний віджет у Flutter, який передає дані вниз по дереву віджетів без явної передачі через конструктори. Дочірні віджети отримують доступ до даних через BuildContext і автоматично підписуються на оновлення. Коли дані в InheritedWidget змінюються, всі залежні віджети перебудовуються. За даними Flutter API Reference, 2025, InheritedWidget лежить в основі Theme, MediaQuery, Localizations і більшості бібліотек керування станом.
Головне
InheritedWidget — це віджет, який робить свої дані доступними для всіх нащадків у Widget Tree. На відміну від звичайного віджета, який передає дані лише через конструктори дочірнім елементам, InheritedWidget дозволяє будь-якому віджету в піддереві отримати доступ до даних без ланцюжка параметрів. Це вирішує проблему “prop drilling” — передачі даних через багато проміжних віджетів, які самі не використовують ці дані.
Flutter включає кілька вбудованих InheritedWidget: Theme (колірна схема та стилі), MediaQuery (розмір екрана, орієнтація, щільність пікселів), Localizations (локалізовані рядки), Directionality (напрям тексту), DefaultTextStyle (стиль тексту за замовчуванням). Ці віджети встановлюються кореневими віджетами на зразок MaterialApp і доступні в усьому додатку.
InheritedWidget не має власного стану — він зберігає дані, передані через конструктор. Коли батьківський InheritedWidget перебудовується з новими даними, викликається метод updateShouldNotify для порівняння старих і нових даних. Якщо метод повертає true, всі залежні віджети позначаються для перебудування. Це простий, але ефективний механізм реактивного оновлення.
Механізм передачі даних через InheritedWidget заснований на Element Tree. Коли віджет викликає dependOnInheritedWidgetOfExactType, відповідний елемент реєструє залежність від InheritedElement. При зміні InheritedWidget, InheritedElement повідомляє всі залежні елементи, які перебудовуються в наступному кадрі.
Метод dependOnInheritedWidgetOfExactType не просто знаходить InheritedWidget у дереві — він підписує поточний елемент на сповіщення. Якби ви використовували findAncestorWidgetOfExactType замість dependOn, віджет отримав би дані, але не перебудовувався б при їх зміні. Це важлива відмінність: dependOn — це підписка, а findAncestor — одноразовий пошук.
Коли віджет запитує InheritedWidget, Flutter піднімається по Element Tree від поточного елемента до кореня, перевіряючи кожен InheritedElement на збіг типу. Перший знайдений InheritedElement повертається. Це означає, що найближчий InheritedWidget у дереві має пріоритет — ви можете перевизначити дані на певному рівні, розмістивши InheritedWidget ближче до нащадків.
class ThemeData {
final Color primaryColor;
final TextTheme textTheme;
const ThemeData({required this.primaryColor, required this.textTheme});
}
class MyTheme extends InheritedWidget {
final ThemeData data;
const MyTheme({required this.data, required Widget child}) : super(child: child);
static MyTheme of(BuildContext context) {
final widget = context.dependOnInheritedWidgetOfExactType<MyTheme>();
assert(widget != null, "MyTheme not found in tree");
return widget!;
}
@override
bool updateShouldNotify(MyTheme oldWidget) => oldWidget.data != data;
}
У цьому прикладі MyTheme використовує статичний метод of для надання даних нащадкам. Метод dependOnInheritedWidgetOfExactType реєструє залежність, а updateShouldNotify порівнює старі та нові дані для визначення необхідності перебудування залежних віджетів.
Створення власного InheritedWidget складається з двох кроків: визначення класу, що успадковує InheritedWidget, та реалізація статичного методу of для доступу з нащадків. Дані передаються через конструктор, а метод updateShouldNotify визначає, коли залежні віджети мають перебудовуватися.
Клас має успадковувати InheritedWidget і приймати дані через конструктор з обов’язковим параметром child. Дані можуть бути будь-якого типу: примітиви, об’єкти, функції. Головне правило — дані мають бути незмінними (immutable), щоб можна було надійно порівнювати старе та нове значення.
Статичний метод of приймає BuildContext і повертає дані InheritedWidget. Всередині викликається dependOnInheritedWidgetOfExactType, який шукає найближчий InheritedWidget зазначеного типу в дереві. Якщо InheritedWidget не знайдено, метод викидає виняток або повертає значення за замовчуванням залежно від реалізації.
Для доступу до даних віджет викликає MyWidget.of(context) всередині методу build. Flutter автоматично підписує віджет на оновлення. Якщо дані зміняться, віджет перебудується в наступному кадрі. Це дозволяє створювати чистий та декларативний код без зайвих параметрів.
class UserPreferences extends InheritedWidget {
final String languageCode;
final bool darkMode;
const UserPreferences({
required this.languageCode,
required this.darkMode,
required Widget child,
}) : super(child: child);
static UserPreferences of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<UserPreferences>()!;
}
@override
bool updateShouldNotify(UserPreferences oldWidget) =>
oldWidget.languageCode != languageCode || oldWidget.darkMode != darkMode;
}
У цьому прикладі UserPreferences зберігає налаштування користувача. Метод updateShouldNotify порівнює кожне поле окремо, що запобігає зайвим перебудуванням при зміні лише одного параметра. Використовуйте аналогічний підхід для власних InheritedWidget з кількома полями.
updateShouldNotify — це ключовий метод InheritedWidget, який визначає, чи потрібно сповіщати залежні віджети про зміну даних. Якщо метод повертає false, залежні віджети не перебудовуються, навіть якщо сам InheritedWidget отримав новий екземпляр з тими самими даними. Це критично важливо для продуктивності.
Порівнюйте лише ті поля, які дійсно змінилися та впливають на відображення. Якщо InheritedWidget містить 10 полів, але лише одне з них впливає на UI, перевіряйте лише це поле. Для колекцій використовуйте глибоке порівняння або імутабельні структури даних. Не використовуйте == для List або Map, оскільки вони порівнюються за посиланням.
Найчастіша помилка — повертати true без порівняння. Це призводить до перебудування всіх залежних віджетів при кожному оновленні батька, навіть якщо дані не змінилися. Друга помилка — повертати false, коли дані змінилися, що призводить до застарілого UI. Третя — складне порівняння, яке виконується кожен кадр і сповільнює роботу.
InheritedWidget і колбеки (передача функцій через конструктори) вирішують різні завдання. InheritedWidget підходить для даних, які потрібні багатьом віджетам на різних рівнях дерева. Колбеки зручні для однонаправленої передачі подій від батька до конкретного нащадка або навпаки. Вибір залежить від архітектури додатку та частоти змін.
Використовуйте InheritedWidget, коли дані потрібні багатьом віджетам на різних рівнях вкладеності: тема додатку, налаштування користувача, інформація про пристрій, дані поточної сесії. InheritedWidget особливо ефективний для “глобальних” даних, які змінюються рідко, але потрібні в різних частинах UI.
Колбеки (функції зворотного виклику) підходять для передачі подій від дочірнього віджета до батьківського: натискання кнопки, вибір елемента списку, відправка форми. Колбеки явно вказують, які дії може виконати нащадок, і не створюють прихованих залежностей. Для передачі даних вниз по дереву на невелику кількість рівнів також простіше використовувати параметри конструктора.
| Критерій | InheritedWidget | Колбеки |
|---|---|---|
| Напрям | Зверху вниз (батько → нащадки) | Знизу вверх (нащадок → батько) або безпосередньо |
| Область | Все піддерево | Конкретний віджет |
| Перебудування | Автоматичне при зміні даних | Потрібен setState вручну |
| Складність | Середня (потрібен клас InheritedWidget) | Низька (просто функція) |
Provider та Riverpod — популярні бібліотеки керування станом у Flutter, побудовані поверх InheritedWidget. Вони розширюють його можливості: додають підтримку ChangeNotifier, автоматичне видалення при демонтуванні, ліниву ініціалізацію та спрощений синтаксис через дженерики.
Provider використовує InheritedWidget для передачі об’єкта будь-якого типу вниз по дереву. ChangeNotifierProvider відстежує зміни через ChangeNotifier і викликає updateShouldNotify при виклику notifyListeners. Це позбавляє розробника від ручного створення InheritedWidget та реалізації updateShouldNotify.
Прямий InheritedWidget дає більше контролю і не потребує зовнішніх залежностей. Provider надає готову інфраструктуру: Consumer, Selector, MultiProvider, ProxyProvider. Вибір залежить від складності додатку. Для простих проектів прямого InheritedWidget достатньо, для великих — Provider або Riverpod знижують шаблонний код.
// Прямий InheritedWidget
class UserProvider extends InheritedWidget {
final UserData userData;
const UserProvider({required this.userData, required Widget child}) : super(child: child);
static UserData of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<UserProvider>()!.userData;
@override
bool updateShouldNotify(UserProvider old) => old.userData != userData;
}
// Еквівалент Provider
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
Обидва підходи в прикладі вирішують одне завдання — передачу UserData вниз по дереву. Provider скорочує кількість коду, але приховує механіку InheritedWidget. Прямий InheritedWidget дає повний контроль і розуміння того, що відбувається, що особливо важливо при вивченні Flutter та налагодженні складних проблем з перебудуванням.
Часто задавані питання
InheritedWidget робить дані доступними для всіх нащадків через BuildContext, а звичайний віджет передає дані лише через конструктор. InheritedWidget також підписує нащадків на оновлення даних.
Залежні віджети перебудовуються лише тоді, коли updateShouldNotify повертає true. Якщо метод реалізовано правильно, перебудування відбувається лише при реальній зміні даних, а не при кожному перебудуванні батька.
Так, можна використовувати будь-яку кількість InheritedWidget в одному дереві. Кожен надає дані певного типу, а віджети можуть отримувати дані з кількох InheritedWidget одночасно.
dependOn підписує віджет на оновлення — при зміні даних віджет перебудується. findAncestor виконує одноразовий пошук без підписки, і віджет не дізнається про зміни даних.
Для простого стану (тема, налаштування) InheritedWidget достатній. Для складного стану з бізнес-логікою використовуйте Provider, Riverpod або BLoC — вони побудовані на InheritedWidget і додають необхідну інфраструктуру.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також