18.
Managing Data with Cloud Firestore
Written by Harun Wangereka
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 Firestore as the backend. The functionality of WhatsUp will remain the same.
In the process, you’ll learn how to add data to 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 Chapter 12: “Firebase Overview” and Chapter 13: “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 Firestore Database from the menu on the left.
Tap Create database in the Cloud Firestore section at the top of the page. Next, you’ll see the dialog with security rules. Firestore offers two security modes for you:
- Test mode will allow anyone to have read and write access to the database for a period of 30 days. You can adjust the date to the one you’d want.
- Production mode will deny all reads and writes to the database.
Choose the test mode for now. You’ll learn more about the security of Cloud Firestore in “Chapter 20: Securing Data in Cloud Firestore”.
Tap Next to go to the next step of creating your database. You’ll see the screen for setting your Cloud Firestore location.
Firestore allows you to set the location where it stores data. In the drop-down widget, choose either a multi-region or a single region from the available list of regions.
Tap Enable. You’ll see your database on the screen after Firebase finishes creating it.
Configuring your Application
You’ve 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. To use these projects, you need to configure them first by creating a project in the Firebase and by adding the google-services.json configuration file to the project. Take a look at Chapter 12: “Firebase Overview” and Chapter 13: “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 build.gradle and add the following dependency at the end of the dependencies block:
implementation 'com.google.firebase:firebase-firestore:23.0.2'
After adding this dependency, sync a Gradle. Click Sync Now in the notification shown after changing the file.
The second thing you need to do is to initialize the Firestore instance. Navigate to CloudFirestoreManager.kt inside firebase/firestore package. Add a database field as a top-level variable below the commentsValues variable:
private val database = FirebaseFirestore.getInstance()
Resolve the import errors when the IDE prompts you. Here, you’re getting a Firestore instance fromFirebaseFirestore. You’ll use it for all the communication with Firestore Database from the application.
With this setup, you’re now ready to start writing and reading data from Firestore.
Writing Data
To learn how to use Firestore, you’ll refactor the existing Whatsup app. The first part ready for refactoring is writing. Instead of writing data to Realtime Database, you’ll write the data to Cloud Firestore.
In CloudFirestoreManager.kt, replace TODO inside addPost() with the following:
// 1
val documentReference = database.collection(POSTS_COLLECTION).document()
// 2
val post = HashMap<String, Any>()
// 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() }
In the code above:
-
First, you call
collection()passing in the posts collection path stored in thePOSTS_COLLECTIONconstant. As a result, the method returns a reference to the collection at the specified path in the database. Then, you use that collection reference to get the document reference by callingdocument(). This method 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. You represent this data as a map where
Stringis 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.
-
The last part is saving the data. First, you call
set()on the document reference. It will replace the data in the document if it already exists or it will create it if it doesn’t. You pass in the map that contains the data that you want to write to that document.set()is an asynchronous operation and, once ready, it returns aTaskobject. Since the operation is asynchronous, you attach two listeners. One isOnSuccessListener()is called if theTaskcompletes successfully, andOnFailureListener()is called if the operation fails. You pass in the actions that the system invokes depending on the result of the operation.
Open AddPostActivity.kt, find addPostIfNotEmpty() and replace TODO inside the if block with:
cloudFirestoreManager.addPost(postMessage, ::onPostAddSuccess, ::onPostAddFailed)
Here, you’re calling previously added code in addPost() on the cloudFirestoreManager instance. You also pass the message you want to add, and success and failure methods.
Build and run. Sign in if necessary. Tap the floating action button at the bottom right corner. Add some text and tap Post.
You’ll get a Toast message that says that the post is saved successfully. The home screen displays nothing.
That is because you haven’t implemented the logic for reading yet.
Open the database in the console. You’ll 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! From the screenshot, you have a posts collection that has a single document. The document has author, content, id and timestamp fields. Most likely, yours will have different content.
For cases where you only need to write small chunks of data, this is the approach you’ll be using. In the next section, you’ll be looking at using transactions to write a bunch of data at the same time.
Transactions
Firestore supports another way of writing data, transactions. Firestore has transactions for 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 in execution in a transaction fails, none of them will be applied because that could potentially leave the database in an inconsistent and undesired state. Firestore applies either all 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 similar to what you did when adding new data. Open CloudFirestoreManager.kt , look for updatePostContent() and replace TODO inside it with 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() }
Here’s what you’re doing:
- You create a map of data that you want to save to the database. This is similar to how you create new data.
- You 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. From there you get the reference to the document that you’re updating by specifying a key. You then call
update()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 PostDetailsActivity.kt, find initializeClickListener() and replace TODO in the updatePostButton on-click listener with:
cloudFirestoreManager.updatePostContent(
post.id,
postDetailsBinding.postText.text.toString().trim(),
::onPostSuccessfullyUpdated,
::onPostUpdateFailed
)
In this code, you’re calling your updatePostContent() and passing the id of the post you want to update. You’re also passing the new message that you’ve added and finally, you pass your success and failure action methods. When you tap UPDATE POST it will update the database with the updated content.
Now you can update posts in the database as well. You’ll be able to test this functionality when you put in place reading logic in “Chapter 19: Reading Data from Cloud Firestore”.
Deleting Data
One last bit of functionality that you’ll add in this chapter is post deleting. Open CloudFirestoreManager.kt, replace TODO inside deletePost() 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 collection() 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() on a document reference which deletes the document referred to by the reference. This method deletes data asynchronously, so you also attach the listeners that get triggered when the deleting succeeds or fails.
Now, open PostDetailsActivity.kt and inside initializeClickListener() replace TODO of the deletePostButton on-click listener with the following:
cloudFirestoreManager.deletePost(post.id, ::onPostSuccessfullyDeleted, ::onPostDeleteFailed)
Here, you make a call to your deletePost() which you’ve updated. You pass the id of the post you want to delete. Now, when you open post details you can delete a post by tapping DELETE. You’ll also test this functionality when you implement reading logic in “Chapter 19: Reading 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 wouldn’t be deleted. When you delete a document only that document is deleted; Firestore doesn’t delete the documents inside the subcollections.
Firebase Console
You can do all operations 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 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 a menu icon in every column title cell. Click on one of them. Then, 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
- You can create a Firestore database in the Firebase console.
- You need to add a Firestore client library for Android to the project to use Firestore APIs.
- You need to initialize a Firestore instance to communicate with the database.
- You call
collection()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 want to save to the database.
- You call
set()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 that you use in cases when you want to write a bunch of data to the database at once.
- You call
update()on a document reference to update fields in the document. - You call
delete()on a document reference which deletes the document referred to by the reference. - Adding, updating and deleting operations are asynchronous.
- You can use the console to manage data in the database.
Where to go from here?
You’ve 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 19: Reading Data from Cloud Firestore” you’ll add the ability to listen for data updates in real-time. You’ll also learn how to read data from the database and do other data operations.