8

I'm currently dealing with a problem where I need some data from an API to show it into my widgets. I've following some Provider architecture pattern, where you setState two times:

1- When data is being fetched

2- When data is already fetched

So the problem I'm currently dealing is, my widget throws the following error:

setState() or markNeedsBuild() called during build.

I know this error is because setState is called during build, but.. how can I fetch my api during build, and then show it to my widgets? Here is my code:

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);
  }
}

BaseModel

 ViewState _state = ViewState.Idle;

  ViewState get state => _state;

  void setState(ViewState viewState) {
    _state = viewState;
    notifyListeners();
  }
Nikssj_
  • 187
  • 2
  • 3
  • 11
  • You don't fetch during `build`. Use `FutureBuilder`. See https://stackoverflow.com/a/60141803/7034640 – Ted Henry Feb 11 '20 at 02:31
  • Its the same, if I fetch using FutureBuilder, the setState is gonna get called again and the problem will be the same.. – Nikssj_ Feb 11 '20 at 02:33
  • It's not the same. The error won't happen. – Ted Henry Feb 11 '20 at 02:36
  • I mean yes, because if I put the API's fetch in the future builder, then the cycle will be: Futurebuilder -> Builds -> Fetch Api inside futurebuilder(Which setState) -> then rebuilds again -> call futurebuilder -> ...... same cycle again – Nikssj_ Feb 11 '20 at 02:39
  • "I'm currently dealing with a problem where I need some data from an API to show it into my widgets." That's exactly what the example I linked does using `FutureBuilder` and there is no error. Run it and see for yourself that it works. – Ted Henry Feb 11 '20 at 02:43
  • Tried and made it work too. Which is the difference with the other solution? In matters of readability, I prefer the other one.. but I dont know which one is a better practice or has a better performance – Nikssj_ Feb 11 '20 at 03:04
  • `FutureBuilder` is the better practice. Your use case is exactly why `FutureBuilder` is part of Flutter. Your use case and `FutureBuilder` are part of the Flutter documentation. https://flutter.dev/docs/cookbook/networking/fetch-data – Ted Henry Feb 11 '20 at 03:05
  • 1
    If you are going to stay with the `HomeViewModel` approach use package provider's `Consumer` rather than `Provider.of`. – Ted Henry Feb 11 '20 at 03:17

1 Answers1

13

You can use WidgetsBinding.instance.addPostFrameCallback
For detail, you can reference https://www.didierboelens.com/faq/week2/

code snippet

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

  });
}
chunhunghan
  • 51,087
  • 5
  • 102
  • 120