Update and Customize Queries on Your Cloud Firestore Data for Actions on Google
In the previous post, we learned about how to enable Firestore in the Firebase console and integrate this with Actions on Google in order to store data scalably for multiple Google Assistant users across multiple devices. In this post, we will dive deeper into the Cloud Firestore by using different query methods. We will be able to query multiple documents and tell users the top X number of planets that they have asked about most frequently. To increase the count number each time a planet is asked about, we’ll update the count property of each document.
Here’s the dialog that you’ll be building after reading this blog post:
Update a document
We’re only going to discuss the code changes necessary to support the top three features. If you’re curious about the existing code, you can check out this sample here. Before we can get the top three planets, we need to implement logic to keep track of every time a planet is being asked about. We want to do this is in the ask_planet_intent.
Step 1
First, we need to extract the definition, word, and a record of the number of inquiries from our document on line 19. What’s important to note here is that now we’re keeping track of the number of times the planet was inquired about, which didn’t exist before in the prior code.
Step 2
Let’s go ahead and update the document in the database by calling the update method. You may be wondering why we need to do {count: count +1}. There are two things you should know here:
We pass in an object into the update method, which tells Firestore the fields to update. In this case, we just want to update the count field. Firestore does not support incrementing values (i.e., count+=1).
Now, if you ask about Mars three times, the count of the Mars document will increment by three. You can verify this by going to the Firebase console.
Tip: For simplicity, we did not use a transaction when updating the data count. As a best practice and to maintain data integrity, we recommend reading more about transactions here and using them in your application.
After adding the above code to increment the count, you can put that count to use by creating a new intent called “ask_top_three_intent”. In this new intent, we will explore different query methods such as .where(), .orderBy(), and .limit().
Create ask_top_three_intent
If you are not familiar with how to create a new intent, check out part 1 of this blog series. If you know how to create an intent, go ahead and create one just like the image below. Remember to enable webhook calls for this intent on the bottom of the page.
Tip: You can set a default value for the number parameter by doing the following steps:
- Click the three dots circled in red.
2. Select Default value.
3. Type “3” in the blank.
Query from multiple documents
Now that you’ve created the ask_top_three_intent, let’s examine the code.
As a frame of reference, the documents have the following structure in Firestore:
The documents stored in our database have two new fields: count and type. Our database contains entries that describe planets, stars (the sun), and the moon. The type is added to help queries find documents that match the request being made, in this case, we want only planets. Since these other entries are not planets, we need a way to filter them out for the ask_top_three_intent. That’s where the new field type comes into play.
If you look closely at line 33, you’ll notice we call the method where() and give it filter criteria. This tells the database to only return documents that are classified as planets. Although this gives us back all the planets, we still need to rank them in order of popularity and that’s why we use orderBy.
At this point, we’ve constructed a query that selects only planets and ranked them in order of popularity, but we’re not limiting the number of documents returned. In order to cap off the number of documents returned, we have to call the limit() method and pass in the maximum number of documents we want back.
Unlike retrieving a single document, when querying for multiple documents, we get back a QuerySnapshot object, which has a property called docs as shown on line 38.
As you may have noticed, the Query API has a lot of ways to let you customize your results.
At this point, if we tried to run the code above, it will not work yet. Instead, it would yield a very helpful error message shown in the next section. That’s because we still have one more thing to do: create a composite index in order to satisfy the query.
What is a composite index?
Firestore automatically creates indexes for each individual field in a collection of documents. This ensures that queries using that single field are scalable. However, the query we need to perform here involves multiple fields. Firestore is unable to perform the query above, but you can tell it to create an index that involves all the fields we need to use here. This type of index is called a “composite index”, and the link in the error message will actually take you to a screen in the Firebase console to create the index that satisfies the query. To learn more about composite indexes, see this overview.
If you run the above code, you would have heard “sorry, I can’t get the top 3 words for you” in the simulator. Okay, what went wrong and how do we find out? The first thing you should always do when you encounter an error is to look at the Firebase logs. Below is a screenshot of the error message I encountered:
Take notice of the error message circled in red. It provides a link to a page that will help you create the missing index. Pretty slick!
Once you go to the URL, you will see a pop-up dialog shown below. Use this to create a “composite index” on two fields: type and count. The reason why we see the pop-up dialog is because we have a where clause on type == ‘planet’ and we order by count descending!
You can find more information about why composite indexes are needed by watching How do queries work in Cloud Firestore by the awesome Todd Kerpelman
It takes a few minutes to build the index and you will see “building” while it’s being created.
Once this is completed, go back to the simulator. This time, you will hear back the top three planets, which might be something like:
Saturn
Mars
Earth
Now, if we test the Action by asking for the top four planets, this is how the dialog will look:
Now you’ve learned how easy it is to query Firestore! Going forward, you can leverage the Firestore database for new Actions you create. Using Firestore is particularly relevant when you want to build something like a leaderboard for a voice game or any Action that requires storing data for multiple users across different devices.
You can find all the code for the index.js file here as a Gist. If you’re interested in learning some more advanced conversational features, like Helper Intents and SSML, please watch the Systers Workshop 2 recording here.
Additionally, you can find a list of resources here, including the Actions on Google Codelabs Level 1 & 2. If you’re new to Actions on Google, I recommend watching the Systers Workshop 1 recording here. Share what you have built with us by tweeting to #AoGDevs and follow @ActionsOnGoogle!