The engineers on the Firebase SDK teams put great effort into making their APIs consistent and easy to use. One thing you may have noticed with the mobile client SDKs is that all of the API calls that deal with reading and writing data are fully asynchronous. This means that the call always returns immediately, without blocking the code to wait for a result. The results come some time later, whenever they’re ready.

In JavaScript, these asynchronous methods usually return a Promise. For Android, the method will return a Task object, which is very similar to a promise. And for iOS, you’ll pass a completion block (“closure” in Swift) that’ll be invoked later to handle the result.

The thing about asynchronous programming is that it’s not really intuitive at first. If you want to fetch some data, it’s natural to want to write code that’s structured something like this:

try {

result = database.get("the_thing_i_want")

// handle the results here

}

catch (error) {

// handle any errors here

}

This is a synchronous call, and it’s short and easy to understand. The result of get() is being returned directly from the function, and the calling code is waiting for it to complete. But this is precisely the problem. You don’t want your code to stop to wait for something that could take a long time.

All those times when you’ve had a terrible mobile connection and waited forever for it to load.

You can think about Firebase asynchronous APIs the same way you think about using an oven. If you want to bake a cake, you typically don’t stand at the oven waiting for it to finish. Nobody interacts with their oven like a synchronous API! Instead, you set a timer on the oven, and it tells you asynchronously when the work is done, while you go and do other things. So much more efficient with your time, right?

So, what does an asynchronous call look like with Firebase? Let’s use the Cloud Firestore API, for example. Fetching a document requires a network connection or a local disk cache, and there are no guarantees about the speed of either of them. So, the document fetch API must be called like this:

Android/Java:

Task<DocumentSnapshot> task =

FirebaseFirestore.getInstance().document("users/pat").get();

task.addOnSuccessListener(new OnSuccessListener() {

public void onSuccess(DocumentSnapshot snapshot) {

// handle the document snapshot here

}

});

task.addOnFailureListener(new OnFailureListener() {

public void onFailure(Exception e) {

// handle any errors here

}

});

web/JavaScript:

var promise = firebase.firestore().doc("users/pat").get();

promise.then(snapshot => {

// handle the document snapshot here

})

.catch(error => {

// handle any errors here

});

iOS/Swift:

Firestore.firestore().document("users/pat")

.getDocument() { (snapshot, err) in

if let snapshot = snapshot {

// handle the document snapshot here

}

else {

// handle any errors here

}

}

Well, that’s a bit of extra typing compared to the simpler synchronous prototype!

So, why make the consumer of an API go through the extra trouble of using an asynchronous API? Can’t we just have simpler synchronous APIs instead? Well, Firebase could provide synchronous APIs for mobile apps, but then we’d inherit one of two problems that are both even worse than writing this extra code:

Calling a synchronous function on your app’s main thread could freeze the app indefinitely, which is a terrible user experience. On Android, it could also soft-crash with an Application Not Responding (ANR) dialog. To avoid blocking the main thread with a synchronous method, we’d have to manage our own threads to call these APIs properly. This is even more code, and can be difficult to get right. Even expert engineers have challenges with correct threading behavior.

If problem #1 is unclear, I’ll take a moment to say more about that in the next section.

It’s worth noting that there are also a couple benefits to using an asynchronous API:

The API provider has the ability to optimize the threading behavior in a way that you might not be able to duplicate yourself. (You should trust the API provider to understand the performance characteristics of their work!) It opens the door for future enhancements, such as the cancellation of ongoing work.

If you ask me, I’d rather have an asynchronous API that manages all the required threading behind the scenes. With this, my app becomes a lot easier to write.

Why is blocking the main thread so bad?

Application runtime environments that have a user interface typically drive all interactions on that UI through a single thread called the “main thread”. A vast majority of application code you write will execute on that thread. The thread is controlled by an event loop, which basically churns through a queue of work items forever, until the app’s process dies. These items of work include handling events, rendering to screen, animations, and anything that has to do with the UI. It has to cycle through these items of work fast enough so that it can also render the screen at a smooth 60 frames per second. That means an item of work has, at most, 16ms to complete. Any more than that, and the main thread has skip the rendering of the UI for your app until that work is finally done. Skipping frames is not good!

So, if one of those bits of work in the event loop takes too long, it’ll get stuck in the queue, and the app will appear “janky” or even completely stuck, even if it’s still doing something! It’s critical to make sure your work on the main thread never blocks, no matter what the reason, so your UI is always smooth and responsive. Don’t be like this: