Hi, Firestore developers. We are pleased to announce that with the latest version of the client SDKs, you are able to use Firestore data bundles in your mobile and web applications! Data bundles can make certain types of Firestore applications much faster or less expensive.
That's great! Just one tiny little follow-up question: What are data bundles?
So essentially, data bundles are serialized groups of documents -- either individual documents, or a number of documents that you've retrieved using a specific query.
You can save these data bundles onto a CDN or your favorite object storage service, and then load them from your client applications. Once they've been loaded on to your client, your clients can read them in from your local cache like any other locally-cached Firestore data.
Well that just sounds like a database query with extra steps. Why would I want to do this?
The biggest reason is that by having your clients read common queries from a bundle on a CDN and then querying specifically against that cached data, you can avoid making extra calls against the Firestore database. If your application has a substantial number of users, this can lead to some cost savings, and could potentially be faster, too.
But to be clear, data bundles are an advanced feature and probably not something you should be considering until your application has a pretty sizable user base.
So what kinds of documents make sense to put into bundles?
The best kinds of bundles are ones where a majority of your users will be reading in all of the documents in that bundle, the number of documents is on the smaller side, and the contents of that data doesn't change too frequently.
Some good examples for this might be:
Data bundles are not good for:
I just had an idea: What if I were to stuff my entire database into a bundle, load that bundle onto my client, and then just query my entire database using only my cache?
That's a terrible idea.
Remember, the Firestore cache is not particularly fast when it comes to searching through large amounts of data. Overloading your cache by asking it to store a lot of documents that your users won't ever use is a good way of slowing your application to an unusable state. You should really only be leveraging data bundles to load up documents that most, if not all, of your users will be reading in.
Also, keep in mind that with bundles, you have to load in the entire bundle of documents when you load up a bundle, whereas with a normal document query, you're only downloading the documents that have changed from your local cache. So from a data usage perspective, stuffing too many documents into a bundle can be quite heavy.
Gotcha. So how would I implement these?
You can read the documentation for all the details, but in general, the process will work something like this:
So, once they're loaded, I can query them like normal data?
Yes. They'll be merged in with your locally cached data and you can make use of them like you would any other cached data. The data bundle also remembers the names of the queries you used to generate the bundle, so you can refer to these queries by name instead of re-creating them in code.
And how do I make sure I'm querying bundled data without incurring any additional Firestore costs?
The best way is to force your client to use the cached data. When you make a document request or database query, you're able to add an option like {source: 'cache'} that directs your client to only use the cached data, even if the network is available.
{source: 'cache'}
Tell me more about these "named queries" -- are these different from regular queries?
Not really -- the trick to using bundles effectively is that you want to make sure the query you're requesting on the client is exactly the same as the query that generated the bundle you've loaded. You could certainly do this in code, but by using a named query, you're ensuring that the client will always make the same query that generated the bundle in the first place. There's less room for error that way.
This also means that you can modify the server query that generates the bundle and, assuming you're still using the same name, the client will also use this new query without your needing to update any of the client code.
Notice that the version number has changed, but the client can continue to run the "seed-data" named query
What if I wanted to include data bundles alongside the rest of my local application data when I publish it to app stores? That way, if a user opens my app for the first time and is offline, I can read in the bundle and use that as starter data for my application.
Yes; that can be another great use of bundles. The process is the same, except you'll be reading in the bundle using a local call. Just be aware of the warnings above -- Firestore wasn't designed to be an "offline-first" database, so try to only load in as much data as you'll need to make sure your application is functional. If you overload your cache with too much information, you'll slow it down too much.
Okay, I think I'm ready to start using data bundles in my application.
Great! As always, feel free to ask questions on Stack Overflow if you need help, or reach out to us on the Cloud Firestore Discussion list if you have any other suggestions. Happy coding!
This article was originally published on the Google Play Medium channel.
Mobile games have evolved rapidly in recent years. Player expectations have increased along with this evolution. Players now demand games with rich and compelling stories that run smoothly regardless of their device. At the same time, as game studios realized the opportunities in going mobile, there is an ever-increasing number of mobile game titles competing for players' attention. In this increasingly competitive market, the ability to iterate and improve player experiences faster than rival titles is vital. Therefore, having the right tools and knowing how to apply them is a key skill for any game developer.
For many years, Firebase has provided game developers with the tools they need to build, release, and operate successful games. More than 2.5 million apps and games actively use Firebase every month, including global game studios such as Gameloft, Pomelo Games, and Halfbrick Studios.
This article explores four scenarios that game developers deal with on a daily basis and shows how Firebase provides the tools and insights to help you stay ahead of rivals.
It’s easier to provide better experiences to players when you understand how they interact with your game. Knowing how much time players spend in the game, what activities they prefer, and how often they come back enables you to personalize the game experience to suit their behavior and preferences. For example, different groups of players have different monetization preferences. Some may choose to spend money to improve their experience, while others may prefer to see ads and trade their time and attention instead. Understanding these differences – even details such as what people prefer to buy and what they do before making their first in-app purchase – is crucial to optimizing player experiences.
Firebase offers robust integration with Google Analytics. This integration helps shed light on who your players are and how they’re engaging with your game by providing insight into in-app events and user properties. This short video will show you how to log custom events and interpret the data to understand your players better.
Through the audiences feature, Firebase’s Google Analytics integration enables you to segment your user base in ways that are important to your business. You can use these custom audiences to filter reports to understand how different players engage with your game and identify patterns of behavior within the audience. Then, you can use this information to send targeted notifications and personalize the game behavior for player profiles. Take a look at this video to see how to set up audiences for your game.
Push notifications can be used for everything from reminders about opening a chest to letting everybody know the biggest update of the year is available. As push notifications allow real-time communication between a game company and its players, they’re often a critical part of a retention and re-engagement strategy. Most successful games have players worldwide, which means choosing just one language and time of day to communicate with them all is not ideal. After all, you don’t want to annoy players by sending them a push notification at 2 a.m.
Firebase Cloud Messaging lets game developers send targeted messages and notifications, which can be customized to suit your brand and align with player preferences. This video shows how you can send push notifications to players with devices set to a particular language and schedule delivery at an appropriate local time. Firebase Cloud Messaging also lets you target by game versions, geographies, your custom audiences, and many more variables. After sending the push notifications, you can see how many were delivered, how many were opened, and, if you choose to set it up, how many conversions happened.
While many players will gladly invest money in the games they enjoy, other players prefer ads in exchange for the gameplay. Many games employ a hybrid monetization strategy, meaning they include both ads and in-app purchases. As a general rule, the more ad impressions, the greater the revenue generated with ads. However, many other factors need to be considered to optimize and balance user experience and revenue. Differences between ad formats, their sizes, and how often they’re shown without disrupting the game experience are important factors.
Once you decide on these factors, how can you determine if the selected formats are the best ones? Is the frequency right or too much, and is the frequency negatively impacting retention? These questions can be answered quantitatively with Firebase A/B testing. A/B testing can be used with Firebase Remote Config to experiment with different combinations of ad types and frequencies to find the best option. To set up an experiment, all you need to do is define a goal, like increase total revenue, and identify secondary metrics such as D1 and D7 retention.
This video walks you through the process of setting up an experiment to test different ad formats so you can identify the best choice for a game. These parameters will vary from game to game, so it is always good to test the options available.
Pomelo Games, one of the top game studios in Uruguay, used Firebase Remote Config and Firebase A/B Testing to test the effect of showing interstitial ads to their entire player base versus a specific segment. Then, they used Google Analytics to measure the impact on revenue and retention. They also used Firebase Crashlytics to keep an eye on their game vitals. After two weeks of testing, the Pomelo team discovered that interstitial ads led to an average 25% increase in AdMob revenue and, surprisingly, a 35% increase in in-app purchases too. In both tests, there was almost no effect on retention. (Check out the full case study.)
Releasing new game features can be nerve-wracking because you may worry about how players will receive the new features. Will players enjoy the new feature you worked hard on? Will the new feature increase engagement and session time? One way to gain confidence that new features will positively impact your key metrics is by slowly rolling them out and seeing how they perform with a subset of your players before wider release.
In addition to gradually rolling out new features, it's also important to continually experiment with new content or in-game mechanics to optimize the player experience. However, constantly iterating your game can be a time-consuming and tedious process if you don't have the right tools.
Firebase Remote Config lets you dynamically configure your game and confidently roll out new features so you can deliver highly personalized experiences to your players without publishing an app update. This video shows you how to set up and tweak Remote Config parameters and instrument feature flagging.
Conclusion
Firebase is a powerful platform. It’s a great fit for game companies that want to enhance how they optimize experiences to delight their players and improve engagement and monetization. To get started with Firebase, you create your project in the Firebase console. To see more examples of how to use Firebase to supercharge your games business, check our Games with Firebase video series, where we walk through each step of the implementation process and share common use cases.
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.
Firebase Extensions are a convenient way to add new functionality to your app with the click of a button. They are pre-packaged solutions for common problems that you can easily add to your Firebase app. Extensions are based on Firebase and Google Cloud products that you already know and use.
On the Firebase Extensions page, you will find a collection of official extensions for features that developers find themselves in need of regularly, such as resizing images, sending emails based on the contents of Firestore documents, translating text, shortening URLs, and more. Most of these extensions have been built and tested by the Firebase team, and follow best practices. In addition, we have teamed up with partners like Stripe to bring you extensions for processing payments and sending invoices.
Installing extensions is easy via the Firebase console or the Firebase command line interface.
To allow us to get more feedback from you and to better understand which use cases and extension features people are most interested in, we are launching a new type of extensions: Experimental Extensions.
These are fully functional extensions that we think you'll find useful, but they haven't yet gone through the same amount of testing and polish as the official published extensions.
The following extensions are built by our engineers looking for feedback from the community.
To install an experimental extension, navigate to the extension's README file in the Firebase Experimental Extensions repo on GitHub, and then click on the Install on Firebase button. This will take you to the Firebase Console, where you can then select the project you'd like to install this extension in.
You can also install extensions via the command line. To do so, make sure you've got the Firebase Command Line tools installed, then run the following command:
$ firebase ext:install firestore-shorten-urls-dynamic-links --project=(your project ID)
This command will install the Shorten URLs with Dynamic Links experimental extension in your project.
We love to hear your feedback, and we're looking forward to seeing what you will build using the new experimental extensions! Use the GitHub issue tracker of the experimental extensions repository to leave feedback.
If you'd like to submit PRs to improve an experimental extension, you will need access to the extension authoring tools. To learn more about this, join our Alpha program.
From conversations with you, we know you're excited to build and share your own extensions.
To share extensions with other developers, you need to become an Extension Publisher. Register as a publisher by joining the Firebase Alpha program, and indicate you're interested in building Community Extensions in the comments field. You can then publish your extensions under your publisher ID.
Your extension code will live in Firebase, so other alpha group members can install your extension directly from the command line.
At the moment, Community Extensions can only be shared with members of the Firebase Alpha program, but we're working hard to let you share your extensions publicly.
We're excited to share these two new kinds of extensions with you, and we can't wait to see what you're going to build!
We made a comic about Crashlytics—the first ever comic created by Firebase! The Great Crash Detective investigates a mystery case of a new crash in her mobile application. Does she solve it, or let her product manager down? Read it to find out!
The inspiration for this comic all started with a customer interview. On the Crashlytics team, we love talking to developers about how they're using our product. So naturally when a developer reached out to us to find out if there were ways to better understand the events leading up to a crash, we jumped on the task. Crashlytics allows you to add custom logs to your crash data. However, this developer needed help searching through these custom logs to find what events these crashes were occurring within.
In August of 2018, Crashlytics released the ability to export your data to BigQuery and enhanced it last year by making it possible to export data in realtime. One of the useful things about exporting your crashes to BigQuery is that you can freely investigate the underlying crashes occurring in your app.
We asked the developer who was interested in searching their custom logs if they had considered exporting their data to BigQuery, they explained that they didn't have bandwidth to learn how to use SQL.
Learning SQL is a continuous process
This was a huge opportunity for us to communicate with customers the alternatives to using SQL (though, we also think exploring your BigQuery data using SQL is pretty great!). For example, Google Data Studio lets you visualize your BigQuery data quickly and easily. You can use our template to customize a report to address your specific needs.
The developer was able to use a Google Data Studio report to visualize their crash reports and analyze their data quickly and easily. They had already followed the steps to enable BigQuery export and the instructions to attach a datasource to our Data Studio Template to create a report, the steps to adding a custom log filter were quick and easy:
First, they navigated to the "Crashes by File" page of the report.
Next, they clicked Insert -> Advanced Filter.
Afterwards, they placed the filter in an empty spot in the report.
Finally, they edited the control field for this filter. On the data pane (to the right of the report), they changed the control field to "logs.message".
That's all that was needed to search a Crashlytics crash dataset for the occurrence of custom log events (here's an example template of the above steps). Searching by other fields within Crashlytics crashes, such as analytics breadcrumbs, is as easy as changing the control field for that filter.
We'd love to hear your feedback. Should we do more Firebase comics? Are you interested in what happens next for the Great Crash Detective? Do you have any feedback about using Crashlytics with BigQuery and Data Studio? Feel free to reach out to us on Twitter or come hang out with us in the Firebase Community Slack channel.
Did we make a comic about Crashlytics, or was it all a dream?
This comic was created by Luke Kruger-Howard, an Ignatz-nominated comic artist, in collaboration with myself and other engineers and product folks from the Crashlytics team.
p.s. Why did the horse's app crash?
Because the app was unstable.
Firebase Security Rules gate your user's access to and enforce validations for Firestore, Firebase Storage, and the Realtime Database. It's important to code review the Security Rules, just like you code review the application code. Because these rules are written in a domain-specific language, that puts some people in the position of code reviewing something they don't feel like they understand.
If you're finding yourself in that position, don't worry! This post will walk through how to approach reviewing and giving good feedback on Security Rules. The examples will be from Firestore Security Rules, and are mostly applicable to Storage as well. If you're reviewing Realtime Database Security Rules, although these principles apply, the rules use a different language, so the examples will be different.
When you're looking at Security Rules, check first for any top level rules that apply to everything. A document can match multiple rules, and if any rule grants access, access is granted. In the example below, there's a specific rule that only grants authors access to post documents, but the global rule lets anyone on the internet read or write to any place in your database:
post
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if true; } match /posts/{post} { allow read, write: if request.resource.data.authorUID == request.auth.uid ; } } }
It's important to look for the global match statement match /{document=**} throughout the entire rules file, not just at the top. If there are thorough rules but also a global match statement, universal access will still be granted through the global match statement.
match /{document=**}
There are a very small number of valid use cases for global match statements. For example, granting access to admin users:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if isAdmin(); } } }
If you find a global rule that is read, write: if true, you can stop your review and ask them to fix it. If you find any other condition on a global rule, make sure it makes sense for your application.
read, write: if true
The next thing to do is to look at the data that you're storing. What is Personally Identifiable Information (PII) that should only be accessed by that particular user?
Security Rules apply to entire documents, not just fields. You should be able to look at each kind of document that you have and describe which kind of users should be able to read it, create it, update it, and delete it.
If you find documents where it's fine for most of the document to be read by anyone, but a few fields need to remain private, break those fields into their own document; the easiest way to do this is usually to create a subcollection off the existing document.
Now check the Security Rules for each of the documents that you identified as containing PII. The best practice for PII is that it is keyed by the user's ID, and only that user is allowed access:
match /secrets/{uid} { allow create, update: if request.auth.uid == uid; }
This pattern only works if there's a max of one document in the collection for each user. For cases of multiple documents per user, you could create subcollections of a user document:
match /users/{uid}/secrets/{secret} { allow create, update: if request.auth.uid == uid; }
Or you could include the id of the user who should have access as an attribute, and restrict access to that user:
match /secrets/{secret} { allow create, update: if request.auth.uid == request.resource.data.uid; }
Ideally, you would check that each document is as locked down as possible, but if you have limited time, the most important thing to check is that PII lives in separate documents and those PII documents are only accessible to that user.
Just like application changes should come with test changes, so should Security Rules. Security Rules tests run against the Firebase Emulator Suite, and can be included in your CI setup.
If you're not yet testing Security Rules, check out this documentation to get started, this video for an overview of testing, and this blog post and video on adding your tests to CI.
If the Security Rules are fully tested, there should be a lot of tests. For each kind of document, there are usually four different kinds of access: read, create, update, and delete. (These are just the most common permissions to grant; create, update, and delete can be rolled up into write, and read can be broken into get and list.) For each permission, there should be a test of the happy path, granting permission, and a test for each situation that should deny access.
read
create
update
delete
write
get
list
Say I have one rule:
// firestore.rules allow update: if // User is the author resource.data.authorUID == request.auth.uid && // `authorUID` and `createdAt` are unchanged request.resource.data.diff(request.resource.data).unchangedKeys().hasAll([ "authorUID", "createdAt" ]) && // Title must be < 50 characters long request.resource.data.title.size < 50;
To completely test this, I would write tests around granting access and each way that access could be denied:
// test.js const firebase = require("@firebase/rules-unit-testing"); const dbAuthorAuth = firebase.initializeTestApp({ projectId: TEST_FIREBASE_PROJECT_ID, auth: { uid: "author", email: "alice@example.com" } }).firestore(); const dbOtherAuth = firebase.initializeTestApp({ projectId: TEST_FIREBASE_PROJECT_ID, auth: { uid: "other", email: "otto@example.com" } }).firestore(); describe("blog posts", () => { before(async () => { dbAuthorAuth.doc("drafts/12345").set({ authorUID: "author", createdAt: Date.now(), title: "Make an apple", content: "TODO!" }); }); it("can be updated by author if immutable fields are unchanged", async () => { await firebase.assertSucceeds(dbAuthorAuth.doc("drafts/12345").update({ title: "Make an app", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); }); it("cannot be updated by anyone other than the author", async () => { await firebase.assertFails(dbOtherAuth.doc("drafts/12345").update({ title: "Make an app", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); }); it("cannot be updated by author if the author ID is changed", async () => { await firebase.assertFails(authorDb.doc("drafts/12345").update({ authorUID: "New Person" title: "Make an app", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); }); it("cannot be updated by author if the created date is changed", async () => { await firebase.assertFails(authorDb.doc("drafts/12345").update({ createdAt: Date.now(), title: "Make an app", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); }); it("cannot be updated if the title is over 50 characters", async () => { await firebase.assertFails(authorDb.doc("drafts/12345").update({ title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); });
As the reviewer, look through their test cases, and make sure they have tested each kind of document. If they've written good test descriptions, that's the easiest place to understand what the Security Rules are doing. Similarly, if they've written code comments in their rules, that can also be a good entry point to understanding the Security Rules.
Subcollections don't inherit the rules from the parent document, so make sure they're specifically covered in the Security Rules. Look at the subcollections you're storing in Firestore; if all documents should have the same access that a parent document has, then that parent document should use the glob syntax: match /post/{id=**}.
match /post/{id=**}
What's more common is that a subcollection has been broken out into a subcollection because a different person needs to read it, or someone else is allowed to write to it. In that case, check the Security Rules to make sure it has its own match statement. Nesting is only a stylistic difference; whether you nest your match statements or not, make sure this is a match statement for the documents in the subcollections:
// Nested match statements are fine match /post/{postID} { allow read: if ... match /comments/{commentID} { allow read: if ... } } // Unnested match statements are fine match /post/{postID} { allow read: if ... } match /comments/{commentID} { allow read: if ... }
If you're reviewing Realtime Database Security Rules, child nodes inherit from parent nodes, the opposite of the behavior in Firestore.
Security Rules can enforce type and data validations for specific fields in addition to preventing unwanted access. If you know that some documents have required fields, immutable fields, fields that must be a timestamp, or a field that must contain data in a specific range, check that those are enforced in the Security Rules for those documents. For example, in this rule, I'm checking that the immutable fields of authorUID, publishedAt, and url aren't changed by an update, and that the required fields of content, title, and visible are still present:
authorUID
publishedAt
url
content
title
visible
allow update: if // Immutable fields are unchanged request.resource.data.diff(request.resource.data).unchangedKeys().hasAll([ "authorUID", "publishedAt", "url" ]) && // Required fields are present request.resource.data.keys().hasAll([ "content", "title", "visible" ]);
If you use the more granular permissions of create, update, and delete in place of write, make sure that validations apply to both create and update.
Because the Admin SDK authorizes using service account credentials, all requests from the Admin SDK, including Cloud Functions for Firebase, bypass Security Rules. To give a thorough security review for a Firebase app, it's also necessary to look at any other writes that are happening to your backend via the Admin SDK.
For example, if I have great Firestore Security Rules, but a Cloud Function exports data to a storage bucket that has no Security Rules, that would be a terrible way to treat my user's data. The flip side of this is that Cloud Functions can be used in situations where Security Rules don't work, for example, returning individual fields from documents. See this blog post for tips to use Cloud Functions in conjunction with Security Rules.