'How to convert a sub collection to list with flutter and firestore?

I have an object Dish who containes a list of ingredients and I want to get them. How can I do? In Firebase, Dish is a Document and Ingredient is a sub collection. I tried this but it doesn't work.

class Dish{

  String name;
  DocumentReference reference;
  List<Ingredient> ingredients;

  Dish.fromMap(Map<String, dynamic> map, {this.reference}){
    this.name = map['name'];

    this
        .reference
        .collection("ingredients")
        .snapshots()
        .listen((QuerySnapshot snap) {
      final List<DocumentSnapshot> ingredientsDocuments = snap.documents;
      List<Ingredient> ing = [];
      for (var i = 0; i < ingredientsDocuments.length; i++) {
        ing.add(Ingredient.fromSnapshot(ingredientsDocuments[i]));
      }
      this.ingredients = ing;
    });
  }



  Dish.fromSnapshot(DocumentSnapshot snapshot)
      : this.fromMap(snapshot.data, reference: snapshot.reference);

  @override
  String toString() => "Dish<$String>";

}

class Ingredient{
  final String name;
  final DocumentReference reference;

  Ingredient.fromMap(Map<String, dynamic> map, {this.reference})
      : assert(map['name'] != null),
        name = map['name'];

  Ingredient.fromSnapshot(DocumentSnapshot snapshot)
      : this.fromMap(snapshot.data, reference: snapshot.reference);

  @override
  String toString() => "Ingredient<$String>";
}


Solution 1:[1]

How are you trying to fetch data from Firestore using Dish class? Were you using any asynchronous task(i.e. Future)? What's not working in your implementation? Any errors that you received?

Since I'm unable to run the repro you've provided, here's a sample code that you can try.

List<Ingredient> ingredients;
// call getDocuments() to fetch data from Firestore and add it to the List
Future<void> getDocuments() async {
  ingredients = List();
 
  var collection = FirebaseFirestore.instance
      .collection('ingredients');
 
  collection.get().then((value) {
    value.docs.forEach((element) {
      setState(() {
        // add the object to the List
        ingredients.add(Ingredient(Ingredient.fromMap(element.data())));
      });
    });
  });
}

As for the Object, it can be as simple as this. No need to pass DocumentReference since we'll only be using it to map the data to the Object and be able to add it in the List.

class Ingredients {
  var name;

  Ingredients(Ingredients document) {
    this.documentName = document.getName();
  }

  dynamic getName() => name;

  Ingredients.fromMap(Map<dynamic, dynamic> document)
      : name = document['name'];
}

You can check a working sample I've posted in here. It has pagination added, but it should have a similar approach with the code snippets I've shared here.

Solution 2:[2]

You can also convert a Dart Map to List using Iterable forEach() method instead.

var list = [];

Ingredients.entries.forEach((e) => list.add(Customer(e.key, e.value)));

print(list);

We apply forEach() to entries property of the map.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Omatt
Solution 2 Paresh Mangukiya