If you've ever built an app with Realtime Database you know that it's fast. When you combine the low-latency websocket connection with the local caching capabilities of the SDK, changes can feel pretty much instantaneous.
But have you ever wondered how fast your database operations are for your users in the real world? As a good app developer you need to collect real-world performance data to make sure that the experience of using your app in the real world matches your expectations! Many people in the tech industry call these field measurements Real User Monitoring (RUM) and they're considered the gold standard for measuring app performance and user experience. Firebase Performance Monitoring is a free and cross-platform service to help you collect and analyze RUM data for your app or website.
Firebase Performance Monitoring automatically measures common metrics like time to first paint and HTTP request performance. Because Realtime Database uses a long-running WebSocket connection rather than separate HTTP requests we'll need to use Custom Traces to monitor the performance of our database operations.
For this post we built a Firebase-powered implementation of the standard TodoMVC app in React using the ReactFire library:
Each time we add, update, or remove an item in our to-do list we're making a change in Realtime Database directly. For example here's the code to add a new todo item:
function App() { // Get an instance of Firebase Realtime Database using the 'reactfire' library const db = useDatabase(); // Load all the 'todos' from the database const todosRef = db.ref("todos"); const list = useDatabaseList(todosRef); // ... // Add a new todo to the database const handleAddTodo = (text) => { todosRef.push({ text, completed: false, }); }; // ... }
This operation appears to be instantaneous because the Realtime Database SDK immediately adds the new todo to the local listener while it waits for the backend to acknowledge, or reject, the write. But what if we want to find out how long it actually takes to commit the write on the server?
Let's add some code to measure how long this really takes. We'll create a new function called tracePromise to help us log a custom trace for any action which returns a Promise and then we'll add a simple custom trace called add-todo.
tracePromise
Promise
add-todo
function tracePromise(trace, promise) { trace.start(); promise.then(() => trace.stop()).catch(() => trace.stop()); } function App() { // Get and instance of Performance Monitoring using the 'reactfire' library const perf = usePerformance(); // ... const handleAddTodo = (text) => { const p = todosRef.push({ text, completed: false, }); // Use the 'tracePromise' helper to see how long this takes const trace = perf.trace("add-todo"); tracePromise(trace, p); }; // ... }
If we deploy this code and head to the Firebase console we can see that the "add-todo" operation takes about 100ms in most cases, with 160ms being the worst case.
If we break this down by country we can see that the operation is much faster for users in the US than in other countries:
This makes sense! Most Realtime Database instances are located in the United States, which can have an impact on latency for users around the world. Geographic latency increases can depend on physical distances as well as the network topology between two points.
We don't often think about it when coding but data can only travel at the speed of light! For two points on opposite sides of the earth the speed of light alone adds 66ms of latency, and that's not including any of the actual network or processing latency along the way. This is why adding RUM to your app is so critical.
Well, the good news is that Realtime Database is now expanding to more regions around the world, beginning with the launch of our Belgium region in late 2020. A todo list app lends itself really well to sharding because each user's data is exclusively their own. So let's add a second Realtime Database instance to our app in the Belgium region, and assign each user to a random database instance to see what effect that has on our latency:
First we'll add a custom attribute to our Performance Monitoring traces so that we can filter the data by location later:
function getMyLocationCode() { // User's location could be stored in a URL param, cookie, localStorage, etc. // ... } function tracePromise(trace, promise) { // Add a custom attribute to the trace before starting it const location = getMyLocationCode(); trace.putAttribute("location", location); trace.start(); promise.then(() => trace.stop()).catch(() => trace.stop()); }
Now let's deploy these changes and wait for new user data to come in. After a few days we can see that our experiment worked! First we can see that our distribution now has two obvious peaks:
This is what we expected, because we're now randomly assigning users to one of two database instances. Depending on the one they get, it will either be close to them or far away.
If we look into the data more, we can see that our German users have a really fast connection to the Belgium instance! They're getting updates in as little as 22ms. That's a huge improvement over the 150ms+ they were getting when communicating with an instance in the US. While the local caching in the Firebase SDK will make the UI feel snappy either way, this will make a huge difference in the speed of collaborative or multi-device scenarios.
With this RUM data in hand we can be confident that adding a new database region can make our app faster. Next we'll need to find a way to detect the user location when they sign up and assign each account to the best region for them. For now we'll leave that as an exercise for the reader!
If you're ready to get started measuring performance in your own app, check out these links:
Version 7.1.0 of the Firebase Unity and C++ SDKs did a lot to improve Firebase Remote Config and better aligned it with the iOS and Android SDKs. In the process, however, there were some minor changes to the API that may require some action on your part.
First, in the Unity/C# SDK, the static methods you’ve been using have been removed from FirebaseRemoteConfig and moved into FirebaseRemoteConfigDeprecated. For technical reasons, we could not deprecate these functions and leave them in place. If you want a fast and easy way to update Firebase in your game, change FirebaseRemoteConfig to FirebaseRemoteConfigDeprecated. This will let your existing code work without any additional changes, but you won't be able to take advantage of the newer features.
FirebaseRemoteConfig
FirebaseRemoteConfigDeprecated
Also worth noting is that IsDeveloperMode no longer has any effect and can be omitted. You likely paired this with a change to MinimumFetchInterfaclInMilliseconds or by passing a TimeSpan into FetchAsync when debugging Remote Config. Shorter fetch intervals now function without IsDeveloperMode making RemoteConfig an even stronger tool in your LiveOps toolbox!
IsDeveloperMode
MinimumFetchInterfaclInMilliseconds
TimeSpan
FetchAsync
C++ developers can leave their code as is, although you will be warned about deprecation.
Developers wishing to future proof their code and take advantage of new features as they become available should switch to using a Remote Config instance rather than using static functions.
Unity/C#
var remoteConfig = FirebaseRemoteConfig.DefaultInstance;
C++
auto remoteConfig = ::firebase::remote_config::RemoteConfig::GetInstance(app);
SetDefaults is now asynchronous. This is probably a good thing for game developers hoping to move logic off of the main thread, but you do have to be a little more cautious when building your app logic.
remoteConfig.SetDefaultsAsync(new Dictionary<string, object> { {"starting_player_lives", 3}, {"theme", "dark"} }).ContinueWithOnMainThread(task => { // it's now safe to Fetch });
std::vector<remote_config::ConfigKeyValueVariant> defaultConfig = { {"starting_player_lives", 3}, {"theme", "dark"}, }; remoteConfig->SetDefaults(defaultConfig.data(), defaultConfig.size()) .OnCompletion([](const ::firebase::Future<void>&){ // it's now safe to Fetch });
It’s now possible to Fetch and Activate Remote Config changes in a single call.
remoteConfig.FetchAndActivateAsync().ContinueWithOnMainThread(task => { // handle completion });
remoteConfig->FetchAndActivate().OnCompletion([](const ::firebase::Future<bool>& future) { // handle completion });
And when you are ready to read your configuration, it’s now possible to retrieve all of the keys and values in a single language-specific native collection. No more getting a list of keys and looking up each value individually!
IDictionary<string, ConfigValue> values = remoteConfig.AllValues;
std::map<std::string, ::firebase::Variant> values = remoteConfig->GetAll();
Finally, if you would like to debug your Remote Config settings, you may want to reduce the interval between fetches.
var configSettings = new ConfigSettings(); if (Debug.isDebugBuild) { // refresh immediately when debugging configSettings.MinimumFetchInternalInMilliseconds = 0; } FirebaseRemoteConfig.DefaultInstance.SetConfigSettingsAsync(configSettings).ContinueWithOnMainThread(task => { // handle completion });
::firebase::remote_config::ConfigSettings settings; settings.minimum_fetch_interval_in_milliseconds = 0; // caution, only do this in debug mode! remoteConfig->SetConfigSettings(settings);
These changes are part of an ongoing effort to improve Remote Config, including some exciting new features you'll be hearing about in the future. Updating your games now will ensure that you can continue to take the advantage of the latest advances in Remote Config.
Even if your mobile app has been downloaded by millions of users worldwide, making it profitable in the long run is a tricky science. Most apps rely on a mix of ads and in-app purchases (IAP) to make money. The challenge is finding the right balance to maximize both revenue streams while ensuring an engaging experience for every user.
But determining your overall ad monetization strategy without negatively impacting in-app purchases isn’t a one-time effort. Between competition from other apps, changing user behavior, and evolving ad formats, you need to continually assess and experiment with your strategy to find an optimal mix. Doing so can keep users from dropping off from your app and even drive a 25% bump in total ads revenue, as mobile games publisher Pomelo Games discovered.
To tackle this challenge, you need a simple way to test and validate changes to your ads strategy in one place. And ideally, you'll want to gauge the impact of any changes on a small subset of users before rolling them out to your entire user base.
Linking AdMob, Firebase, and Google Analytics provides a streamlined solution to experiment with ads, and make smarter decisions based on app and ad performance insights. Here’s what each tool brings to the table:
Firebase Remote Config allows you to change the appearance and behavior of your app dynamically for any target audience — with no need to release an app update. For instance, you could design a new branding style for users in a certain country or region or change your app's color theme to match a seasonal promotion. You can also provide different app ad experiences, customized to different users in your app.
From there, you can use Firebase A/B Testing with Remote Config to run product and marketing experiments with variants of your app and analyze the results. This helps you make informed decisions about what’s working and whether your changes should be rolled out to more users.
Let’s say you launched a hit space shooter game and want to figure out what type of gameplay keeps users engaged — an easier version with fewer aliens to fight, or a more challenging version with fewer power-ups and a lot more monsters.
Using Remote Config, you build in the framework that enables you to add more challenging elements to your game without having to re-code and publish an entirely new version. Then, you can set up an A/B test that deploys these challenging elements to a small group of users, like your expert-level gaming audience. As part of setting up the A/B test, you choose primary and secondary metrics to optimize for, such as retention rate or total estimated revenue, and then you watch how the more challenging variant performs compared to the easier version.
And thanks to Firebase’s integration with Google Analytics, the actions that users are taking inside the app as a result of your experiments are factored into determining how well a variant performs.
Applied to your ads strategy, this testing framework using Firebase allows you to optimize for goals, like total ads revenue, while also tracking the impact on secondary metrics, like overall app monetization and user retention.
For instance, you might want to figure out if you can earn more ads revenue by adjusting the frequency capping without a drop in user retention. Using AdMob, you can create two ad units that vary in how often they’re shown to the user — say, one ad every 20 minutes versus one ad every five minutes. You can then use Remote Config and A/B testing to evaluate how these two different ad frequencies impact your ad revenue. You can also add secondary metrics to watch during the A/B test, like user retention and IAP revenue.
Or perhaps you’ve noticed a steady drop in ad clicks as people spend more time in your game and suspect it’s related to ad formats. For this case, you can experiment with the various ad formats in AdMob and A/B test these variants on a small number of users who spend more time in your game (an audience determined by Google Analytics). Then, when your A/B test determines which ad format increases ad clicks, you can roll out the new format more widely.
Whether you want to experiment with frequency capping to increase revenue or serve ads to a specific audience, linking AdMob with Firebase and Google Analytics leads to smarter, data-driven decisions. With insights about which users are most likely to spend money in your app, you can even fine-tune who sees an ad versus who’s encouraged to make a purchase instead.
Mobile game publishers around the world have successfully used these tools to optimize their ads and in-app purchases strategies without hindering the player experience. After hearing plenty of positive user feedback — including about the ads themselves — Four Thirty Three Inc. and Pomelo Games (mentioned earlier) were inspired to transform their entire business model, with Firebase tools at the core.
You can watch this session from Firebase Summit and learn more about features you can unlock by linking Firebase, Google Analytics, and AdMob.
Last year we announced our investment in making Firebase libraries more Kotlin-friendly with Firebase Kotlin extension (KTX) libraries. Since then we have seen increasing interest for Kotlin within the Firebase community. In this blog post, we’ll go over how developing with Kotlin can lead to fewer crashes; and how you can monitor your app’s stability with Firebase Crashlytics once your app has been released.
Users expect to have a seamless experience every time they use your app. Crashes can cause churn and poor reviews, and quality issues are one of the main causes of early app deletion. Android apps built with Kotlin have 20% fewer crashes, which is one of the reasons that over 70% of the Top 1000 apps on the Play store have adopted Kotlin. Using Kotlin allows you to reduce the chances of getting null pointer exceptions, which are the #1 type of crashes on Google Play.
Furthermore Kotlin extension libraries let you write cleaner code by reducing boilerplate and making it easier to take advantage of advanced Kotlin language features even when using libraries originally written in Java.
In addition to our Firebase KTX libraries, you can also use the coroutines Kotlin extension libraries by Jetbrains to write safer async code with Kotlin and Firebase:
dependencies { // Coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' // Coroutines extensions for the Tasks API implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.9' // Firestore Kotlin extensions implementation 'com.google.firebase:firebase-firestore-ktx:22.0.2' }
Consider this code to fetch a document from Firestore. This code uses the Firebase Android SDK and the Play services Tasks library, both of which are Java libraries:
fun getUser() { val query = FirebaseFirestore.getInstance() .collection("users").document("user123") // query.get() returns a Task, we attach a callback to get the result query.get().addOnSuccessListener { snap -> val user = snap.toObject(User::class.java) } }
By using the Kotlin extension libraries above we can make this code simpler, safer, and easier to read:
suspend fun getUser() { // Firebase.firestore is a convenient syntax from the firestore-ktx library val query = Firebase.firestore .collection("users").document("user123") // The kotlinx-coroutines-play-services library allows us to "await" the // result of a Task and avoid callbacks, which simplifies our control flow val snap = query.get().await() // The .toObject<T> function from the firestore-ktx library uses Kotlin's support // for advanced generics to avoid the need to pass a Class object val user = snap.toObject<User>() }
Example provided by Firebase GDE Rosario Pereira Fernandes
Once you’ve developed and released your app, Firebase Crashlytics helps you improve and monitor your app stability. With Crashlytics you can track, prioritize and fix stability issues that erode app quality, in real-time. For instance, custom logs and keys in Crashlytics provides you with information on the specific state of your app leading up to a crash and gives context on why a crash occurred. With this level of in-depth insight, you can uncover the root causes of crashes more quickly before they affect a large number of your users.
For further analysis of your Crashlytics data and to segment your user data, you can also export all your crash data to BigQuery in real-time. For example, you can determine emerging crashes in new code, or see the top crash issues for the day to help you prioritize and fix them faster. You can also use our Data Studio template to easily visualize this data with custom dashboards. Data Studio dashboards are easy to collaborate on and share so your team can work more efficiently; even your team members who aren't comfortable with SQL can easily work around BigQuery data sets.
These are just a few examples of the ways you can improve your app stability with Firebase, Kotlin Extensions and Crashlytics. It’s easy to get started with Crashlytics and Firebase Kotlin Extension libraries, and as always if you need help please feel free to reach out to us through our Community Slack.
Happy developing!
A few months ago, we released Firestore for Games into open alpha. Thanks to all of your feedback, today, we're happy to announce that Cloud Firestore for Games is now publicly available in beta for C++ and Unity developers.
Firestore is a new performant and scalable serverless database option from Firebase for all your game development needs. It can help you add guilds to your latest mobile game, build a backend for your next big turn-based game, or add realtime chat. It’s also a key component of Firebase Extensions like Translate Text and Trigger Email, making it easier than ever to deploy pre-built complex workflows to your games.
Cloud Firestore is Google’s next generation cloud NoSQL database. It’s fast, reliable, and ready to scale for whatever workload you send its way. For the past two years it’s been generally available for the Web, iOS, and Android, and now it’s available for game developers as well.
Cloud Firestore exists alongside our existing Realtime Database. We’ll continue to support Realtime Database because it still works great for when you need to share data quickly with lots of players in near realtime. With Firestore you get more advanced queries on your data, 99.999% guaranteed uptime, and support for up to a million concurrent players -- all whilst remaining fast enough for many of your gaming backend needs. If you need more help deciding which database is right for you, check out this helpful guide.
Even though Firestore has been generally available for some time, the existing SDKs were built with the workflows and tools favored by app and web developers. Although game developers working in languages like JavaScript, Kotlin, and Swift would have no problem adopting the existing development kits, you would’ve had to jump through hoops to access them from Unity or C++. However, here at Firebase and Google, we want to make it easy for game developers to build, release, and operate games. So, we've built SDKs that bring game developers the same levels of convenience that app developers enjoy.
The Beta tag means that we believe that the Firestore SDKs for Unity and C++ are now stable enough that game developers can confidently use them in shipping games. Bugs and small API tweaks may crop up here and there as more developers pick it up, but Firestore is now a viable option when starting new game projects.
A big improvement that alpha testers might notice is that the new libraries have full feature parity with the platform specific Firestore SDKs. This includes many features alpha testers have asked for, such as OnSnapshotsInSync and support for the Blob data type. All transforms (like incrementing values) and query types (like "array-contains-any") are now implemented as well. We’ve also worked to make Firestore more consistent with the other Unity Firebase SDKs by ensuring that events fire on the main thread and adding more consistency to error reporting.
OnSnapshotsInSync
Since we’ve seen that many game developers tend to target multiple platforms with a single codebase, we’ve ensured that every feature in Firestore SDK C++ SDK works identically on iOS, Android, Windows, Mac, and Linux. This means that you can develop your game logic on a desktop computer with access to the entire Firestore for games feature set, and remain confident that it will continue to function on your target device. We've also fully opened sourced the C++ SDK so you can easily build, debug, and port your code to wherever you need.
This Firestore beta, like the alpha, ships with the standard Firebase C++ and Unity SDKs. To use it, simply ensure you’re using the latest Firebase SDK either by downloading the C++ zip or the Unity zip. Remember, if you come across any bugs, report them to us through our support channel or on the C++ or Unity issue pages.