2

I have a Future which returns a map. I then need to use the values of that map to await another future and then return the entire result at the end. The problem is that dart can't await async Map.forEach() methods (see this: https://stackoverflow.com/a/42467822/15782390).

Here is my code:

the debug console shows that the items printed are in the following order:

flutter: getting journal entries
flutter: about to loop through pictures
flutter: getting picture
flutter: returning entries
flutter: [[....]] (Uint8List)
Future<List<JournalEntryData>> getJournalEntries() async {
    List<JournalEntryData> entries = [];
    print('getting journal entries');

    EncryptService encryptService = EncryptService(uid);

    await journal.get().then((document) {
      Map data = (document.data() as Map);
      print('about to loop through pictures');
      data.forEach((key, value) async {
        print('getting picture');
        dynamic pictures = await StorageService(uid).getPictures(key);
        print('done getting image');
        entries.add(JournalEntryData(
          date: key,
          entryText: encryptService.decrypt(value['entryText']),
          feeling: value['feeling'],
          pictures: pictures,
        ));
      });
    });
    print('returning entries');
    return entries;
  }
  Future getPictures(String entryID) async {
    try {
      final ref = storage.ref(uid).child(entryID);
      List<Uint8List> pictures = [];

      await ref.listAll().then((result) async {
        for (var picReference in result.items) {
          Uint8List? pic = await ref.child(picReference.name).getData();
          if (pic == null) {
            // TODO make no picture found picture
            var url = Uri.parse(
                'https://www.salonlfc.com/wp-content/uploads/2018/01/image-not-found-scaled-1150x647.png');
            var response = await http.get(url);
            pic = response.bodyBytes;
          }
          pictures.add(pic);
        }
      });
      return pictures;
    } catch (e) {
      print(e.toString());
      return e;
    }
  }

2 Answers2

2

It's quite annoying to have to use for-loops when you need async behaviour, specially on Maps, because as the other answer shows, that requires you to iterate over entries and then take the key and value out of it like this:

for (final mapEntry in data.entries) {
    final key = mapEntry.key;
    final value = mapEntry.value;
    ...
}

Instead of that, you can write a utility extension that does the work for you:

extension AsyncMap<K, V> on Map<K, V> {
  Future<void> forEachAsync(FutureOr<void> Function(K, V) fun) async {
    for (var value in entries) {
      final k = value.key;
      final v = value.value;
      await fun(k, v);
    }
  }
}

Then, you can use that like this:

await data.forEachAsync((key, value) async {
    ...
});

Much better.

Renato
  • 12,940
  • 3
  • 54
  • 85
  • 1
    Thanks for the help. That does seem like a more elegant solution. I hope dart by default allows for something similar in the `Future`, pun intended. –  Jul 07 '21 at 08:38
0

Don't mix the use of then and await since it get rather confusing and things are no longer being executed as you think. Also, the use of forEach method should really not be used for complicated logic like what you are doing. Instead, use the for-each loop. I have tried rewrite getJournalEntries here:

Future<List<JournalEntryData>> getJournalEntries() async {
  List<JournalEntryData> entries = [];
  print('getting journal entries');

  EncryptService encryptService = EncryptService(uid);

  final document = await journal.get();
  Map data = (document.data() as Map);
  print('about to loop through pictures');

  for (final mapEntry in data.entries) {
    final key = mapEntry.key;
    final value = mapEntry.value;

    print('getting picture');
    dynamic pictures = await StorageService(uid).getPictures(key);
    print('done getting image');
    entries.add(JournalEntryData(
      date: key,
      entryText: encryptService.decrypt(value['entryText']),
      feeling: value['feeling'],
      pictures: pictures,
    ));
  }
  print('returning entries');
  return entries;
}

And getPictures here. I have only removed the use of then here.

Future getPictures(String entryID) async {
  try {
    final ref = storage.ref(uid).child(entryID);
    List<Uint8List> pictures = [];
    final result = await ref.listAll();

    for (var picReference in result.items) {
      Uint8List? pic = await ref.child(picReference.name).getData();
      if (pic == null) {
        // TODO make no picture found picture
        var url = Uri.parse(
            'https://www.salonlfc.com/wp-content/uploads/2018/01/image-not-found-scaled-1150x647.png');
        var response = await http.get(url);
        pic = response.bodyBytes;
      }
      pictures.add(pic);
    }
    return pictures;
  } catch (e) {
    print(e.toString());
    return e;
  }
}
julemand101
  • 28,470
  • 5
  • 52
  • 48
  • 1
    Thank you very much. The code does look cleaner now. Thanks for the help! It also fixed my problem! Have a great day! –  Jul 04 '21 at 19:49