Cloud Firestore Transactions & Batched Writes with Flutter

Akash Debnath
4 min readAug 8, 2020

--

Cloud Firestore Transactions and Batched Writes with Flutter.

Why we should use Transactions

Transactions are very important topic in database. There are some cases when you want to run atomic operation in database. Atomic means either it happens completely or nothing. Some examples are -

  1. You want to transfer money from one account to another. But in the middle of the transfer your account is credited by someone else. Now in this case you will be in a problem as in the client side you have the old account balance.
  2. You want to change your name in your social media account and as Cloud Firestore keep a denormalized structure, your name in all your post have to be changed also. If any error happens then some of your post will have your old name and some with new updated name.

So, to handle all this situation we have to use transactions. Using the Cloud Firestore client libraries, you can group multiple operations into a single transaction. Now cloud firestore does not lock the database in case of transaction as SQL Databases, as client app may go offline during a transaction. And if it happens then that document will be locked for infinite time.

In all the examples the _firestore refers to the CloudFirestore instance. Keep that in mind.

Firestore _firestore = Firestore.instance;

How a Cloud Firestore Transaction works

Transactions are atomic and they don’t work in Offline Mode.

  1. First it reads any number of document from the database.
  2. Then it perform some logical works if needed.
  3. Now, before any write operation it checks if any document related to this transaction read has been changed or modified or updated. If it has, then it goes to step 1 and again do all things.
  4. If no document modification, then it performs the write operations.

Rules of Cloud Firestore Transactions

There are some rules when we will perform any transaction in Cloud Firestore

  1. All read operations have to be done first, then we will perform write operations. Otherwise you will get errors.
  2. Function which call a transaction may run more than one time. So we should not modify app state inside a transaction.
  3. Transaction will fail when we go offline. It’s obvious because transaction runs on up to date data from server.
  4. We will do all the read and write operation by Transaction variable provided by the TransactionHandler , not by the document or collection reference itself.
  5. You can write to max 500 documents in one transaction.

Transaction Example

  1. Example 1 — Social Media post likes increment.
await _firestore.runTransaction((transaction)async{
DocumentReference postRef = _firestore.collection('posts')
.document(documentId);
DocumentSnapshot snapshot = await transaction.get(postRef);
int likesCount = snapshot.data['likes'];
await transaction.update(postRef,{
'likes' : likesCount + 1
});
});

In this example we are incrementing the likes of a post. We get the snapshot of the post by transaction and update the likes by the previous likes plus one. Now in this case if someone also likes the post at that time, your transaction will check if the document has updated or not before write operation, if updated the transaction will run again, get the new likes count.

2. Example 2 — Money transfer between accounts.

await _firestore.runTransaction((transaction)async{   CollectionReference accountsRef = _firestore
.collection('accounts');
DocumentReference acc1Ref = accountsRef.document('account1');
DocumentReference acc2Ref = accountsRef.document('account2');
DocumentSnapshot acc1snap = await transaction.get(acc1Ref);
DocumentSnapshot acc2snap = await transaction.get(acc2Ref);
await transaction.update(acc1Ref,{
'amount' : acc1snap.data['balance'] - 200
});
await transaction.update(acc2Ref,{
'amount' : acc2snap.data['balance'] + 200
});
});

Limit your Transaction Time

You can limit the time of a transaction. If the transaction does not complete within the given time it will fail.

await _firestore.runTransaction((transaction)async{
// your transaction
},timeout: Duration(seconds: 10));

Handel app state outside transaction

As transaction function may run multiple times for concurrency control, we should handle app state related work outside transaction.

try{
Map<String,dynamic> success = await _firestore
.runTransaction((transaction)async{
// your transaction
...
return {
// any data related to transaction success
};

},timeout: Duration(seconds: 10));

// handle your app state here,outside the transaction handler
print(success);

}catch(e){
print(e);
}

Batched Writes

If we do not need to read any documents in your operation set, we can execute multiple write operations as a single batch that contains any combination of set(), update(), or delete() operations. A batch of writes completes atomically and can write to multiple documents. Batched Writes are completely atomic and unlike Transaction they don’t depend on document modification in which they are writing to. Batched writes works fine even when the device is offline.

You can get a WriteBatch by calling .batch() in CloudFirestore instance. Then you can perform set, update and delete operation on any document. All the operations will be atomic.

WriteBatch writeBatch = _firestore.batch();// set data
writeBatch.setData(documentReference, {
'name' : 'Batch name 1',
'age' : 22,
});
// update data
writeBatch.updateData(documentReference,{
'name' : 'Updated name'
});
// delete document
writeBatch.delete(documentReference);

// unless commit is called, nothing happens.
writeBatch.commit()

Unless you call .commit(), the batched writes will not execute.

Thanks for reading . Follow me for more Firebase with Flutter article.

Next article will be on Firebase Cloud Firestore Queries.

--

--