I'm trying to build an e-commerce app using Flutter and Firestore, I'm having a challenge in building the cart. using the codes below, I have been able to get the products a user wishes to add to the cart using the product id. My challenge is how to use the id or the keys to fetch the details of the products from Firestore and store the cart products either in Firestore or SQLite so that I can query from there and display them on the cart page.
appstate.dart
class AppStateModel extends Model {
final Map<int, int> _productsInCart = <int, int>{};
Map<int, int> get productsInCart => Map<int, int>.from(_productsInCart);
void addProductToCart(int productId) {
if (!_productsInCart.containsKey(productId)) {
_productsInCart[productId] = 1;
} else {
_productsInCart[productId]++;
}
notifyListeners();
}
void removeItemFromCart(int productId) {
if (_productsInCart.containsKey(productId)) {
if (_productsInCart[productId] == 1) {
_productsInCart.remove(productId);
} else {
_productsInCart[productId]--;
}
}
notifyListeners();
}
void clearCart() {
_productsInCart.clear();
notifyListeners();
}
}
product_display.dart page with onPressed function to get the id of the item clicked to add to cart
CupertinoActionSheetAction(
child: const Text('Add To Cart'),
onPressed: () {
model.addProductToCart(products[index].id);
},
)
product.dart
class Products{
final String category;
final String description;
final int id;
final int price;
final String title;
final String url;
const Products( {this.category,this.description,this.id, this.price, this.title, this.url,
});
}
CartProduct.dart
class CartProducts{
final String category;
final String description;
final int id;
final int price;
final String title;
final String url;
const CartProducts( {this.category,this.description,this.id, this.price, this.title, this.url,
});
}
Now let say I have products with ids 1, 4, 6, 9, 11 in product cart, when I print in the console using print(model.productsInCart.keys), this was the output (1, 4, 6, 9, 11), now my challenge is how to use these ids to query the products with ids 1,4,6,9,11 from Firestore collection products and store them either in Firebase or SQLite so that I can display them in the cart page for a user to view his/her items in cart.
I think this is what you are trying to do is
Firestore.instance.collection("collection").document("id").get().then((querySnapshot){
print(querySnapshot.data);
});
Obviously replace collection with your collection name and id with the id you are trying to fetch. Here I am using .then
syntax, but you can pass everything before .then
to the FutureBuilder
as usual.
Edit: You'll need to add a helper method for fetching all the data from the firestore.
Future<List<Products>> getCollection() async {
List<int> idList = [];
// productsInCart[key] = value; where key is id and value is amount in cart
productsInCart.forEach((key, value) {
idList.add(key);
});
List<Products> productList = [];
for (var id in idList) {
var documents = (await Firestore.instance
.collection('products')
.where('id', isEqualTo: id)
.getDocuments())
.documents;
if (documents.length > 0) {
var doc = documents[0]
.data; // if there are multiple documents with given id get first document
var prod = Products(
id: doc['id'],
title: doc['title'],
price: doc['price'],
category: doc['category'],
description: doc['description']);
productList.add(prod);
}
}
return productList;
}
then use FutureBuilder to build the list
FutureBuilder<List<Products>>(
future: getCollection(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var list = snapshot.data;
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) => ListTile(
title: Text("${list[index].title}"),
subtitle: Text(
"Amount in cart : ${productsInCart[list[index].id]}"),
));
} else {
return Text("");
}
},
);
I am using Future
and FutureBuilder
instead of Stream
and StreamBuilder
, because having to query using multiple ids is a tedious task in cloud firestore, since firestore doesn't have official support for Logical OR. So having to gather data from multiple stream sources is difficult. Using FutureBuilder has the same output as using StreamBuilder as long as product detail is not being changed while app is being used.
Edit 2: To use multiple stream sources use StreamGroup from async package. Here is what final code looks like
Stream<List<Products>> _getCollection() async* {
List<int> idList = [];
// productsInCart[key] = value; where key is id and value is amount in cart
productsInCart.forEach((key, value) {
idList.add(key);
});
StreamGroup<QuerySnapshot> streamGroup = StreamGroup();
for (var id in idList) {
var stream = Firestore.instance
.collection('products')
.where('id', isEqualTo: id)
.snapshots();
streamGroup.add(stream);
}
//using map to store productDetails so that same products from multiple stream events don't get added multiple times.
Map<int, Products> productMap = {};
await for (var val in streamGroup.stream) {
var documents = val.documents;
var doc = documents[0].data;
var product = Products.fromMap(doc);
productMap[product.id] = product;
yield productMap.values.toList();
}
}
StreamBuilder<List<Products>>(
stream: _getCollection(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var values = snapshot.data;
return ListView.builder(
itemCount: values.length,
itemBuilder: (context, index) => ListTile(
title: Text(values[index].title),
subtitle: Text(
"Amount in cart : ${productsInCart[values[index].id]}"),
));
} else {
print("There is no data");
return Text("");
}
},
),
I've added named constructor for convenience
Products.fromMap(Map map) {
this.id = map['id'];
this.title = map['title'];
this.description = map['description'];
this.price = map['price'];
this.category = map['category'];
}