This is the code I am trying to run:
class TransactionEntity {
final Entity entity;
final DocumentReference<Map<String, dynamic>> document;
DocumentSnapshot? snapshot;
final String writeOperation;
TransactionEntity(
{required this.entity,
required this.document,
required this.writeOperation,
this.snapshot});
void set(DocumentSnapshot documentSnapshot) {
snapshot = documentSnapshot;
}
DocumentSnapshot? get documentSnapshot {
return snapshot;
}
Transaction? executeWriteOperation(Transaction transaction) {
if (writeOperation == "update") {
Map<String, dynamic> attributesToUpdate = {};
var entityData = entity.toJson();
if (snapshot != null) {
var data = snapshot!.data() as Map<String, dynamic>;
entityData.forEach((attributeName, attributeValue) {
if (attributeValue is List) {
if (!listEquals(attributeValue, data[attributeName])) {
attributesToUpdate[attributeName] = data[attributeName];
}
} else if (attributeValue is Entity || attributeValue is Map) {
var nestedMap = {};
var nestedObject = attributeValue.toJson();
nestedObject.forEach((key, value) {
if (value != data[attributeName][key]) {
String newKey = "$attributeName.$key";
nestedMap[newKey] = data[attributeName][key];
}
});
if (nestedMap.isNotEmpty) {
attributesToUpdate[attributeName] = nestedMap;
}
} else if (data[attributeName] != attributeValue) {
attributesToUpdate[attributeName] = attributeValue;
}
});
}
return transaction.update(document, attributesToUpdate);
} else if (writeOperation == "set") {
return transaction.set(document, entity.toJson());
} else if (writeOperation == "delete" && snapshot!.exists) {
return transaction.delete(document);
}
return null;
}
}
Future<void> updateProductTransaction(
Product product,
int nLocations,
) async {
try {
List<ProductVariant> productVariants =
product.variants == null || product.variants!.isEmpty
? [product.defaultVariant!]
: product.variants!;
List<ProductOption> productOptions = product.options ?? [];
DocumentReference<Map<String, dynamic>> productDocument =
FirebaseFirestore.instance.collection("product").doc(product.id);
List<DocumentReference<Map<String, dynamic>>> productVariantDocuments =
await FirebaseFirestore.instance
.collectionGroup("productVariant")
.where("parentId", isEqualTo: product.id)
.get()
.then((value) => value.docs.map((e) => e.reference).toList());
Map<String, List<DocumentReference<Map<String, dynamic>>?>>
inventoryItemDocuments = {};
for (var variant in productVariants) {
await FirebaseFirestore.instance
.collectionGroup("inventoryItem")
.where("parentId", isEqualTo: variant.id)
.get()
.then((value) {
inventoryItemDocuments[variant.id!] =
value.docs.map((e) => e.reference).toList();
});
if (inventoryItemDocuments.isEmpty) {
inventoryItemDocuments[variant.id!] =
List.generate(nLocations, (index) => null);
}
}
List<DocumentReference<Map<String, dynamic>>> productOptionDocuments = [];
if (productOptions.isNotEmpty) {
productOptionDocuments = await FirebaseFirestore.instance
.collectionGroup("productOption")
.where("parentId", isEqualTo: product.parentId)
.get()
.then((value) => value.docs.map((e) => e.reference).toList());
}
return await FirebaseFirestore.instance
.runTransaction((transaction) async {
DocumentSnapshot productSnapshot =
await transaction.get(productDocument);
if (!productSnapshot.exists) {
throw EntityException("Prodotto non esistente!");
}
List<TransactionEntity> transactions = [];
transactions.add(TransactionEntity(
entity: product,
document: productDocument,
writeOperation: "update",
snapshot: productSnapshot,
));
for (var productVariant in productVariants) {
DocumentSnapshot? productVariantSnapshot = await transaction.get(
productVariantDocuments.firstWhere(
(variant) => variant.id == productVariant.id,
orElse: null));
var productVariantDocument = FirebaseFirestore.instance.doc(
"{product/${product.id}/productVariant/${productVariant.id}}");
if (productVariantSnapshot.exists) {
transactions.add(TransactionEntity(
entity: productVariant,
document: productVariantDocument,
writeOperation: "update",
snapshot: productVariantSnapshot,
));
for (var inventoryItem in productVariant.inventoryItems!) {
var inventoryItemDocument = FirebaseFirestore.instance.doc(
"{product/${product.id}/productVariant/${productVariant.id}/inventoryItem/${inventoryItem.id}",
);
var doc = inventoryItemDocuments[productVariant.id]!.firstWhere(
(item) => item?.id == inventoryItem.id,
orElse: null);
DocumentSnapshot? inventoryItemSnapshot;
if (doc != null) await transaction.get(doc);
transactions.add(TransactionEntity(
entity: inventoryItem,
document: inventoryItemDocument,
writeOperation: "update",
snapshot: inventoryItemSnapshot,
));
}
} else {
transactions.add(TransactionEntity(
entity: productVariant,
document: productVariantDocument,
writeOperation: "set",
));
for (var inventoryItem in productVariant.inventoryItems!) {
var inventoryItemDocument = FirebaseFirestore.instance.doc(
"{product/${product.id}/productVariant/${productVariant.id}/inventoryItem/${inventoryItem.id}",
);
transactions.add(TransactionEntity(
entity: inventoryItem,
document: inventoryItemDocument,
writeOperation: "set",
));
}
}
}
for (var optionDocument in productOptionDocuments) {
DocumentSnapshot? productOptionSnapshot =
await transaction.get(optionDocument);
var option = productOptions
.firstWhere((option) => option.id == optionDocument.id);
var productOptionDocument = FirebaseFirestore.instance
.doc("{product/${product.id}/productOption/${option.id}}");
if (productOptionSnapshot.exists) {
transactions.add(TransactionEntity(
entity: option,
document: productOptionDocument,
writeOperation: "update",
snapshot: productOptionSnapshot,
));
} else {
transactions.add(TransactionEntity(
entity: option,
document: productOptionDocument,
writeOperation: "set"));
}
}
} catch (e, stacktrace) {
print(stacktrace);
throw e;
}
}
When it run I get an error:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49 throw_
packages/firebase_core/src/internals.dart 91:18 <fn>
dart-sdk/lib/async/zone.dart 1687:54 runUnary
dart-sdk/lib/async/future_impl.dart 178:22 handleError
dart-sdk/lib/async/future_impl.dart 779:46 handleError
dart-sdk/lib/async/future_impl.dart 800:13 _propagateToListeners
dart-sdk/lib/async/future_impl.dart 610:5 [_completeError]
dart-sdk/lib/async/future_impl.dart 879:16 <fn>
dart-sdk/lib/async/zone.dart 1692:54 runBinary
dart-sdk/lib/async/future_impl.dart 175:22 handleError
dart-sdk/lib/async/future_impl.dart 779:46 handleError
As you can see in the code, all reads are executed before write operations.
I am using a catch in the method updateProductTransaction
but why is not catched that exception?
The TransactionEntity collects Documents and operations to execute after reads.
The ProductVariant, ProductOptions are subcollections of the Product collection.
The InventoryItem is a subcollection of the ProductVariant subcollection.
It can't find the document because of the wrong path, for example:
"{product/${product.id}/productVariant/${productVariant.id}}");
I had to remove the '{', '}'.