Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter - Dart : wait a forEach ends

I try to modify a string using data in each node I retrieve from Firebase database, and then to write a file with the modifed string (called "content").

Here is what I tried :

// Retrieve initial content from Firebase storage
var data = await FirebaseStorage.instance.ref().child("...").getData(1048576);
var content = new String.fromCharCodes(data);

// Edit content with each node from Firebase database
final response = await FirebaseDatabase.instance.reference().child('...').once();
response.value.forEach((jsonString) async {
     ...
     // cacheManager.getFile returns a Future<File>
     cacheManager.getFile(signedurl).then((file){ 
         // Modify content
         content=content.replaceAll('test',file.path);
     });
});

// Finally write the file with content
print("test");
final localfile = File('index.html');
await localfile.writeAsString(content);

Result :

"test" is shown before the forEach ends.

I found that we can do in Dart (https://groups.google.com/a/dartlang.org/forum/#!topic/misc/GHm2cKUxUDU) :

await Future.forEach

but in my case if I do : await Future.response.value.forEach (sounds a bit weird)

then I get :

Getter not found: 'response'. await Future.response.value.forEach((jsonString) async {

How to wait that forEach ends (with "content" modified) before to write the file with new content?

Any idea?

like image 214
Julien Avatar asked Dec 21 '18 14:12

Julien


2 Answers

If you use for(... in ) instead of forEach you can use async/await

Future someMethod() async {
  ...

  for(final jsonString in response.value) {
     ...
     // cacheManager.getFile returns a Future<File>
     cacheManager.getFile(signedurl).then((file){ 
         // Modify content
         content=content.replaceAll('test',file.path);
     });
  });
}

With forEach the calls fire away for each jsonString immediately and inside it await works, but forEach has return type void and that can't be awaited, only a Future can.

like image 122
Günter Zöchbauer Avatar answered Oct 02 '22 22:10

Günter Zöchbauer


You defined the callback for forEach as async, which means that it runs asynchronously. In other words: the code inside of that callback runs independently of the code outside of the callback. That is exactly why print("test"); runs before the code inside of the callback.

The simplest solution is to move all code that needs information from within the callback into the callback. But there might also be a way to await all of the asynchronous callbacks, similar to how you already await the once call above it.

Update I got working what I think you want to do. With this JSON:

{
  "data" : {
    "key1" : {
      "created" : "20181221T072221",
      "name" : "I am key1"
    },
    "key2" : {
      "created" : "20181221T072258",
      "name" : "I am key 2"
    },
    "key3" : {
      "created" : "20181221T072310",
      "name" : "I am key 3"
    }
  },
  "index" : {
    "key1" : true,
    "key3" : true
  }
}

I can read the index, and then join the data with:

final ref = FirebaseDatabase.instance.reference().child("/53886373");
final index = await ref.child("index").once();
List<Future<DataSnapshot>> futures = [];
index.value.entries.forEach((json) async {
  print(json);
  futures.add(ref.child("data").child(json.key).once());
});
Future.wait(futures).then((List<DataSnapshot> responses) {
  responses.forEach((snapshot) {
    print(snapshot.value["name"]);
  });
});
like image 33
Frank van Puffelen Avatar answered Oct 02 '22 23:10

Frank van Puffelen