Время от времени возникает необходимость показать диалог именно тогда, когда пользователь открывает новый экран. Возможно, это связано с определенными требованиями, чтобы показать ему ценную информацию или, возможно, вам нужно предупредить его о каком-то важном факте в вашем приложении; и, учитывая его актуальность, вы, возможно, решили показать диалог именно тогда, когда пользователь открывает экран.
📽 Видеоверсия доступна на YouTube и Odysee
Если ваш виджет представляет собой StatefulWidget
, возможно, вы уже думали о том, чтобы сделать что-то подобное:
class SomeWidget extends StatefulWidget {
const SomeWidget({Key? key}) : super(key: key);
@override
State<SomeWidget> createState() => _SomeWidgetState();
}
class _SomeWidgetState extends State<SomeWidget> {
@override
void initState() {
super.initState();
// Try to open a dialog in initState()
showDialog(
context: context,
builder: (context) => AlertDialog(
content: const Text('Dialog content'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Accept'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sample'),
),
body: const Center(
child: Text('Body of the Scaffold'),
),
);
}
}
Но затем вы получаете ошибку, подобную этой:
======== Exception caught by widgets library =======================================================
Следующее утверждение было брошено при построении Builder:
dependOnInheritedWidgetOfExactType<_LocalizationsScope>() или dependOnInheritedElement() был вызван до завершения _PostFrameCallbackSampleState.initState().Когда наследуемый виджет изменяется, например, если изменяется значение Theme.of(), его зависимые виджеты перестраиваются. Если ссылка зависимого виджета на наследуемый виджет находится в конструкторе или методе initState(), то перестроенный зависимый виджет не будет отражать изменения в наследуемом виджете.
Обычно ссылки на наследуемые виджеты должны встречаться в методах widget build(). В качестве альтернативы инициализацию на основе унаследованных виджетов можно поместить в метод didChangeDependencies, который вызывается после initState и при каждом последующем изменении зависимых виджетов.
Причина, по которой это происходит, заключается в том, что виджет находится в фазе рендеринга, поэтому, чтобы показать диалог, мы должны дождаться завершения этой фазы. Как мы можем это сделать? К счастью, в классе WidgetsBinding есть метод addPostFrameCallback
, документация которого говорит нам следующее:
Запланировать обратный вызов для конца этого кадра.
С помощью этого метода мы можем “запланировать” эту операцию так, чтобы она выполнялась в конце рендеринга текущего кадра, следующим образом:
class PostFrameCallbackSample extends StatefulWidget {
const PostFrameCallbackSample({Key? key}) : super(key: key);
@override
State<PostFrameCallbackSample> createState() =>
_PostFrameCallbackSampleState();
}
class _PostFrameCallbackSampleState extends State<PostFrameCallbackSample> {
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: const Text('Dialog content'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Accept'),
),
],
),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Post Frame Callback sample'),
),
body: const Center(
child: Text('Body of the Scaffold'),
),
);
}
}
Таким образом, мы можем без проблем отобразить диалог в начале жизненного цикла этого виджета. На самом деле, этот способ можно использовать и для любых других операций, требующих изменения пользовательского интерфейса, кроме показа диалога.
Если ваш виджет является StatelessWidget
, мы можем обернуть его в FutureBuilder
следующим образом:
class PostFrameCallbackSampleStateless extends StatelessWidget {
const PostFrameCallbackSampleStateless();
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _showDialog(context),
builder: (context, snapshot) => Scaffold(
appBar: AppBar(
title: const Text('Post Frame Callback Stateless'),
),
body: const Center(
child: Text('Body of the Scaffold'),
),
),
);
}
Future<void> _showDialog(BuildContext context) async {
WidgetsBinding.instance?.addPostFrameCallback((_) {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: const Text('Dialog content'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Accept'),
),
],
),
);
});
}
}
Показ диалога при добавлении нового виджета в дерево больше никогда не будет проблемой 😀.
Примеры из этого руководства вы можете найти здесь.
Счастливого кодинга!