A Practical Approach to Cloud Functions for Firebase: Direct Calls
This is the second article in a series going over how to use Cloud Functions for Firebase by building usable examples. If you’re not familiar with Cloud Functions and how to set them up, or you simply want to read this series from the beginning, then you can find the previous article here:
In the first article we deployed a “Hello World” example to Cloud Functions, which we could hit with a standard GET request to receive a response. In this article we will take a closer look at how developers can access Cloud Functions in various situations. This article will cover how you can send data to a Cloud Function directly, and how you can use one of Firebase’s pre-built SDKs to call a Cloud Function. For both of these scenarios I will frame the solution around a practical example that a developer might use in one of their projects.
Let’s go ahead and get started.
Sending Data to a Cloud Function
We will begin by learning about is how to send data to a Cloud Function through a standard POST request that includes additional information. To make this interesting, let’s put something together that will be a bit more useful than our last “Hello World” example: a Cloud Function that accepts a latitude and longitude, and then uses Google’s reverse geocoding API to give the caller some information about a location.
The first thing we’ll need to do is get an API key and enable the Geocoding API. We can do this by going to the Google Cloud Console and selecting our project, then searching for Geocoding API
. Once it’s found, go ahead and click on the ENABLE
button to turn it on.
With that done, let’s return to the Google Cloud Platform console, go down to APIs & Services in the side panel, and select Credentials.
From there you can click on the CREATE CREDENTIALS button at the top and select API key.
After clicking on that option, a dialog window should come up with your API key (it should start with the letters AIza
). Save it somewhere, as we’ll need it soon. Now that that’s done, let’s get back to our Functions code :)
We’ll start off by adding the additional Node library that we’ll need for our networking requests: axios.
We’ll then add a require line for this library at the top of our index.js file.
Let’s also add a new Function with a similar structure to helloWorld, but we’ll call it reverseGeocode
. This Function will start off by extracting the latitude and longitude values from the request as lat
and lng
From there we’ll add our code within this Function for making a request to the reverse geocoder API. After receiving the response from the API, this Function will return the formatted address for the provided latitude and longitude.
You’ll notice there’s one part of the above code that says YOUR_API_KEY_HERE
. Be sure to replace that with your own API key from earlier in this article.
Let’s go ahead and deploy this Function. Now that it’s able to read data sent to it and do something useful, let’s test it out. We can do this with a simple cURL command that passes our data in JSON format.
This request sends in the latitude and longitude for Denver, Colorado, and should receive the address for a government building in the middle of the city.
Firebase SDKs
Now that we have a Cloud Function written that can respond to a request with an address, let’s take a look at how you can use one of the Firebase SDKs to wrap the call and streamline our process a bit. For this section we’ll create an Android app that has a Google Map, and when the user long presses on a point on the map, it will call our Cloud Function to retrieve the nearest address and display it on the device. We can start by creating a new Android project in Android Studio and selecting the Google Maps Activity template.
After clicking next, we can fill in our app information, such as the package name, and select the programming language that we want to use. For this sample, let’s make sure we’re using Kotlin. After the IDE has finished loading, we can connect our Android app to our Firebase backend. Rather than repeating those steps here, we can refer back to the official Firebase setup documentation on how to do this.
We will also need to enable the Google Maps SDK for Android in our Google Cloud Platform console. Luckily this step is similar to how we enabled the Geocoding API, and we can even reuse the same API key that we generated in the previous section (though in practice you would create separate keys for your backend and your mobile app in case you needed to disable or change one).
Once that’s enabled, we can add our API key to the Android project in the google_maps_api.xml file.
At this point we should be able to run our Android app and see a Google Map with a marker and high level view over Sydney, New South Wales, Australia.
Honestly, this view isn’t the most useful for what we want to do. Let’s go ahead and modify the onMapReady
method in our Android app to zoom in a bit more on Sydney, and enable our zoom controls. We can also remove the marker since it’s not necessary for what we’re doing.
If we run our app now, it should look like this, albeit a little off-center from the city due to rounded coordinates, but that’s OK. Feel free to update the coordinates and play with the map settings to get a feel for it :)
Now let’s actually call our Function when the user long presses on the map. We can do this by adding a new onMapLongClickListener
to our map, converting the LatLng
point to a data HashMap
, and then calling our HttpsCallable
from within the listener.
Now if we run this, we’ll see our map again, but we can also long press on the map to trigger our Function and show the results in a Toast
message. However, if we do this now, we should see something like this in the log messages:
Wait, what? If we check our Functions logs, we’ll see this line:
Based on that, it looks like our latitude and longitude data isn’t where our Function expects it (we can come to this conclusion because of the two undefined values in the log). It turns out that the Firebase SDKs wrap our data in a data object, so we’ll need to adjust our Function to account for that. Luckily, we only need to change two lines to accept this new data
object.
Now if we deploy the Function and run our app again…
Oh great, another error. This one is at least a lot more straightforward. Right now we’re just returning the data as a regular string, but it looks like the SDK requires data to be returned in a JSON format. Let’s go ahead and update our Function so that instead of using response.send()
, it’ll return a 200 code with a new JSON object. Additionally, just like the SDK required sending data wrapped in a data object, it expects this JSON response to also use a data object. We’ll go ahead and add that now instead of going through another failing step.
If we run our app one last time, we should be able to long click in various places and get addresses showing up on our screen. For example, if we click near the Taronga Zoo in Sydney, we should get their address on Bradley Head Rd.
By this point you’ve probably noticed that we’re displaying these addresses in JSON format. While it doesn’t look great in this situation, you can expand on this demo by parsing the response data from a Cloud Function using GSON or any other technique/tool to make it fit your app’s use-cases.
onCall Functions
Great, so our relatively simple Cloud Functions should be ready to go (take ‘relatively simple’ with a grain of salt — if these articles are your first time using Cloud Functions, they can be confusing, and that’s OK! Practice makes perfect :)), but there’s a few downsides. One of these is that our Function is currently open to anyone with the URL. We can get around this is by changing our onRequest
Cloud Functions to onCall
. While onRequest
and onCall
Functions are similar, there are a couple important differences to be aware of. For starters, onCall
Functions can only be accessed by a Firebase SDK (specifically JavaScript, Android, and iOS at the time of this writing), and they’re able to automatically attach state data, such as a user’s auth credentials, or a Firebase Messaging token. These Functions also automatically deserialize requests and validate authentication tokens, which paves the way for more security for your apps.
Firebase
onCall
functions pave the way for more security for your apps
Given all of this, onCall
Functions are the preferred and recommended way for you to directly call your Firebase backend code.
Rather than rewriting our previous Cloud Function, let’s go ahead and create a new one called reverseGeocode2
. This will have a slightly different signature that accepts a set of data, and a Firebase specific CallableContext
object. We will also want to ensure that our new function is asynchronous by using the async
keyword.
The body of this Function won’t change much, so let’s go ahead and add that now, then we’ll address the differences.
You can see that we’re extracting the latitude and longitude directly from the data object, instead of pulling it from a more general request
object. The property names used here, lat
and lng
, also match the property names that are used in our Android app. We’ll then make a network request using axios
, but we’ll add the return
and await
keywords before the get
request. This will wait for the network request to finish, and then will send a response back to our calling app. One thing worth noting here is that it’s a better practice to use Promises, but we’ll save that for a later discussion. The final thing to note here is that we’ve added a line to throw a new HttpsError
if something goes wrong. This will let our calling app know that something has broken, rather than waiting indefinitely for a response.
After those minor changes, we can call this new Cloud Function from our Android app by updating the callable name, but everything else will stay the same.
If you run your app now, everything should act and look exactly like it did before (a little anti-climatic, unfortunately), but now we’re using the recommended Cloud Functions access type.
Conclusion
In this article we’ve taken a look at two ways we can pass data to Cloud Functions for Firebase: directly through a POST request, and through a pre-built Firebase SDK. We also learned about calling an external API from Cloud Functions through the use of Google’s reverse geocoding API.
For the next article in this series we will take a look at how you can trigger a Cloud Function by changing data in Cloud Firestore, plus some of the other things you can do with Firestore from a Cloud Function, such as reading, writing, and querying data. Following that, we’ll use what we’ve learned about Firestore to create examples around scheduling Cloud Functions on a timer, as well as using Google’s Pub/Sub messaging bus triggers. See you there!