Как правильно получать API с помощью Provider во Flutter

В настоящее время я сталкиваюсь с проблемой, когда мне нужны данные из API, чтобы отобразить их в моих виджетах. Я следую некоторому шаблону архитектуры поставщика, где вы устанавливаетеState два раза:

1- При получении данных

2- Когда данные уже получены

Итак, проблема, с которой я сейчас сталкиваюсь, заключается в том, что мой виджет выдает следующую ошибку:

setState() or markNeedsBuild() called during build.

Я знаю, что эта ошибка вызвана тем, что setState вызывается во время сборки, но ... как я могу получить свой api во время сборки, а затем показать его своим виджетам? Вот мой код:

NewsPage.dart

    class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  SideBarWidget _sidebar;

  @override
  void initState() {
    Provider.of<HomeViewModel>(context, listen: false)
        .fetchUltimaNoticia(context); --> ****Data to get fetched****
    _sidebar = const SideBarWidget();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('INICIO'),
        centerTitle: true,
        automaticallyImplyLeading: false,
        leading: Builder(
          builder: (context) => IconButton(
            icon: const Icon(Icons.menu),
            onPressed: () => Scaffold.of(context).openDrawer(),
          ),
        ),
      ),
      drawer: _sidebar,
      body: FormNoticiaContainer(),
    );
  }
}

FormContainer ()

Widget build(BuildContext context) {
    return _crearBodyNoticia(context);
  }

  Widget _crearBodyNoticia(context) {
    final homeVm = Provider.of<HomeViewModel>(context, listen: true);
    return homeVm.state == ViewState.Busy
        ? Center(child: CircularLoading())
        : Center(
            child: DataToShowWidget()

HomeViewModel.dart

    class HomeViewModel extends BaseModel {
  ////////
  //NOTICIAS
  ////////

  NoticiaDB _noticia;

  NoticiaDB get noticia => _noticia;

  set setNoticia(NoticiaDB noticia) {
    _noticia = noticia;
  }

  Future fetchUltimaNoticia(BuildContext context) async {
    setState(ViewState.Busy);

    var response = await noticiaProvider.obtenerNoticiaPublicada();

    setNoticia = response;

    setState(ViewState.Idle);
  }
}

Базовая модель

 ViewState _state = ViewState.Idle;

  ViewState get state => _state;

  void setState(ViewState viewState) {
    _state = viewState;
    notifyListeners();
  }

person Nikssj_    schedule 11.02.2020    source источник
comment
Вы не выполняете загрузку в течение build. Используйте FutureBuilder. См. stackoverflow.com/a/60141803/7034640   -  person Ted Henry    schedule 11.02.2020
comment
То же самое, если я получу с помощью FutureBuilder, setState будет вызван снова, и проблема будет той же ..   -  person Nikssj_    schedule 11.02.2020
comment
Это не тоже самое. Ошибка не произойдет.   -  person Ted Henry    schedule 11.02.2020
comment
Я имею в виду, что да, потому что если я помещу выборку API в future builder, то цикл будет следующим: Futurebuilder - ›Builds -› Fetch Api внутри futurebuilder (which setState) - ›затем снова перестраивает -› call futurebuilder - ›... ... снова тот же цикл   -  person Nikssj_    schedule 11.02.2020
comment
В настоящее время я сталкиваюсь с проблемой, когда мне нужны данные из API, чтобы отобразить их в моих виджетах. Именно это и делает приведенный мною пример с использованием FutureBuilder, и ошибки нет. Запустите его и убедитесь, что он работает.   -  person Ted Henry    schedule 11.02.2020
comment
Пробовал и заставил тоже работать. В чем разница с другим решением? Что касается читабельности, я предпочитаю другой .. но я не знаю, какой из них лучше или имеет лучшую производительность.   -  person Nikssj_    schedule 11.02.2020
comment
FutureBuilder - лучшая практика. Именно ваш вариант использования объясняет, почему FutureBuilder является частью Flutter. Ваш вариант использования и FutureBuilder являются частью документации Flutter. flutter.dev/docs/cookbook/networking/fetch-data   -  person Ted Henry    schedule 11.02.2020
comment
Если вы собираетесь придерживаться подхода HomeViewModel, используйте Consumer поставщика пакетов, а не Provider.of.   -  person Ted Henry    schedule 11.02.2020


Ответы (1)


Вы можете использовать WidgetsBinding.instance.addPostFrameCallback
Подробнее см. https://www.didierboelens.com/faq/week2/

фрагмент кода

@override
void initState(){
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((_){
    Provider.of<HomeViewModel>(context, listen: false)
    .fetchUltimaNoticia(context);  

  });
}
person chunhunghan    schedule 11.02.2020
comment
Это работает так, как я хотел. Это правильная практика? Я имею в виду, что если мне нужно получить данные при нажатии новой страницы, я могу использовать это без каких-либо проблем? - person Nikssj_; 11.02.2020
comment
да. Без каких-либо проблем. - person chunhunghan; 11.02.2020
comment
@NicoAybar, пожалуйста, отметьте это как ответ, если он вам поможет. благодаря. - person chunhunghan; 11.02.2020