I have a simple collection of dish, for each of those I have a list of ingredients.

I want to search all dishes with particular ingredients, but if I wrote an ingredients I can retrieve more than one ingredient. Imagine for example I have 3 dishes with Oil ingredient, but I can have different oil (Extra virgin, Peanut ...) so when I wrote OIL I have to retrieve all dish with all types of oil.

I have list of dishes where each dish is composed by:

dish_1-->
|       |-->name: name_1
|       |-->ingredients -->
|                         |--> 1 --> 
|                                  |-->id:1
|                                  |-->name: Extra Virgin Oil
|                         |--> 3 --> 
|                                  |-->id:3
|                                  |-->name: Pasta
|
dish_2-->
|       |-->name: name_2
|       |-->ingredients -->
|                         |--> 2 --> 
|                                  |-->id:2
|                                  |-->name: Peanut Oil
|                         |--> 3 --> 
|                                  |-->id:4
|                                  |-->name: Tomato

I used Model - Helper - Provider and my idea is retrieve all ingredient-ID with "Oil" from Ingredient collection and after query by those ID. in my dish_helpers I wrote:

Future<List<DishModel>> searchDishByIngredient({String ingredient}) {
    String searchKey = ingredient[0].toUpperCase() + ingredient.substring(1);
    List<DishModel> dishes = [];
    return _firestore.collection(subcollection).orderBy("name").startAt([searchKey]).endAt(
        [searchKey + '\uf8ff']).get().then((result) {
  for (DocumentSnapshot ingredient in result.docs) {
        _firestore.collection(collection).where("ingredients."+ingredient.id.toString()+".id",isEqualTo: int.parse(ingredient.id) ).get().then((result) {
          for (DocumentSnapshot dish in result.docs) {
            dishes.add(DishModel.fromSnapshot(dish));
            print("Length: " + dishes.length.toString());
          }
        });
      }
      return dishes;
    });
  }

and in dish Provider

Future searchByIngredient({String ingredientName}) async {
    dishesSearched = await _dishServices.searchDishByIngredient(ingredient: ingredientName);
    notifyListeners();
  }

When I search by ingredient, I obtain always an empty list but in console I obtain :"Length: 2".

I'm getting crazy in order to understand what I missing

Can anyone help me?

Other ways to perform search are accepted.

Thank you


Solution 1: Tarik Huber

It looks like you return the get call to the collection and not the List itself. I would recommend writing the code like this:

Future<List<DishModel>> searchDishByIngredient({String ingredient}) async {
  String searchKey = ingredient[0].toUpperCase() + ingredient.substring(1);
  List<DishModel> dishes = [];

  dynamic result = await _firestore
      .collection(subcollection)
      .orderBy("name")
      .startAt([searchKey]).endAt([searchKey + '\uf8ff']).get();

  for (DocumentSnapshot ingredient in result.docs) {
    dynamic result2 = await _firestore
        .collection(collection)
        .where("ingredients." + ingredient.id.toString() + ".id",
            isEqualTo: int.parse(ingredient.id))
        .get();

    for (DocumentSnapshot dish in result2.docs) {
      dishes.add(DishModel.fromSnapshot(dish));
      print("Length: " + dishes.length.toString());
    }
  }

  return dishes;
}

In your code snippet the async call to get the collection would be executed and log the data as you explained it but you would not get the List you expect as return as it returns you the Future for the get on the collection.


Solution 2: Lars

It looks like you are returning the dishes before you populated the List. This is because the for-loop has async calls in it (then()) and completes after you returned the dishes.
The solution is to simply use Future.forEach and await the execution of that. There is also no need to return the .then() callback, since you can just await it.

Future<List<DishModel>> searchDishByIngredient({String ingredient}) async {
    String searchKey = ingredient[0].toUpperCase() + ingredient.substring(1);
    List<DishModel> dishes = [];
    var result = await _firestore.collection(subcollection).orderBy("name").startAt([searchKey]).endAt([searchKey + '\uf8ff']).get();    
    await Future.forEach(result.docs, (DocumentSnapshot ingredient) {
      _firestore.collection(collection).where("ingredients." + ingredient.id.toString() + ".id",isEqualTo: int.parse(ingredient.id)).get().then((result) {
        for (DocumentSnapshot dish in result.docs) {
          dishes.add(DishModel.fromSnapshot(dish));
          print("Length: " + dishes.length.toString());
        }
      });
    });
    return dishes;
  }