4
votes

Question is updated

How would I query 2 collections? I have a wishlist collection where each wishlist document looks like this:

documentID: "some-wishlist-id",
modified: "1564061309920",
productId: "2tqXaLUDy1IjxLafIu9O",
userId: "0uM7Dt286JYK6q8iLFyF4tG9cK53"

And a product collection where each product document looks like this. The productId in the wishlist collection would be a documentID in the product collection:

documentID: "2tqXaLUDy1IjxLafIu9O",
dateCreated: "1563820643577",
description: "This is a description for Product 9",
images: ["some_image.jpg"],
isNegotiable: true,
isSold: false,
rentPrice: 200,
sellPrice: 410,
title: "Product 9",
totalWishLists: 0,
userId: "0uM7Dt286JYK6q8iLFyF4tG9cK53"

NOTE: To be clear, the wishlist query should return a list of documents that I need to iterate over to retrieve the product.

Not sure if I need to use streams or futures in this case, but this is what I have so far:

Future<Product> getProduct(String documentId) {
return Firestore.instance
    .collection(APIPath.products())
    .document(documentId)
    .get()
    .then((DocumentSnapshot ds) => Product.fromMap(ds.data));
}

Query getWishListByUser(userId) {
    return Firestore.instance
        .collection(APIPath.wishlists())
        .where('userId', isEqualTo: userId);
}

Future<List<Product>> getProductsWishList(userId) async {
    List<Product> _products = [];

    await getWishListByUser(userId)
        .snapshots()
        .forEach((QuerySnapshot snapshot) {
      snapshot.documents.forEach((DocumentSnapshot snapshot) async {
        Map<String, dynamic> map = Map.from(snapshot.data);
        map.addAll({
          'documentID': snapshot.documentID,
        });

        WishList wishList = WishList.fromMap(map);

        await getProduct(wishList.productId).then((Product product) {
          print(product.title); // This is printing
          _products.add(product);
        });
      });
    });

    // This is not printing
    print(_products);

    return _products;
  }

Thanks

2
you want to render that data on list? means some fields from product and some from wishlist?Muhammad Noman
@MuhammadNoman I want to return just the products that are in the wishlist. So nothing from the wishlist.zee
and you have wishlistId of that wishlist?Muhammad Noman
Yes. I'll add documentID to the original question.zee
check the below answerMuhammad Noman

2 Answers

4
votes

With any database you'll often need to join data from multiple tables to build your view.

In relational databases, you can get the data from these multiple tables with a single statement, by using a JOIN clause.

But in Firebase (and many other NoSQL databases) there is no built-in way to join data from multiple locations. So you will have to do that in your code.

Create Wishlist Model:

class Wishlist {

  Wishlist({
    this.id,
    this.modified,
    this.productId,
    this.userId
  });

  final String id;
  final String modified;
  final String productId;
  final String userId;

  Wishlist.fromMap(json)
    : id = json['id'].toString(),
      modified = json['modified'].toString(),
      productId = json['productId'].toString(),
      userId = json['userId'].toString();
}

And in your API file, do this:

final Firestore _fireStore = Firestore.instance;    

getWishList(wishlistId) async {
  return await _fireStore.collection('wishlists').document(wishlistId).get();
}

getProduct(productId) async {
  return await _fireStore.collection('product').document(productId).get();
}

Future<List<Product>>getProductsWishList(wishlistId) async {

  var _wishlists = null;
  List<Product> _products = []; // I am assuming that you have product model like above

  await getWishList(wishlistId).then((val) {
    _wishlists = Wishlist.fromMap(val.data);

    _wishlists.forEach((wish) {
      await getProduct(wish.productId).then((product) {
          _products.add(product));
      });
    });
  });

  return _products;
}
2
votes

I found a solution last weekend but forgot to post it. Figured I do that now in case someone else has this problem.

I used a combination of StreamBuilder and FutureBuilder. Not sure if there is a better answer, perhaps combining multiple streams? But this worked for me.

return StreamBuilder<List<Wishlist>>(
  stream: wishListStream(userId),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      List<Wishlist> wishlists = snapshot.data;

      if (wishlists.length > 0) {
        return new GridView.builder(
          scrollDirection: Axis.vertical,
          itemCount: wishlists.length,
          itemBuilder: (context, index) {
            Future<Product> product =
                getProduct(wishlists[index].productId);

            return FutureBuilder(
              future: product,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  Product product = snapshot.data;

                  // Do something with product
                } else {
                  return Container();
                }
              },
            );
          },
          gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
          ),
        );
      } else {
        return Text('No products in your wishlist');
      }
    }

    if (snapshot.hasError) {
      print('WishlistPage - ${snapshot.error}');
      return Center(child: Text('Some error occurred'));
    }

    return Center(child: CircularProgressIndicator());
  },
);