tl;dr: Для обробки ProviderNotFoundException
в Dialog
потрібно повторно впровадити ChangeNotifier
у контекст діалогу за допомогою ChangeNotifierProvider.value
.
Іноді баги, з якими ми стикаємось, відкривають нам можливість глибше зрозуміти, як працюють інструменти, якими ми користуємось, і один з таких багів, з яким ми нещодавно зіткнулись, стосується доступу до ChangeNotifier
в Dialog
.
Отже, що саме таке ChangeNotifier
? Це ключовий компонент пакету provider
у Flutter, який використовується для керування станом. Фактично, ChangeNotifier
— це об'єкт, до якого можна підключити прослуховувачів, що працює за принципом, схожим на патерн Observer.
При спробі доступу до ChangeNotifier
всередині Dialog
зазвичай виникає ProviderNotFoundException
. Щоб зрозуміти цю проблему і дізнатися, як її вирішити, давайте розглянемо практичний приклад.
Уявімо собі простий ChangeNotifier
, RocketLauncher
, який ініціює запуск ракети, що може бути як успішним, так і невдалим:
class RocketLauncher extends ChangeNotifier {
int failed = 0;
int successful = 0;
int get count => failed + successful; void launch() {
if (Random().nextBool()) {
successful += 1;
} else {
failed += 1;
}
notifyListeners();
}
}
Цей ChangeNotifier
повідомляє своїх слухачів щоразу, коли відбувається запуск.
Структура нашого додатку виглядає так:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider(
create: (context) => RocketLauncher(),
builder: (context, _) => const Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LaunchButton(),
StatsButton(),
],
),
),
),
);
}
Тут LaunchButton
ініціює запуски, а StatsButton
відображає статистику. ChangeNotifierProvider
робить RocketLauncher
доступним для нащадків віджетів.
class LaunchButton extends StatelessWidget {
const LaunchButton({super.key});
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: context.watch<RocketLauncher>().launch,
icon: const Icon(Icons.rocket_launch),
);
}
}
LaunchButton
просто викликає метод launch
у RocketLauncher
.
class StatsButton extends StatelessWidget {
const StatsButton({super.key});
@override
Widget build(BuildContext context) {
final launcher = context.watch<RocketLauncher>(); return TextButton(
child: Text("${launcher.count} launches"),
onPressed: () {
showDialog(
context: context,
builder: (context) => const StatsDialog(),
);
},
);
}
}
StatsButton
показує загальну кількість запусків і відкриває діалог для додаткових деталей.
class StatsDialog extends StatelessWidget {
const StatsDialog({super.key});
@override
Widget build(BuildContext context) {
final launcher = context.read<RocketLauncher>(); return Dialog(
child: Column(
children: [
Text("${launcher.failed} failed rocket launches"),
Text("${launcher.successful} successful rocket launches"),
],
),
);
}
}
StatsDialog
читає RocketLauncher
, щоб відобразити кількість невдалих і успішних запусків.
Однак, при відкритті діалогу виникає ця помилка:
The following ProviderNotFoundException was thrown building StatsDialog(dirty):
Error: Could not find the correct Provider above this StatsDialog Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice.
Причина виникнення цієї помилки полягає в тому, що showDialog
у Flutter використовує інший контекст, ніж контекст його викликача.
Документація Flutter для showDialog
вказує: “Віджет, який повертає builder, не має спільного контексту з тим місцем, з якого спочатку викликається showDialog.”
Щоб вирішити цю проблему, ми можемо передати існуючий екземпляр RocketLauncher
в контекст діалогу, використовуючи ChangeNotifierProvider.value
:
class StatsButton extends StatelessWidget {
const StatsButton({super.key});
@override
Widget build(BuildContext context) {
final launcher = context.watch<RocketLauncher>(); return TextButton(
child: Text("${launcher.count} launches"),
onPressed: () {
showDialog(
context: context,
builder: (context) => ChangeNotifierProvider.value(
value: launcher,
builder: (context, _) => const StatsDialog(),
),
);
},
);
}
}
На завершення, робота з ProviderNotFoundException
у Flutter — це поширена проблема, яка підкреслює важливість розуміння керування контекстом у фреймворку Flutter. Використовуючи ChangeNotifierProvider.value
, розробники можуть ефективно налагодити контекст між батьківськими віджетами та їх діалогами.
Перекладено з: Lesson learned: sharing context with Flutter dialogs