How to load list only after FutureBuilder gets snapshot.data?

Issue

I have a JSON method that returns a List after it is completed,

Future<List<Feed>> getData() async {
  List<Feed> list;
  String link =
      "https://example.com/json";
  var res = await http.get(link);
  if (res.statusCode == 200) {
    var data = json.decode(res.body);
    var rest = data["feed"] as List;
    list = rest.map<Feed>((json) => Feed.fromJson(json)).toList();
  }
  return list;
}

I then call this, in my initState() which contains a list hello, that will filter out the JSON list, but it shows me a null list on the screen first and after a few seconds it loads the list.

    getData().then((usersFromServer) { 
          setState(() {.   //Rebuild once it fetches the data
hello = usersFromServer
          .where((u) => (u.category.userJSON
              .toLowerCase()
              .contains('hello'.toLowerCase())))
          .toList();
            users = usersFromServer;
            filteredUsers = users;
          });
        });

This is my FutureBuilder that is called in build() method, however if I supply the hello list before the return statement, it shows me that the method where() was called on null (the list method that I am using to filter out hello() )

FutureBuilder(
            future: getData(),
            builder: (context, snapshot) {
              return snapshot.data != null ?
                Stack(
                  children: <Widget>[
                    CustomScrollView(slivers: <Widget>[
                      SliverGrid(
  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 200.0,
    mainAxisSpacing: 10.0,
    crossAxisSpacing: 10.0,
    childAspectRatio: 4.0,
  ),
  delegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return Container(
        child: puzzle[0],
      );
    },
    childCount: 1,
  ),
)
                    ]),
                  ],
                )
               :
               CircularProgressIndicator();
              
            });

Solution

You are calling your getData method multiple times. Don’t do that. Your UI waits for one call and your code for the other. That’s a mess. Call it once and wait for that call.

You need to define the future to wait for in your state:

Future<void> dataRetrievalAndFiltering;

Then in your initstate, assign the whole operation to this future:

(note that I removed the setState completely, it’s not needed here anymore)

dataRetrievalAndFiltering = getData().then((usersFromServer) { 
      hello = usersFromServer.where((u) => (u.category.userJSON.toLowerCase().contains('hello'.toLowerCase()))).toList();
        users = usersFromServer;
        filteredUsers = users;
      
    });

Now your FurtureBuilder can actually wait for that specific future, not for a new Future you generate by calling your method again:

FutureBuilder(
        future: dataRetrievalAndFiltering,
        builder: (context, snapshot) {

Now you have one Future, that you can wait for.

Answered By – nvoigt

Answer Checked By – David Goodson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *