Search code examples
flutterdartgoogle-cloud-firestorefuture

Dart Function always returns a Future


I build a repository to get data from firestore and return it as a list of objects called Keekzs.

The required aim is to have a Future<KtList<Keekz>> returned, however I am stuck at the point that for whatever reason I am always getting Future<KtList<Future<Keekz>>> to be returned. I isolated most of the inner parts and prefix it with an await expression wherever possible, but nothing changes.

Does anyone have an idea?

Here is my code:

final keekzs = await FirebaseFirestore.instance.collection(Paths.keekzsToplevelCollection).get().then(
          (keekzsSnap) async => keekzsSnap.docs.map((keekzDoc) async {
            final keekzDocData = keekzDoc.data();
            final cardData = await keekzDoc.reference
                .collection(Paths.keekzCardsSublevelcollection)
                .get()
                .then(
                  (snapshotCards) async => snapshotCards.docs,
                );
            keekzDocData.addAll({
             "keekzCards": Map.fromEntries(
                cardData.map(
                  (cardDataDoc) => MapEntry(cardDataDoc.id, cardDataDoc.data()),
                ),
              )
            });
            return keekzDocData;
          }).map((keekzMapFuture) async {
            final keekzDTO = await keekzMapFuture.then(
              (keekzMap) => KeekzDto.fromFirestoreData(
                keekzMap,
                keekzMap['keekzId'].toString(),
              ).toDomain(),
            );
            return keekzDTO;
          }).toImmutableList(),
        );
 return keekzs;

Solution

  • Like @jamesdlin mentioned in the comment :

    Calling .map() with an async callback will return an Iterable of Futures. You can use Future.wait to obtain a List<T> from an Iterable<Future<T>>.

    If you observe your map function returning Future<Keekz> instead of Keekz because you are awaiting your result and that leads to Future<List<Future<Keekz>>> instead of the desired Future<List<Keekz>>.

    Instead of async await you can use Future.wait to await all the futures returned by the map function and then convert the resulting list of Keekz objects into an immutable list with the help of toImmutableList()

    The updated code may look something like :

    final keekzsSnap = await FirebaseFirestore.instance
        .collection(Paths.keekzsToplevelCollection)
        .get();
    final keekzFutures = keekzsSnap.docs.map((keekzDoc) async {
      final keekzDocData = keekzDoc.data();
      final cardDataSnap = await keekzDoc.reference
          .collection(Paths.keekzCardsSublevelcollection)
          .get();
      final cardData = cardDataSnap.docs;
      keekzDocData.addAll({
        "keekzCards": Map.fromEntries(
          cardData.map(
            (cardDataDoc) => MapEntry(cardDataDoc.id, cardDataDoc.data()),
          ),
        ),
      });
      return keekzDocData;
    });
    
    final keekzDTOFutures = await Future.wait(keekzFutures);
    final keekzs = keekzDTOFutures.map((keekzMap) =>
        KeekzDto.fromFirestoreData(
          keekzMap,
          keekzMap['keekzId'].toString(),
        ).toDomain()).toImmutableList();
    
    return keekzs;
    

    Here I have separated the fetching of keekzsSnap and mapping logic to get simplicity. By using Future.wait() we have awaited all the futures returned by mapping logic and stored in the keekzDTOFutures variable.

    Finally, we map over keekzDTOFutures to convert each keekzMap into a Keekz object, and convert the resulting list into an immutable list using toImmutableList().

    Reference : wait static method