Flutter получает контекст в методе initState

Я не уверен, что initState - это правильная функция для этого. То, что я пытаюсь достичь, - проверить, когда страница отображается для выполнения некоторых проверок, и на основе их открытия AlertDialog чтобы при необходимости внести некоторые настройки.

У меня есть Страница, в которой есть состояние. Функция initState выглядит так:

@override
void initState() {
    super.initState();
    if (!_checkConfiguration()) {
        _showConfiguration(context);
    }
}

_showConfiguration:

void _showConfiguration(BuildContext context) {
    AlertDialog dialog = new AlertDialog(
        content: new Column(
            children: <Widget>[
                new Text('@todo')
            ],
        ),
        actions: <Widget>[
            new FlatButton(onPressed: (){
                Navigator.pop(context);
            }, child: new Text('OK')),
        ],
    );

    showDialog(context: context, child: dialog);
}

Если есть лучший способ сделать эти проверки и, если нужно, вызвать модальный, пожалуйста, укажите мне в правильном направлении, я искал функцию onState или onRender или обратный вызов, который я мог бы назначить функции build которая будет вызываться в рендеринге, но не смог найти его.


Редактировать: он швы здесь, у них была аналогичная проблема: Flutter Redirect на страницу в initState

Ответ 1

Контекст переменной-члена может быть доступен во время initState, но не может использоваться для всего. Это из флаттера документации initState:

Вы не можете использовать [BuildContext.inheritFromWidgetOfExactType] из этого метод. Однако, [didChangeDependencies] будет вызван немедленно следуя этому методу, и [BuildContext.inheritFromWidgetOfExactType] можно использовать там.

Вы можете переместить логику инициализации на didChangeDependencies, однако это может быть не совсем то, что вам нужно, поскольку didChangeDependencies можно вызывать несколько раз в жизненном цикле виджета.

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

Простой способ сделать это - использовать будущее.

Future.delayed(Duration.zero,() {
  ... showDialog(context, ....)
}

Другой способ, который может быть более "правильным", - это использовать планировщик флаттера для добавления обратного вызова после фрейма:

SchedulerBinding.instance.addPostFrameCallback((_) {
  ... showDialog(context, ....)
});

И, наконец, вот небольшая хитрость, которую я хотел бы сделать, чтобы использовать асинхронные вызовы в функции initState:

() async {
  await Future.delayed(Duration.zero);
  ... showDialog(context, ...)      
}();

Вот полностью проработанный пример с использованием простого Future.delayed:

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  bool _checkConfiguration() => true;

  void initState() {
    super.initState();
    if (_checkConfiguration()) {
      Future.delayed(Duration.zero,() {
        showDialog(context: context, builder: (context) => AlertDialog(
          content: Column(
            children: <Widget>[
              Text('@todo')
            ],
          ),
          actions: <Widget>[
            FlatButton(onPressed: (){
              Navigator.pop(context);
            }, child: Text('OK')),
          ],
        ));
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
    );
  }
}

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

Ответ 2

Обертывание с Future

  @override
  void initState() {
    super.initState();
    _store = Store();
    new Future.delayed(Duration.zero,() {
      _store.fetchContent(context);
    });
  }

Ответ 3

Вы должны использовать пакет AfterLayout, просто посмотрите его пример, и он предоставит прекрасное решение этой проблемы.

https://pub.dartlang.org/packages/after_layout

Ответ 4

Простое использование Timer.run()

@override
void initState() {
  super.initState();
  Timer.run(() {
    // you have a valid context here
  });
}