17.
Managing Data with Cloud Firestore
Written by Dean Djermanović
In the previous chapter, you learned the basics of Cloud Firestore. You learned what Firestore is, how it differs from Realtime database, and how it structures its data. In this chapter, you’ll integrate Firestore into the app. You’ll refactor the current WhatsUp app to use the Firestore as the backend. All of the functionality of WhatsUp app will remain the same. In the process, you’ll learn how to add data to the Firestore, how to update and delete data, and how to use Firebase console to manage Firestore data.
Getting started
You need to set up Firestore before you can start using it. If you followed along with Realtime database chapters, you have already created the project in the Firebase console. If you didn’t, go back to the “Chapter 11: Firebase overview” and “Chapter 12: Introduction to Firebase Realtime Database” to see how to create the project in the console and how to connect your app with Firebase.
Creating the database
Open your WhatsUp project in the Firebase console. Select Database from the menu on the left. You’ll see this screen:
Click the Create database button in the Cloud Firestore section at the top of the page. Security rules dialog will open:
Two security modes are offered to you. Test mode will allow anyone to have read and write access to the database while the locked mode will deny all reads and writes to the database. Choose the test mode for now. You’ll learn more about the security of the Cloud Firestore in the “Chapter 19: Securing data in Cloud Firestore”.
Click the enable button.
Your database will open after Firebase finishes creating it. This what you’ll see:
Configuring application
You have created the database in the console. Now you need to configure your app so it can communicate with the database.
NOTE: This chapter provides starter and final projects that you can use to follow along. In order to use those projects, you need to configure them first by creating a project in the Firebase and by adding google-services.json configuration file to the project. Take a look at “Chapter 11: Firebase overview” and “Chapter 12: Introduction to Firebase Realtime database” to see how to do that.
Open the starter project for this chapter.
First, you need to add a Firestore client library for Android to the project. Open the apps level build.gradle file and add the following dependency:
implementation 'com.google.firebase:firebase-firestore:20.1.0'
The second thing you need to do is to initialize the Firestore instance. Open the CloudFirestoreManager class and add database field:
private val database = FirebaseFirestore.getInstance()
FirebaseFirestore represents the Firestore instance and it is used for all the communication with the Firestore database from the application.
Writing data
The first part of the app that you’ll refactor to use Firestore is writing. Instead of writing data to Realtime Database you’ll write the data to the Cloud Firestore.
In the CloudFirestoreManager class replace the TODO inside the addPost function with the following:
val documentReference = database.collection(POSTS_COLLECTION).document() //1
val post = HashMap<String, Any>() //2
//3
post[AUTHOR_KEY] = authenticationManager.getCurrentUser()
post[CONTENT_KEY] = content
post[TIMESTAMP_KEY] = getCurrentTime()
post[ID_KEY] = documentReference.id
//4
documentReference
.set(post)
.addOnSuccessListener { onSuccessAction() }
.addOnFailureListener { onFailureAction() }
- First, you call the
collectionmethod passing in the posts collection path. Thecollectionmethod returns a reference to the collection at the specified path in the database. You use that collection reference to get the document reference by calling thedocumentmethod, which points to the new document within that collection with an auto-generated ID. You’ll use that document reference to get the document ID that you’ll store in the database with the post object. - Here, you create data that you want to save to the document. This data is represented as a map of
String, Anytype whereStringis the type of the key andAnyis the type of the value. - You need to populate the map with the values that you want to write to the database. You add author, post content, timestamp and the ID of the document to the map.
- This where you actually save the data. First, you call the
setmethod on the document reference that will replace the data in the document if it already exists or it will create it if it doesn’t. You pass in thepostmap that contains the data that you want to write to that document. Thesetmethod returns aTaskwhich represent an asynchronous operation. Since the operation is asynchronous, you attach two listeners,OnSuccessListenerthat is called if theTaskcompletes successfully, andOnFailureListenerthat is called if theTaskfails. You pass in the actions that you receive as the function parameters.
Open the AddPostActivity class and replace the TODO inside the addPostIfNotEmpty function with the call to the cloudFirestoreManager.addPost:
cloudFirestoreManager.addPost(postMessage, ::onPostAddSuccess, ::onPostAddFailed)
Build and run your app. Click on the floating action button at the bottom right corner. Add some text and click the Post button:
You should get a toast message that says that the post save is successful. Nothing is displayed on the home screen. That is because you haven’t implemented the logic for reading yet.
Open the database in the console. You should see your post there. If you don’t see it try to refresh the page:
Congratulations, you saved your first post to the Firestore database!
Transactions
Firestore supports another way of writing data, transactions. Transactions are used in cases when you want to write a bunch of data to the database at once. While the transaction is executing, the user won’t be able to read that partially changed state. If one of the operations that are executed in a transaction fails, none of them will be applied because that could potentially leave the database in an inconsistent and undesired state. Either all are applied or none. One transaction operation can write to 500 documents maximally.
There is another type of transaction called batched write. Batched write allows you to perform a bunch of writes all at once, in other words, in a batch. It works in a way that you specify what you want to change and tell the SDK to change it. You don’t have to worry about what happens if the operation fails halfway through. None of the changes will be applied in that case. Also, another user won’t be able to change that same data that you are currently changing because those write operations are atomic which means that the operation is guaranteed to be isolated from other operations that may be happening at the same time. Batch operation is also much more efficient than doing many individual write operations. A good use case for batch write is when you want to change many related documents and your new value does not depend on the old value.
To learn more about transactions and batched writes check out the official documentation: https://firebase.google.com/docs/firestore/manage-data/transactions.
Updating data
Next, you’ll add an update feature to your app. You’ll see that it’s very similiar to what you did when adding new data. Open the CloudFirestoreManager class and replace the TODO inside the updatePostContentwith the following:
//1
val updatedPost = HashMap<String, Any>()
//2
updatedPost[CONTENT_KEY] = content
//3
database.collection(POSTS_COLLECTION)
.document(key)
.update(updatedPost)
.addOnSuccessListener { onSuccessAction() }
.addOnFailureListener { onFailureAction() }
- You start the same way as when adding a new post, by creating a map of data that you want to save to the database.
- When updating an existing document you only need to specify the data that you are updating. In this case, this is only the post content.
- Finally, you get the reference to the posts collection and from there you get the reference to the document that you’re updating by specifying a key. Then you call
updatemethod on a document reference which updates fields in the document. The listener logic stays the same as in the case of writing new data.
Open the PostDetailsActivity class and inside the initializeClickListener function replace the TODO in the on-click listener, so the update post button will update the database with the update content when clicked:
cloudFirestoreManager.updatePostContent(
post.id,
postText.text.toString().trim(),
::onPostSuccessfullyUpdated,
::onPostUpdateFailed
)
Now you can update posts in the database as well. You’ll be able to test this functionality when you implement reading logic in the “Chapter 18: Read data from Cloud Firestore”.
Deleting data
One last bit of functionality that you’ll add in this chapter is post deleting. Open the CloudFirestoreManager class and replace the TODO inside deletePost function with the following:
database.collection(POSTS_COLLECTION)
.document(key)
.delete()
.addOnSuccessListener { onSuccessAction() }
.addOnFailureListener { onFailureAction() }
To delete a post you first get a reference to the collection of the post by calling the collection method on a database instance and passing in the path to the posts collection. Then you get a reference to the specific post document that you want to delete by passing in the key which is the ID of the post. Finally, you call delete method on a document reference which deletes the document referred to by the reference. The delete method deletes data asynchronously, so you also attach the listeners that get triggered when the deleting succeeds or fails.
Now, open the PostDetailsActivity class and inside the initializeClickListener function replace the TODO inside the delete button click listener with the following:
cloudFirestoreManager.deletePost(post.id, ::onPostSuccessfullyDeleted, ::onPostDeleteFailed)
Now when you open post details you can delete post by tapping the delete button. You’ll also test this functionality when you implement reading logic in the “Chapter 18: Read data from Cloud Firestore”.
One important thing to mention here is when you delete a post document, if that document contained a subcollection, that subcollection would not have been deleted. When you delete a document only that document is deleted; Firestore does not delete the documents inside the subcollections.
Firebase console
You can do all of these operations that you implemented in this chapter, like adding data, updating and deleting, manually in the Firebase console.
Open firebase console in the browser and navigate to the Firestore database.
From here, you can view your database data. You can click on any collection to see the documents within that collection, and you can click on any document to see the details of that document.
You’ll also notice these menu icons. Click on the in the last column for example. There you’ll see the options to delete a document or a specific field of the document:
You can also filter the documents inside the collection by clicking the filter button:
Visit the official documentation https://firebase.google.com/docs/firestore/using-console to explore more possibilities of the console.
Key points
- Firestore database is created in the Firebase console.
- You need to add a Firestore client library for Android to the project in order to use Firestore APIs.
- You need to initialize a Firestore instance in order to communicate with the database.
- You call the
collectionmethod passing in the collection path to get a reference to the collection at the specified path in the database. - You need to create and populate the map of data that you wan’t to save to the database.
- You call the
setmethod on the document reference that will replace the data in the document if it already exists or it will create it if it doesn’t to save the data to the database. You pass in the map that contains the data that you want to write to that document. - Firebase supports transactions which are used in cases when you want to write a bunch of data to the database at once.
- You call the
updatemethod on a document reference to update fields in the document. - You call the
deletemethod on a document reference which deletes the document referred to by the reference. - Adding, updating and deleting operations are asynchronous.
- You can use the Firebase console to manage data in the Firestore database.
Where to go from here?
You implemented adding, updating, and deleting functionalities in this chapter and you saw how you can use the Firebase console to achieve that. You can visit the official documentation https://firebase.google.com/docs/firestore/manage-data/add-data to learn more about these operations.
You still don’t have a way to test these functionalities. In “Chapter 18: Reading data from Cloud Firestore” you’ll add the ability to listen for data updates in real time. You’ll learn how to read data from the database and to do other data manipulation operations.