zee
zee

Reputation: 666

Flutter Firestore query 2 collections

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

Upvotes: 5

Views: 7880

Answers (2)

zee
zee

Reputation: 666

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());
  },
);

Upvotes: 2

Muhammad Noman
Muhammad Noman

Reputation: 1366

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;
}

Upvotes: 4

Related Questions