Double update in FutureBuilder?

Issue

I have a FutureBuilder which is getting data from my firebase realtime database. Result should be a list of all posts, that also works but only for one second or so, it gets updated two times for what ever reason and then the second time the data is gone again. Here is my code for the request:

Future<List<Posts>> getpostsOfThread() async {
    List<String> threads = [];

    await database
        .ref()
        .child("users/")
        .child(user!.uid)
        .child("threadsFollowing")
        .once()
        .then((value) => {
              value.snapshot.children.forEach((element) {
                threads.add(element.value.toString());
              })
            });
    List<Posts> posts = [];

    threads.forEach((element) async {
      print("?");
      await database
          .ref()
          .child("SecretChat")
          .child("threads")
          .child(element)
          .child("posts")
          .once()
          .then((value) => {
                value.snapshot.children.forEach((element) async {
                  posts.add(Posts(
                      postID: element.key.toString(),
                      title: element.child("title").value.toString(),
                      likes: int.parse(element.child("likes").value.toString()),
                      picturePath:
                          element.child("picturePath").value.toString()));
                })
              });
    });

    return posts;
  }

And here the code of the FutureBuilder:

Container(
            decoration: BoxDecoration(
                color: Color.fromRGBO(247, 241, 236, 1),
                borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(30),
                    topRight: Radius.circular(30))),
            height: height * 0.7379,
            child: SafeArea(
                minimum: EdgeInsets.all(20),
                child: (Container(
                    child: Center(
                        child: Column(children: [
                  //Text(massages.toString()),
                  Flexible(
                      child: FutureBuilder(
                          future: getpostsOfThread(),
                          builder: (BuildContextcontext,
                              AsyncSnapshot<List<Posts>> snapshot) {
                            print(snapshot.data);
                            if (snapshot.hasData) {
                              int lenght = snapshot.data!.length;
                              print(lenght);

                              return Container(
                                  child: ListView.builder(
                                itemCount: lenght,
                                itemBuilder: (context, index) {
                                  return Container(
                                    child: Text(
                                        snapshot.data![index].likes.toString()),
                                  );
                                },
                              ));
                            }

                            return Container();
                          }))
                ]))))))

Solution

There are generally 2 ways to wait for the results of a Future.

(1) myFuture.then((value) {});

(2) await myFuture();

When using method (1), the sequence of code will continue after the call and when the Future finishes, the then() method triggers and that code runs.

For example:

myMethod() {
  myFuture.then((value) { console.log("myFuture Finished"); });
  console.log("myMethod Finished");
}

Here, you should see "myMethod Finished" print to the console before "myFuture Finished."

When you need to wait for the future to finish before moving on to the next code sequence, you use the await keyword.

myMethod() async {
  await myFuture();
  console.log("myMethod Finished");
}

With this example. "myMethod Finished" will only appear in the console once the Future method has completed.

You should also be aware that making the forEach method of a List is very similar to calling a Future method in that code that follows will continue to execute while the forEach loop iterates. When needing to await data from a Future for each iteration of a loop, you will want to use different syntax. I often fall back to for(int i = 0; i < list.length; i++) {} because it has worked consistently for me.

With this information, I have modified your code to take advantage of the await keyword and ensured that the getpostsOfThread() method will only return once all of the code within has finished processing which should ensure that the threads and posts are returned. Here is what I came up with:

Future<List<Posts>> getpostsOfThread() async {
    
    // assign the result to a variable
    var value = await database
            .ref()
            .child("users/")
            .child(user!.uid)
            .child("threadsFollowing")
            .once();
            
    // simplify mapping the results to the threads
    List<String> threads = value.snapshot.children.map((element) => element.value.toString()).toList();

    List<Posts> posts = [];
    
    // don't use .forEeach with an async
    // instead use a loop that can take advantage of the existing async
    for(int i = 0; i < threads.length; i++) {
        // assign the result for this thread to a variable
        var value2 = await database
                .ref()
                .child("SecretChat")
                .child("threads")
                .child(threads[i])
                .child("posts")
                .once();
        // without async, loop results and create Posts
        value2.snapshot.children.forEach((element) {
            posts.add(Posts(
                    postID: element.key.toString(),
                    title: element.child("title").value.toString(),
                    likes: int.parse(element.child("likes").value.toString()),
                    picturePath:
                            element.child("picturePath").value.toString()));
        });
    }
    
    // finally return the list of Posts
    return posts;
    
}

Keep in mind that this code makes a lot of assumptions and does not error check anything. I would recommend adding error checking / catching where appropriate to avoid unexpected crashes.

Answered By – daddygames

Answer Checked By – Clifford M. (FlutterFixes Volunteer)

Leave a Reply

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