Flutter PRO совет #0: Открыть диалог при запуске

Время от времени возникает необходимость показать диалог именно тогда, когда пользователь открывает новый экран. Возможно, это связано с определенными требованиями, чтобы показать ему ценную информацию или, возможно, вам нужно предупредить его о каком-то важном факте в вашем приложении; и, учитывая его актуальность, вы, возможно, решили показать диалог именно тогда, когда пользователь открывает экран.

📽 Видеоверсия доступна на 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'),
            ),
          ],
        ),
      );
    });
  }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Показ диалога при добавлении нового виджета в дерево больше никогда не будет проблемой 😀.

Примеры из этого руководства вы можете найти здесь.

Счастливого кодинга!

Оцените статью
Procodings.ru
Добавить комментарий