3

I'm Creating an infinite scroll listView and i want to change the _loadingState String from 'loading...' to 'loaded' using setState in the _loadNames function, but when _loadNames is called from itemBuilder, I get the 'setState called during build error'.Using the RefreshIndicator works fine and updates the items, but scrolling to the bottom causes the error. How can i be able to call _loadNames from the ListViews builder without getting an error or what other approach can i use. NB: Dont want to use redux or bloc.

class _HomePageState extends State<HomePage> {
     List<String> names = [];
     List<String> _shownNames = [];
     int currentPage = 0;
     int limit = 20;
     String _loadingState = 'loading';
     bool loading = true;

     @override
     void initState() {
       super.initState();
       for (int i = 0; i < 200; i++) {
         names.add("hello $i");
       }
       _loadNames();
     }

     @override
     Widget build(BuildContext context) {
       // TODO: implement build
       return new Scaffold(
         appBar: new AppBar(title: new Text('User')),
         body: Column(children: <Widget>[
           Text(_loadingState),
           Expanded(child:_getListViewWidget()),
         ],)
       );
     }

     Widget _getListViewWidget(){
       ListView lv =  new ListView.builder(
         itemCount: _shownNames.length,
         itemBuilder: (context, index){
         if(index >= _shownNames.length - 5 && !loading){
           _loadNames(); // Getting error when this is called
         }
         return  ListTile(
           title: Text(_shownNames[index]),
         );
       });

       RefreshIndicator refreshIndicator = new RefreshIndicator(
         key: _refreshIndicatorKey,
         onRefresh: (){
           _loadNames();
           return null;
         },
         child: lv
       );
       return refreshIndicator;
     }

    _loadNames(){
       loading = true;
       setState(() {
         _loadingState = 'loading...';
       });

       new Timer(const Duration(seconds: 5), () {
          setState(() {
            _shownNames.addAll(names.getRange(currentPage, currentPage + limit));
           currentPage += limit;
           _loadingState = 'loaded';
         });
         loading = false;
       });
     }
  }
nonybrighto
  • 8,752
  • 5
  • 41
  • 55

1 Answers1

5

Change _loadNames() {

to

_loadNames(){
   loading = true;
   // setState(() {
     _loadingState = 'loading...';
   // });

and

     onRefresh: (){
       _loadNames();
       return null;
     },

to

     onRefresh: (){
       setState(() => _loadNames());
     },

update

_loadNames(){
   loading = true;

   new Future.delayed(Duration.zero, () => setState(() {
     _loadingState = 'loading...';
   }));
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks a lot for your help.The main problem is from The ListViews itemBuilder. How do i show a 'loading...' or 'loaded' text when `loadNames()` is called in itemBuilder without using setState within the loadNames function? What i want to achieve is simply an infinite scroll listview with progress indicator. – nonybrighto Jul 09 '18 at 17:37
  • I updated my answer. If you do the `setState()` delayed it won't interfere with `build`. This way you can keep the other code as it was. – Günter Zöchbauer Jul 09 '18 at 17:46
  • @GünterZöchbauer is there a name for this way of doing things? E.g. are we setting a static field directly and not using setState() so as to not trigger a rebuild? – Colin Ricardo Mar 12 '19 at 23:37
  • @Colin I don't really undestand your comment. My answer is about not calling `setState()` from `build()` – Günter Zöchbauer Mar 13 '19 at 03:58
  • Sorry, I wasn't too clear. In OP's question, it seems to me that calling `setState()` in `_loadNames()` causes his issues. In your response, it looks like instead of actually calling `setState()` you instead do `_loadingState = 'loading...';` directly — is that right? – Colin Ricardo Mar 13 '19 at 11:33
  • Because `_loadNames` is called using `setState()` already. Removing `setState()` from `_loadNames` and using it in `onRefresh: () ...` makes it possible to call `_loadNames` from within `build` (which is a bad idea anyway because `build()` shouldn't have side effects) where `setState()` **must not** be called and from an event handler (`onRefresh`) where `setState()` **is** required. – Günter Zöchbauer Mar 13 '19 at 11:37