It’s been a year since we all came together in Madrid, Spain for Firebase Summit 2019, shared announcements and updates, and listened to your product feedback. Since then the world as we know it has changed, but here at Firebase our mission to help developers succeed by making it easy to build and operate mobile and web apps remains the same.
We’ve been working hard to build tools to help you accelerate app development, gain actionable insights and scale with ease. We’re looking forward to sharing these updates with you at this year’s virtual Firebase Summit, happening 27-28 October at 9:30AM PST each day. All you have to do is prop up your mobile device or laptop, head over to firebase.google.com/summit, and tune in from wherever you’d like.
While we will miss connecting with you in person, we are excited that even more developers can join us this year.
Here’s what to expect during the two days:
Join us 27 October at 9:30 am PST for the opening keynote to learn how Firebase can help you build and operate your apps. After the keynote, you can ask questions in the chat and have them answered live by the Firebase team during #AskFirebase.
Stay tuned for 11 technical sessions on topics like monitoring your latest release, increasing user engagement, optimizing your app revenue, and more. These sessions will be live streamed over the two days so you can watch and chat with other developers and the Firebase team, or you can watch them on-demand at your convenience.
For a more hands-on learning experience, watch us live code an app from scratch using Flutter and Firebase. You can also try one of our new demos, codelabs with walkthrough videos and multi-step tracks to learn about popular topics such as how to build a web app using Firebase.
We will be releasing more details about Firebase Summit in the coming weeks. In the meantime, sign up for updates on our event page, subscribe to the Firebase YouTube channel, and follow us on Twitter to join the conversation.
One of the biggest challenges game developers face is figuring out how to improve monetization without compromising their game experience. Many game developers embed ads into their titles, which enables them to offer their games for free and remove the cost barrier of adoption for players - while still generating revenue. In-app advertising can be lucrative, when done effectively and in moderation.
But how do you know what types of ads are best-suited for your game? How do you ensure ads won’t drive away players? These are the exact questions Pomelo Games had. For answers, they turned to Firebase.
Pomelo Games is one of the top game studios in Uruguay. They pride themselves on developing unique and polished games that capture players’ imaginations. Their recent release, Once Upon a Tower, “is an easy-to-pick-up, hard-to-put-down, free-to-play game,” says co-founder Jonás Mora. A Play Store Editors’ Choice, the game is beloved for its high-fidelity graphics, as well as the “fairness of its free-to-play mechanics,” says Jonás.
So when the team needed to improve the game’s monetization, they were unsure how to proceed. They were looking for a way to increase revenue without sacrificing the affordability and game quality their players loved.
Pomelo Games used Firebase Remote Config and Firebase A/B Testing to test a new ad format: interstitials. They also used Google Analytics to monitor revenue and Firebase Crashlytics to keep an eye on stability.
Although initially opposed to the idea, Jonás and his team discovered that showing interstitial ads to their entire player base led to an average 25% increase in AdMob revenue, and surprisingly, a 35% increase in in-app purchases as well. In both cases, there was almost no negative impact on retention or game stability. Firebase gave Pomelo Games the confidence to try new approaches to grow revenue without driving players away. Read Pomelo Games’ full story and get details on their success with Firebase in our new case study. And learn more about how Firebase can help you build and grow your game, and see what other game studios are using Firebase.
Nothing spoils a great game for a user like a crash, and most of us have been there too: you’re just about to solve the puzzle, defeat the final boss, or cross the finish line, and then...crash.
For Tapps Games, a Brazilian game developer, it was particularly important that players had a stable and high performing gaming experience during one of the feature rollouts to their popular game, Vlogger Go Viral. With more than 11M monthly active users, the Tapps team didn’t have time to investigate every negative review manually — it would take days, leaving a considerable number of users waiting for resolution.
To solve this, Tapps Games enabled Firebase Crashlytics velocity alerts, which let them know right away when there was an increase in the severity of crashes occurring in the Vlogger Go Viral game. Crashlytics also helped them prioritize, identify and track the state and sequences of events that led to the crash using custom keys and custom logs. The Vlogger Go Viral team then used Remote Config to shut down the problem area in stages and used staged rollouts on the Google Play console to slowly release the new version to a subset of it’s players before moving ahead to a full rollout
Check out the full case study to find out how Tapps Games used Crashlytics and Remote Config to increase crash free user rate and improve ratings, and learn more ways Firebase can help you build and grow your game.
We’re excited to announce several new features that make developing with Firebase Hosting even better!
Our new integration with Cloud Logging gives you access to web request logs for your Hosting sites. Cloud Logging, previously known as Stackdriver Logging, makes it easy to view, search, and filter logs. You can learn from where and when you have visits to your site, your site's response statuses, the latency of end user requests, and more.
To get started, link your Firebase project to Cloud Logging in the Cloud Logging integration card in the Firebase console or visit the Hosting docs to learn more.
Hosting will now compress your assets with Brotli encoding. We’ll automatically serve the smallest, best-compressed version of your content, based on what your user's client is able to handle.
Brotli compression gives you a boost in performance because assets are smaller, use less bandwidth, and are delivered more quickly to your end users. This means you’ll have lower bandwidth costs, while your end users will enjoy faster sites.
The best part? You’ll get this automatically the next time you deploy.
We know it’s important to you to provide a great experience for your users everywhere. Firebase Hosting now supports serving country and language specific content, backed by the power of our global CDN. Previously, the best strategy was to use Cloud Functions to look at the country and/or language code and reply with a redirect as necessary.
Firebase Hosting’s i18n rewrites allow developers to serve different content, depending on a user's language preferences or country location. In your public directory, create an i18n directory containing separate folders for each language/country combination, then add the i18n field to your project's firebase.json config. I18n rewrites also work for localized 404 pages. Learn more about using i18n rewrites in our documentation.
i18n
firebase.json
We hope you all are excited for these new features, but stay put because we have even more coming in the future!
The latest Google Analytics for Firebase SDK release now makes it possible for you to log the screen_view event manually for both Android and iOS!
screen_view
When you add the Google Analytics for Firebase SDK to your app, there are a number of different events that start getting collected automatically - including the screen_view event for your Android and iOS apps.
This event is incredibly important to understand your users' behavior since it can tell you the number of users who have visited each screen in your app, and which screens are the most popular. In addition, this event powers the Behavior reports offered in Google Analytics’s App + Web properties, which can tell you how much time users spend in each screen. It also powers the Path Analysis tool where you can discover the most popular navigational paths within your app.
Behavior overview reporting page
Path analysis technique
Most of the automatically collected events, including screen_view, use protected event names that prevent you from being able to manually log events with the same name. There are good reasons for doing this because a lot of these events power other Firebase products, like A/B Testing and Firebase Predictions, and first class reporting features in Google Analytics.
But we’ve heard a lot of your feedback - and screen_view is one event for which you wanted more control over how and when the event gets logged - in addition to having the option to continue logging events automatically.
So our latest release now makes this possible. This release also marks the deprecation of the setCurrentScreen (on Android) and setScreenName (on iOS) methods which will be replaced by the new calls to manually log the screen_view event.
There are many benefits to having more control for when the screen_view event gets logged:
For example, you might only want to log a screen_view event when a user has spent at least some minimum amount of time on the screen.
In fact, you might recall that Google Analytics previously logged session_start events ten seconds after an app first loaded into the foreground. While this has changed and session_start events are logged immediately, there can be instances in your app when it makes sense to wait a few seconds before logging a meaningful screen_view.
session_start
In a video streaming app, users may often load up a title selection screen and quickly scroll through and select a series they’ve already been watching to pick up where they left off. They might not really be searching for new titles, even though they’re visiting the title screen, and you'll want to only log screen views here when you really know that they’re looking for new titles, not just quickly scrolling through it.
Your app might have subsections in some screens (think “Container View Controllers” with inner Child VCs or Fragments), or in the case of a game or a Flutter app, be driven by one screen that powers other views that present meaningful actions a user can take, or represent places in your app where users will spend a significant amount of time. In such cases, you might want to log a screen_view event manually, and add additional event parameters that represent the subsections of your app that your users spend more time exploring.
A good example here is an app that provides a customer support chat interface that overlays onto an existing screen the user is viewing. In such cases, you might want to log a manual screen_view event that represents that chat interface and that can collect more information in added event parameters - like which topic or which screen the user was on before they opened the support chat window.
In addition to the reasons above, there was an issue on iOS devices with automatically collected screen_view events using the soon-to-be-deprecated setScreenName method in which the events were dual-logged. This change also corrects this issue so it is no longer a factor.
setScreenName
You can log a manual screen_view just like you would any other event in Google Analytics. You can also include the two optional parameters (i) screen_name and (ii) screen_class, as well as any custom event parameters you want to include. The two optional parameters take the place of the firebase_screen and firebase_screen_class event parameters that get passed into automatically collected screen_view events.
screen_name
screen_class
firebase_screen
firebase_screen_class
Specifically for iOS, this would look like:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // After enough time has passed to make this screen view significant. Analytics.logEvent(AnalyticsEventScreenView, parameters: [ AnalyticsParameterScreenName: screenName!, AnalyticsParameterScreenClass: screenClass!, MyAppAnalyticsParameterFitnessCategory: category! ]) }
And for Android:
@Override public void onResume() { super.onResume(); // After enough time has passed to make this screen view significant. Bundle bundle = new Bundle(); bundle.putString(FirebaseAnalytics.Param.SCREEN_NAME, screenName); bundle.putString(FirebaseAnalytics.Param.SCREEN_CLASS, screenClass); bundle.putString(MyAppAnalyticsConstants.Param.TOPIC, topic); mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, bundle); }
As mentioned before, the new API will allow you to log manual screen_view events along with the option to continue collecting screen_view events automatically. Since both are possible at the same time, let’s get into some questions to learn more about how the new feature works with the existing automatic event collection.
Yes. While you can use both together at the same time, you can disable automatic event collection for screen_view events if you’d like. On iOS, set FirebaseAutomaticScreenReportingEnabled to “NO” in your info.plist. On Android, set google_analytics_automatic_screen_reporting_enabled to “false” in your manifest.
FirebaseAutomaticScreenReportingEnabled
google_analytics_automatic_screen_reporting_enabled
The setScreenName and setCurrentScreen method calls were used to set a custom-defined screen name as the firebase_screen_name parameter value included with all automatically logged screen_view events. If you want to continue collecting custom screen names on your screen_view events, you will need to log the screen_view event manually and pass in the screen_name event parameter (likely using the same value you were using in the soon-to-be deprecated methods).
setCurrentScreen
firebase_screen_name
Note that for automatically reported screen_view events, the screen name value will be left blank, and the screen class value will be set to that of the most recently shown screen.
If you disable automatic screen reporting, some values are still accessible and can be automatically added to your manually logged screen_view events, such as the firebase_screen_class parameter.
This is because parameters like the screen class are first-class metrics that feed into Google Analytics’ Behavior Reports, and so we’ve gone the extra mile to automatically populate this field using information we know on the client. On Android, the current activity will be used to populate the screen class parameter. On iOS, if you make a call to the logEvent() method, the root UIViewController will be used to populate the screen class value. You can always overwrite this value by passing in a different screen_class parameter, though. On both platforms the screen name will be left blank (unless you specify it), and the value “not set” will be displayed in the Google Analytics console.
logEvent()
Yes, all event parameters that are reported along with automatically collected screen_view events will also be automatically reported with manually logged screen_view events - with one temporary exception for the engagement_time_msec on iOS devices. We are working on implementing support to have the engagement time parameter included with manually reported screen_view events on iOS, but for the time being this parameter isn’t included and instead, is reported on a designated user_engagement event.
engagement_time_msec
We hope you enjoy the new ability to manually log your screen view events, and would love to hear more feedback from you about this feature, or about the Google Analytics for Firebase SDK for your apps in general. Please reach out to us at our Firebase Google Group, on StackOverflow or on our Firebase Support channel to submit your feature requests.
Firebase is committed to building an easy to use and robust backend service for Unity developers. Recent changes in the build system accompanying Unity 2020.1 have caused some of the integrations that the Firebase games team built for Unity to stop functioning correctly. Our team is aware of this, and is working hard to rectify the situation. Until then, we recommend that you continue to use Unity 2019 for the best developer experience. For those of you that are on Unity 2020.1, the rest of this post details the workarounds needed to get things up and running.
The Firebase SDK for Unity includes native C++ code and Android specific logic. It also requires some build time operations for all of its products to function properly. Because of this, we have written a number of tools and custom routines that make strong assumptions about Unity’s build process. These continue to work to simplify integration back to Unity version 5, but in Unity 2020.1 the engine overhauled its build system invalidating our previous assumptions. This means that the mechanism by which AndroidX and Jetifier are integrated no longer functions as well as the mechanism by which the information in google-services.json is transmitted to your final Android game. This is required to initialize the Android side of Firebase and to properly hook it up to the Firebase backend.
google-services.json
Further, components that the Crashlytics SDK requires to function are no longer generated. In the worst case scenario, this manifests in a crash on launch.
Although we recommend sticking with the 2019 branch of Unity for the best experience, Unity’s new build system is very similar to a typical Android Java project. This simplifies the workaround we will highlight below, and will mirror some steps in the getting started with Android guide.
Note that because you are performing operations that the Firebase SDK would typically handle automatically, you may have to undo any changes when a full fix is rolled out by our team.
The first fix is to help Firebase find your google-services.json file. Normally the Firebase Unity Plugin would do this automatically, replicating the work of the Google Services plugin Android developers may be familiar with. When you add or update your google-services.json file, the plugin creates a version that Android reads as a “resource” at Assets/Plugins/Android/Firebase/res/values/google-services.xml. Typically this would get copied into your final project automatically, but we can do a little manual work to ensure it gets included!
Assets/Plugins/Android/Firebase/res/values/google-services.xml
First generate a custom mainTemplate.gradle and gradleTemplate.properties (which we’ll need in a moment) via Project Settings/Player/Publishing Settings if you haven’t already:
mainTemplate.gradle
gradleTemplate.properties
Project Settings/Player/Publishing Settings
If you’ve disabled EDM4U’s integration or you just want to be extra careful, now is a great time to also force resolve your Android dependencies from Assets/External Dependency Manager/Android Resolver/Force Resolve. This will update mainTemplate.gradle in a manner that still cuts back on most of the work we’ll have to do:
Assets/External Dependency Manager/Android Resolver/Force Resolve
Now you need to tell gradle where to find the google-services.xml file by adding this to the very bottom of your mainTemplate.gradle:
google-services.xml
android { sourceSets { main { def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") res.srcDirs += (unityProjectPath + '/Assets/Plugins/Android/Firebase/res/values/google-services.xml') } } }
There’s one more fix to get base functionality working in Unity, you’ll see that towards the top of mainTemplate.gradle there are some lines like:
// Android Resolver Repos Start ([rootProject] + (rootProject.subprojects as List)).each { ext { it.setProperty("android.useAndroidX", true) it.setProperty("android.enableJetifier", true) } }
This code used to go through every project in the build and enable AndroidX and Jetifier. Although we could perform a similar modification via gradle, Android developers are generally encouraged to use gradleTemplate.properties - which is supported via Unity’s new build pipeline.
So if you remembered to generate it earlier in the tutorial, add the following lines to the end of gradleTemplate.properties:
android.useAndroidX=true android.enableJetifier=true
At this point, most of Firebase should function. This includes your ability to measure your game’s performance with Google Analytics, use that to customize your player’s experience with Remote Config, and to Authenticate you user to begin your game’s connection social experience.
Crashlytics needs a little more work to get operational since it has to embed itself a little deeper into the build process. To continue, you will need the baseProjectTemplate.gradle (the “project level gradle file”). You’ll also need launcherTemplate.gradle since we need an “Application” for Crashlytics (mainTemplate.gradle is technically a “Library”):
baseProjectTemplate.gradle
launcherTemplate.gradle
There’s no need to Force Resolve dependencies again since EDM4U doesn’t know about these files yet! But you can always do it to be safe (the way I modified mainTemplate.gradle purposely avoids bits of the file that would get updated).
Now open baseProjectTemplate.gradle and look for something that looks roughly like:
allprojects { buildscript { repositories {**ARTIFACTORYREPOSITORY** google() jcenter() } dependencies {
Inside allprojects.buildscript.dependencies (the dependencies { bit in this example), add this code to tell gradle about Crashlytics:
allprojects.buildscript.dependencies
dependencies {
// Add the Crashlytics Gradle plugin. classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
and this line of code to the very end of launcherTemplate.gradle to add the Crashlytics plugin to your Application:
// Apply the Crashlytics Gradle plugin apply plugin: 'com.google.firebase.crashlytics'
We are working hard to resolve these issues as soon as possible so you can go back to building games quickly, easily, and confidently, with Firebase and Unity. To those developers who did upgrade to Unity 2020.1 and encountered issues, we apologize for any confusion or difficulty that this has caused. We would like to reassure you all that there is also active work on some exciting updates that will aid in avoiding similar issues in the future and bring more transparency to this process.
If these steps don’t work for you, or a specific Firebase library isn’t working after you’ve completed these steps, feel free to join in the conversation at the Unity GitHub repository or reach out to support and we will be happy to provide further help.
For your reference, I’ve included all of my modified files here. Do not copy these files directly, but use them to verify that you’ve made the proper changes in the proper locations:
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN // Android Resolver Repos Start ([rootProject] + (rootProject.subprojects as List)).each { ext { it.setProperty("android.useAndroidX", true) it.setProperty("android.enableJetifier", true) } } ([rootProject] + (rootProject.subprojects as List)).each { project -> project.repositories { def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") maven { url "https://maven.google.com" } maven { url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/Firebase/Editor/AnalyticsDependencies.xml:18, Assets/Firebase/Editor/AppDependencies.xml:20, Assets/Firebase/Editor/CrashlyticsDependencies.xml:20, Assets/Firebase/Editor/RemoteConfigDependencies.xml:20 } mavenLocal() jcenter() mavenCentral() } } // Android Resolver Repos End apply plugin: 'com.android.library' **APPLY_PLUGINS** dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start implementation 'com.google.firebase:firebase-analytics:17.4.1' // Assets/Firebase/Editor/RemoteConfigDependencies.xml:15 implementation 'com.google.firebase:firebase-analytics-unity:6.15.2' // Assets/Firebase/Editor/AnalyticsDependencies.xml:18 implementation 'com.google.firebase:firebase-app-unity:6.15.2' // Assets/Firebase/Editor/AppDependencies.xml:20 implementation 'com.google.firebase:firebase-common:19.3.0' // Assets/Firebase/Editor/AppDependencies.xml:13 implementation 'com.google.firebase:firebase-config:19.1.4' // Assets/Firebase/Editor/RemoteConfigDependencies.xml:13 implementation 'com.google.firebase:firebase-config-unity:6.15.2' // Assets/Firebase/Editor/RemoteConfigDependencies.xml:20 implementation 'com.google.firebase:firebase-crashlytics:17.0.0' // Assets/Firebase/Editor/CrashlyticsDependencies.xml:13 implementation 'com.google.firebase:firebase-crashlytics-unity:6.15.2' // Assets/Firebase/Editor/CrashlyticsDependencies.xml:20 // Android Resolver Dependencies End **DEPS**} // Android Resolver Exclusions Start android { packagingOptions { exclude ('/lib/arm64-v8a/*' + '*') exclude ('/lib/armeabi/*' + '*') exclude ('/lib/mips/*' + '*') exclude ('/lib/mips64/*' + '*') exclude ('/lib/x86/*' + '*') exclude ('/lib/x86_64/*' + '*') } } // Android Resolver Exclusions End android { compileSdkVersion **APIVERSION** buildToolsVersion '**BUILDTOOLS**' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { minSdkVersion **MINSDKVERSION** targetSdkVersion **TARGETSDKVERSION** ndk { abiFilters **ABIFILTERS** } versionCode **VERSIONCODE** versionName '**VERSIONNAME**' consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD** } lintOptions { abortOnError false } aaptOptions { ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" }**PACKAGING_OPTIONS** }**REPOSITORIES** **IL_CPP_BUILD_SETUP** **SOURCE_BUILD_SETUP** **EXTERNAL_SOURCES** android { sourceSets { main { def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") res.srcDirs += (unityProjectPath + '/Assets/Plugins/Android/Firebase/res/values/google-services.xml') } } }
org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M org.gradle.parallel=true android.enableR8=**MINIFY_WITH_R_EIGHT** **ADDITIONAL_PROPERTIES** android.useAndroidX=true android.enableJetifier=true
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN allprojects { buildscript { repositories {**ARTIFACTORYREPOSITORY** google() jcenter() } dependencies { // If you are changing the Android Gradle Plugin version, make sure it is compatible with the Gradle version preinstalled with Unity // See which Gradle version is preinstalled with Unity here https://docs.unity3d.com/Manual/android-gradle-overview.html // See official Gradle and Android Gradle Plugin compatibility table here https://developer.android.com/studio/releases/gradle-plugin#updating-gradle // To specify a custom Gradle version in Unity, go do "Preferences > External Tools", uncheck "Gradle Installed with Unity (recommended)" and specify a path to a custom Gradle version classpath 'com.android.tools.build:gradle:3.6.0' **BUILD_SCRIPT_DEPS** // Add the Crashlytics Gradle plugin. classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0' } } repositories {**ARTIFACTORYREPOSITORY** google() jcenter() flatDir { dirs "${project(':unityLibrary').projectDir}/libs" } } } task clean(type: Delete) { delete rootProject.buildDir }
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN apply plugin: 'com.android.application' dependencies { implementation project(':unityLibrary') } android { compileSdkVersion **APIVERSION** buildToolsVersion '**BUILDTOOLS**' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { minSdkVersion **MINSDKVERSION** targetSdkVersion **TARGETSDKVERSION** applicationId '**APPLICATIONID**' ndk { abiFilters **ABIFILTERS** } versionCode **VERSIONCODE** versionName '**VERSIONNAME**' } aaptOptions { noCompress = ['.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS**] ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" }**SIGN** lintOptions { abortOnError false } buildTypes { debug { minifyEnabled **MINIFY_DEBUG** proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG** jniDebuggable true } release { minifyEnabled **MINIFY_RELEASE** proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG** } }**PACKAGING_OPTIONS****SPLITS** **BUILT_APK_LOCATION** bundle { language { enableSplit = false } density { enableSplit = false } abi { enableSplit = true } } }**SPLITS_VERSION_CODE****LAUNCHER_SOURCE_BUILD_SETUP** // Apply the Crashlytics Gradle plugin apply plugin: 'com.google.firebase.crashlytics'
Firebase Remote Config allows developers to control the appearance and the behaviour of their apps without having to publish an app update. You can build your app with in-app default configurations and later change those configurations by updating your project’s Remote Config template.
We are happy to announce that you can now manage your Remote Config templates directly from the Admin Node.js SDK.
Typically, you would use the Firebase console to specify and update Remote Config templates manually. However, sometimes you need more automation and server-side control of these settings, such as scheduling Remote Config updates or importing configurations from your own proprietary systems and other Firebase projects. Until now the Remote Config REST API was the only way to implement such use cases, which at times could be cumbersome.
As of version 8.13.0, the Admin SDK introduced a Remote Config API that simplifies the process of creating, accessing, and updating your Remote Config templates from your own backend servers and also from managed Cloud environments like Google Cloud Functions.
Let’s go through some examples on how to use the Firebase Admin Node.js SDK to manage your projects’ templates. You can start by creating a fresh Remote Config template for your project, or you can get the current active template and make changes to it. Here's how you can get the current active template in Node.js:
const admin = require('firebase-admin'); admin.initializeApp(); const template = await admin.remoteConfig().getTemplate(); console.log('ETag from server: ' + template.etag);
Each Remote Config template version is associated with an entity tag (ETag) for version control purposes. The Remote Config API uses this ETag to prevent race conditions and concurrent updates to resources. To learn more about ETags, see ETag - HTTP.
Once you have obtained your current active Remote Config template you can start making modifications. Here are some of the operations you can perform using the Admin Node.js SDK:
Let’s go through an example on adding a new Remote Config condition and a parameter to your template.
// add a new condition template.conditions.push({ name: 'android_en', expression: "device.os == 'android' " + "&& device.country in ['us','uk']", tagColor: 'BLUE' as admin.remoteConfig.TagColor, }); // add a new parameter, that references the above condition template.parameters['header_text'] = { defaultValue: { value: 'A Gryffindor must be brave, talented and helpful.' }, conditionalValues: { 'android_en': { value: 'A Droid must be brave, talented and helpful.' }, }, };
The above example creates a new condition named android_en and uses it in a conditional value of the parameter named header_text. Checkout the documentation to learn more about conditional expressions. Keep in mind that the conditions in a template are listed in descending order by priority. A parameter might have several conditional values associated with it. If multiple conditions evaluate to true, the first condition in the list takes precedence.
android_en
header_text
Once you make updates to your template you must publish it to make the new changes effective. After you publish a Remote Config template using the Firebase Admin Node.js SDK, that template becomes the current active version of your project. Before you publish a template you can always validate it to check for any errors.
try { const validatedTemplate = admin.remoteConfig() .validateTemplate(template); const publishedTemplate = admin.remoteConfig() .publishTemplate(validatedTemplate); } catch(err) { console.log('Error publishing the template:', error); }
For more details and code samples on programmatically modifying Remote Config templates, check out the detailed documentation.
In addition to the API for modifying Remote Config templates, the Firebase Admin Node.js SDK provides an API to manage Remote Config template versions. This API supports the following operations.
The listVersions operation allows you to retrieve a list of metadata for all stored versions of your project, sorted in reverse chronological order. Note that the last 300 versions are stored. All versions that correspond to non-active Remote Config templates (that is, all except the template that is being fetched by clients) are also deleted if they are more than 90 days old. Here’s a sample code to fetch a list of template versions.
listVersions
// list all the Remote Config template Versions // Only the last 300 versions are stored. const listVersionsResult = admin.remoteConfig().listVersions(); listVersionsResult.versions.forEach((version) => { console.log('version', JSON.stringify(version)); });
For a complete list of available operations and examples, check out the documentation on programmatically managing Remote Config template versions.
We hope this new addition of the Remote Config API in the Admin Node.js SDK helps with your backend development tasks. Stay tuned for the Remote Config API to be added across other Firebase Admin SDK platforms. We are excited to see how you use the new APIs in your projects! Hop over to our Github Repo to check out the implementation. Help us further improve our SDKs by providing your valuable feedback, reporting issues, and contributions to our codebase.
Most apps that you build with Firebase’s backend services, such as Realtime Database, Cloud Firestore, and Cloud Storage, need some way to sign users in: among other things, this lets you provide a consistent experience across sessions and devices, and lets you set user-specific permissions. Firebase Authentication helps you meet this requirement by providing libraries and services that you can use to quickly build a new sign-in system for your app.
But what if your organization already uses a service such as Okta to handle user identity? With Firebase Custom Authentication, you can use any user identity service (including Okta) to authenticate with Firebase, and this post will show you how.
You’ll learn how to build a Firebase and Okta integration, which will have two components:
By the way, this approach can also be used with some modification for other identity services, such as Auth0, Azure Active Directory, or your own custom system.
Ready to get started? Great! But, before you write any code, you’ll need to set up your Okta and Firebase projects.
First, set up an Okta project on the Okta Developer site:
Set the Base URIs and Login redirect URIs to the location where you plan to host your web frontend (http://localhost:5000 if you’re using the Firebase Hosting emulator) and enable the Authorization Code grant type.
http://localhost:5000
When you’re done, take note of the app's Client ID for later.
Then, set up a Firebase project in the Firebase console:
If you plan to eventually host your web app with Firebase, you can automatically set up Firebase Hosting and simplify configuration by enabling Also set up Firebase Hosting for this app.
Finally, if you plan to deploy your token exchange endpoint as a Cloud Function:
Now that your projects are set up, you’ll write the crucial piece: the token exchange endpoint.
The job of the token exchange endpoint is to take a user’s Okta access token and, if it’s valid, produce a Firebase custom authentication token that represents the same user.
This endpoint needs to be able to verify the authenticity of the Okta access token. To accomplish this, use the Express.js middleware provided in Okta’s developer documentation (reproduced below, with minor modifications):
const OKTA_ORG_URL = // Your Okta org URL const OktaJwtVerifier = require('@okta/jwt-verifier'); const oktaJwtVerifier = new OktaJwtVerifier({ issuer: `${OKTA_ORG_URL}/oauth2/default` }); // Middleware to authenticate requests with an Okta access token. const oktaAuth = async (req, res, next) => { const authHeader = req.headers.authorization || ''; const match = authHeader.match(/Bearer (.+)/); if (!match) { res.status(401); return next('Unauthorized'); } const accessToken = match[1]; try { const jwt = await oktaJwtVerifier.verifyAccessToken( accessToken, 'api://default'); req.jwt = jwt; return next(); // Pass the request on to the main route. } catch (err) { console.log(err.message); res.status(401); return next('Unauthorized'); } }
Any endpoint protected by this middleware will require a valid Okta access token in the Authorization header. If the token is valid, it will insert the decoded token into the request before passing the request along by calling next().
Authorization
next()
Now, you can write the token exchange endpoint:
const express = require('express'); const app = express(); const cors = require('cors')({origin: 'https://YOUR_DOMAIN'}); const firebaseAdmin = require('firebase-admin'); const firebaseApp = firebaseAdmin.initializeApp(); // Get a Firebase custom auth token for the authenticated Okta user. // This endpoint uses the `oktaAuth` middleware defined above to // ensure requests have a valid Okta access token. app.get('/firebaseCustomToken', [cors, oktaAuth], async (req, res) => { const oktaUid = req.jwt.claims.uid; try { const firebaseToken = await firebaseApp.auth().createCustomToken(oktaUid); res.send(firebaseToken); } catch (err) { console.log(err.message); res.status(500).send('Error minting token.'); } });
This endpoint uses the Firebase Admin SDK to mint a Firebase custom authentication token using the user’s Okta UID. When you sign a user in with this token for the first time (on the frontend), Firebase Authentication will add a user record with the same UID to your project.
This process of using an Okta access token to acquire a Firebase custom token is the key idea behind integrating Okta and Firebase. But, let’s go one step further and write a simple web frontend to demonstrate the use of the endpoint.
The demo frontend is a plain HTML and JavaScript web app that uses the Firebase Authentication Web SDK and Okta’s sign-in widget library.
Start with two containers: one for authenticated user content and one for Okta’s sign-in widget:
<div id="authenticated-user-content" hidden> <h2>Authenticated with Firebase</h2> <p id="user-info"></p> <button onclick="firebase.auth().signOut();">Sign out</button> </div> <div id="signin-widget" hidden></div>
Set up a Firebase authentication state listener that shows some user profile information to signed-in users and Okta’s sign-in widget to signed-out users:
const oktaSignIn = new OktaSignIn({ baseUrl: OKTA_ORG_URL, redirectUri: window.location.url, authParams: { display: 'page', }, el: '#signin-widget', }); firebase.auth().onAuthStateChanged((user) => { if (user) { // User is signed in. Display some user profile information. document.getElementById('user-info').innerHTML = `Hi, ${user.displayName}! Your email address is ${user.email} and your UID is ${user.uid}.`; document.getElementById('authenticated-user-content').hidden = false; document.getElementById('signin-widget').hidden = true; } else { // User is signed out. Display the Okta sign-in widget. oktaSignIn.showSignInToGetTokens({ clientId: OKTA_CLIENT_ID, redirectUri: window.location.url, getAccessToken: true, getIdToken: true, scope: 'openid profile email', }); document.getElementById('authenticated-user-content').hidden = true; document.getElementById('signin-widget').hidden = false; } });
When a user signs in with Okta’s widget, their browser briefly redirects to Okta’s authorization server, and then, assuming the user signed in successfully, redirects back to your app with the response.
Use Okta’s sign-in library to get the Okta access token from the response and use the access token to get a Firebase custom token from your token exchange endpoint:
if (oktaSignIn.hasTokensInUrl()) { // Get the access token from Okta. const oktaTokenResponse = await oktaSignIn.authClient.token.parseFromUrl(); const accessToken = oktaTokenResponse.tokens.accessToken.value; // Use the access token to call the firebaseCustomToken endpoint. const firebaseTokenResponse = await fetch(CUSTOM_TOKEN_ENDPOINT, { headers: { 'Authorization': `Bearer ${accessToken}`, } }); const firebaseToken = await firebaseTokenResponse.text(); // (Continued below.) }
And finally, authenticate with Firebase using the custom token:
// (Continued from above.) try { await firebase.auth().signInWithCustomToken(firebaseToken); } catch (err) { console.error('Error signing in with custom token.'); }
When the call to signInWithCustomToken() completes, the auth state listener will detect the change and display the user’s profile information.
signInWithCustomToken()
At this point, the user is authenticated with Firebase and you can use any of Firebase’s authentication-enabled services, such as Realtime Database, Cloud Firestore, and Cloud Storage. See the Security Rules documentation for more information on granting resource access to authenticated users.
For the complete demo app and backend that the code snippets above came from, see the Authenticate with Firebase using Okta sample on GitHub.
As many game developers know, customizing your game for different types of players is a great way to increase player engagement and retention. That’s why Firebase offers products like Analytics, Remote Config, Predictions and A/B Testing that allow you to tailor your app’s content and configuration for different player segments based on profile, past actions and future predicted behavior. For example, you can provide different onboarding flows for players based on their country or simplify a game level for players who are predicted to churn in hopes of keeping them engaged.
One key area for personalization includes monetization, but figuring out the right strategy for the right group of players can be tricky. That’s why PeopleFun, maker of some of the top word games on Android and iOS, turned to Firebase Predictions. They used Predictions in their hit game Wordscapes to create player segments based on predicted behavior and identify players who were unlikely to make in-app purchases in the next seven days. Those players were shown more rewarded video ads, while the players likely to make a purchase were not. This helped PeopleFun achieve the right balance between ads and IAP.
Read more about how PeopleFun used Firebase Predictions to increase lifetime value by up to 5% here, and check out other ways Firebase can help you supercharge your games!
In a competitive app ecosystem, making sure your app doesn’t crash frequently is integral to your app’s success. So with the graduation of Firebase Crashlytics SDK out of Beta, we think it’s a good time to highlight the benefits of integrating Crashlytics into your app. Read on for a refresher on the essential tools that Crashlytics provides to help you debug crashes and get the most out of your crash reports.
Even with access to crash reports, getting to the root cause of a crash can be pretty time consuming. Not only does the Crashlytics dashboard provide a holistic and clear view of what your users are experiencing, but you also get detailed suggestions on what could have caused a fatal error with crash insights.
Crash insights appear on your dashboard next to the crash report and provide additional context by highlighting potential root causes, such as SDK bugs and API misuse, that might be common across multiple apps. This serves as a starting point for investigation, which saves you time and speeds up your workflow.
It can be frustrating to see a user run into a crash that you can’t seem to reproduce on your end. Crashlytics can help with this by allowing you to track the state and sequence of application usage prior to a crash through custom keys and custom logs. Custom keys provide a snapshot of information at one point in time, recording the last known value; custom logs record the events a user went through during their session.
For example, you might want to know how many items a user had in their shopping cart before a crash occurred. By naming a key using a string (e.g. “item purchase count”) and setting the value programmatically, Crashlytics uploads these key/values with the next crash. These keys and values are then visible right next to your stack trace.
Even with custom keys and logs, trying to manually capture every event your user triggers in your app can be daunting. However, if you integrate Crashlytics with Google Analytics, you can automatically capture predefined Google Analytics events, known as breadcrumbs. Breadcrumbs can further enhance the data captured with custom logs, giving you even more information on the cause of a crash.
Just like custom logs and keys, breadcrumbs can be found within your stack trace in the Crashlytics dashboard, and will show the actions a user has taken prior to a crash, as well as the parameters within the event.
For instance, going back to the shopping cart example, breadcrumbs will capture event parameters like product ID, product name, type of currency used, quantity of items in the cart, etc. Here is a full list of the automatically collected events that Google Analytics breadcrumbs captures.
You never want to miss a critical user issue, but it can be tough to stay on top of crash reports around-the-clock. Using Crashlytics alerts, you can configure real-time alerts by three different levels of your app’s stability. Velocity alerts, considered high priority, are sent when an issue goes over a certain threshold within your user base. Regression alerts are sent when a previously closed issue has recurred in a new version of your app, typically medium priority. New issue alerts are sent when a new issue has occurred, and are generally low priority.
You can customize these alerts in the Crashlytics console, and receive them via Slack, PagerDuty, Jira, or email.
Not only can you view your crashes in the Crashlytics dashboard, but you can also export all Crashlytics data to BigQuery. This enables you to filter and segment your user data for further analysis. For example, you can figure out emerging crashes in new code, or see the top Issues for today so you can 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 maneuver BigQuery data sets.
And recently we also launched the ability to export this data in real time, enabling you to power custom workflows and alerts based on real-time data.
These are just a few examples of the exciting things you can do with Crashlytics to keep your apps stable and your users happy. As always, if you need help getting started please feel free to reach out to us directly through our Community Slack or via Stack Overflow!
Our team is driven by the belief that apps have drastically improved the way we live, work, learn, and socialize, keeping us connected to each other and plugged into the information we need. Now more than ever, we understand the importance of supporting our developer community by ensuring you have the technology and resources you need to keep your business up and running. Whether you’re a high-growth startup or a global enterprise, we’re still here to help you build and operate your app.
TensorFlow Lite is the official framework for running TensorFlow models on mobile and edge devices. It is used in many of Google’s major mobile apps, as well as applications by third-party developers. When deploying TensorFlow Lite models in production, you may come across situations where you need some support features that are not provided out-of-the-box by the framework, such as:
In these cases, instead of building your own solutions, you can leverage Firebase to quickly implement these features in just a few lines of code.
Firebase is the comprehensive app development platform by Google, which provides you infrastructure and libraries to make app development easier for both Android and iOS. Firebase Machine Learning offers multiple solutions for using machine learning in mobile applications.
In this blog post, we show you how to leverage Firebase to enhance your deployment of TensorFlow Lite models in production. We also have codelabs for both Android and iOS to show you step-by-step of how to integrate the Firebase features into your TensorFlow Lite app.
You may want to deploy your machine learning model over-the-air to your users instead of bundling it into your app binary. For example, the machine learning team who builds the model has a different release cycle with the mobile app team and they want to release new models independently with the mobile app release. In another example, you may want to lazy-load machine learning models, to save device storage for users who don’t need the ML-powered feature and reduce your app size for faster download from Play Store and App Store.
With Firebase Machine Learning, you can deploy models instantly. You can upload your TensorFlow Lite model to Firebase from the Firebase Console.
You can also upload your model to Firebase using the Firebase ML Model Management API. This is especially useful when you have a machine learning pipeline that automatically retrains models with new data and uploads them directly to Firebase. Here is a code snippet in Python to upload a TensorFlow Lite model to Firebase ML.
# Load a tflite file and upload it to Cloud Storage. source = ml.TFLiteGCSModelSource.from_tflite_model_file('example.tflite') # Create the model object. tflite_format = ml.TFLiteFormat(tflite_source=source) model = ml.Model(display_name="example_model", model_format=tflite_format) # Add the model to your Firebase project and publish it. new_model = ml.create_model(model) ml.publish_model(new_model.model_id)
Once your TensorFlow Lite model has been uploaded to Firebase, you can download it in your mobile app at any time and initialize a TensorFlow Lite interpreter with the downloaded model. Here is how you do it on Android.
val remoteModel = FirebaseCustomRemoteModel.Builder("example_model").build() // Get the last/cached model file. FirebaseModelManager.getInstance().getLatestModelFile(remoteModel) .addOnCompleteListener { task -> val modelFile = task.result if (modelFile != null) { // Initialize a TF Lite interpreter with the downloaded model. interpreter = Interpreter(modelFile) } }
There is a diverse range of mobile devices available in the market nowadays, from flagship devices with powerful chips optimized to run machine learning models to cheap devices with low-end CPUs. Therefore, your model inference speed on your users’ devices may vary largely across your user base, leaving you wondering if your model is too slow or even unusable for some of your users with low-end devices.
You can use Performance Monitoring to measure how long your model inference takes across all of your user devices. As it is impractical to have all devices available in the market for testing in advance, the best way to find out about your model performance in production is to directly measure it on user devices. Firebase Performance Monitoring is a general purpose tool for measuring performance of mobile apps, so you also can measure any arbitrary process in your app, such as pre-processing or post-processing code. Here is how you do it on Android.
// Initialize a Firebase Performance Monitoring trace val modelInferenceTrace = firebasePerformance.newTrace("model_inference") // Run inference with TensorFlow Lite interpreter.run(...) // End the Firebase Performance Monitoring trace modelInferenceTrace.stop()
Performance data measured on each user device is uploaded to Firebase server and aggregated to provide a big picture of your model performance across your user base. From the Firebase console, you can easily identify devices that demonstrate slow inference, or see how inference speed differs between OS versions.
When you iterate on your machine learning model and come up with an improved model, you may feel very eager to release it to a production right away. However, it is not rare that a model may perform well on test data but fail badly in production. Therefore, the best practice is to roll out your model to a smaller set of users, A/B test it with the original model and closely monitor how it affects your important business metrics before releasing it to all of your users.
Firebase A/B Testing enables you to run this kind of A/B testing with minimal effort. The steps required are:
Here is an example of setting up an A/B test with TensorFlow Lite models. We deliver each of two versions of our model to 50% of our user base and with the goal of optimizing for multiple metrics.
Then we change our app to fetch the model name from Firebase and use it to download the TensorFlow Lite model assigned to each device.
val remoteConfig = Firebase.remoteConfig remoteConfig.fetchAndActivate() .addOnCompleteListener(this) { task -> // Get the model name from Firebase Remote Config val modelName = remoteConfig["model_name"].asString() // Download the model from Firebase ML val remoteModel = FirebaseCustomRemoteModel.Builder(modelName).build() val manager = FirebaseModelManager.getInstance() manager.download(remoteModel).addOnCompleteListener { // Initialize a TF Lite interpreter with the downloaded model interpreter = Interpreter(modelFile) } }
After you have started the A/B test, Firebase will automatically aggregate the metrics on how your users react to different versions of your model and show you which version performs better. Once you are confident with the A/B test result, you can roll out the better version to all of your users with just one click.
Check out this codelab (Android version or iOS version) to learn step by step how to integrate these Firebase features into your app. It starts with an app that uses a TensorFlow Lite model to recognize handwritten digits and show you:
Amy Jang, Ibrahim Ulukaya, Justin Hong, Morgan Chen, Sachin Kotwani
If you're using Cloud Firestore or Cloud Storage for Firebase, you're also using Security Rules. (If you're using the default rules instead of tailoring them to your app, this is where to start!) We're excited to announce that in the last few months we've released some substantial improvements to the tools for writing and debugging Rules, improvements to the Rules language itself, and increases to the size limits for Rules!. These are a few of the great new features. Check out the Security Rules Release Notes for a comprehensive list of everything we've released.
We've released several improvements to make the rules language more expressive and succinct. One particularly verbose pattern was comparing the new values of a document to existing values. The new Set type available in Rules is purpose-built for these comparisons, and also has methods for functionality you'd expect for a Set, like getting the intersection, union, or difference between Sets. For example:
Set type
Allow a user to create a document if the document has required and optional fields, but not others:
allow create: if (request.resource.data.keys().toSet() .hasOnly(["required","and","optional","keys"])
Sets come with == and in operators and hasAll, hasAny, hasOnly, difference, intersection, union, and size methods.
==
in
hasAll
hasAny
hasOnly
difference
intersection
union
size
Sets are most useful in conjunction with the Map class, and because the request and resource objects are both structured as maps, you're probably already familiar with it. Map recently got a few new methods, diff and get, that will hopefully open the door to more concise rules for everyone. Here's how they work:
Map
request
resource
diff
get
Map.diff() is called on one map, and takes the second map as an argument: map1.diff(map2). It returns a MapDiff object, and all of the MapDiff methods, like addedKeys, changedKeys, or affectedKeys return a Set object.
map1.diff(map2)
addedKeys
changedKeys
affectedKeys
Set
Map.diff() can solve some verbose patterns like checking which fields changed before and after a request. For example, this rule allows an update if the "maxLevel" field was the only field changed:
allow update: if request.resource.data.diff(resource.data).changedKeys().hasOnly(["maxLevel"]);
In the next example, posts have a field indicating the user role required to modify the post. We'll use Map.get() to get the "roleToEdit" field. If the document doesn't have the field, it will default to the "admin" role. Then we'll compare that to the role that's on the user's custom claims:
"roleToEdit"
"admin"
allow update, delete: if resource.data.get("roleToEdit", "admin") == request.auth.token.role;
Keep in mind that because Sets are not ordered but Lists are. You can convert a List to a Set, but you can't convert a Set to a List.
Local variables have been one of the most requested features in Rules, and they're now available within functions. You can declare a variable using the keyword let, and you can have up to 10 local variables per function.
let
Say you're commonly checking that a user meets the same three conditions before granting access: that they're an owner of the product or an admin user, that they successfully answered a challenge question, and that they meet the karma threshold.
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /products/{product} { allow read: if true; allow write: if (exists(/databases/$(database)/documents/admins/$(request.auth.uid)) || exists(/databases/$(database)/documents/product/owner/$(request.auth.uid))) && get(/databases/$(database)/documents/users/$(request.auth.uid)) .data.passChallenge == true && get(/databases/$(database)/documents/users/$(request.auth.uid)) .data.karma > 5; } match /categories/{category} { allow read: if true; allow write: if (exists(/databases/$(database)/documents/admins/$(request.auth.uid)) || exists(/databases/$(database)/documents/product/owner/$(request.auth.uid))) && get(/databases/$(database)/documents/users/$(request.auth.uid)) .data.passChallenge == true && get(/databases/$(database)/documents/users/$(request.auth.uid)) .data.karma > 5; } match /brands/{brand} { allow read, write: if (exists(/databases/$(database)/documents/admins/$(request.auth.uid)) || exists(/databases/$(database)/documents/product/owner/$(request.auth.uid))) && get(/databases/$(database)/documents/users/$(request.auth.uid)) .data.passChallenge == true && get(/databases/$(database)/documents/users/$(request.auth.uid)) .data.karma > 5; } } }
Those conditions, along with the paths I'm using for lookups can all now become variables in a function, which creates more readable rules:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { function privilegedAccess(uid, product) { let adminDatabasePath = /databases/$(database)/documents/admins/$(uid); let userDatabasePath = /databases/$(database)/documents/users/$(uid); let ownerDatabasePath = /databases/$(database)/documents/$(product)/owner/$(uid); let isOwnerOrAdmin = exists(adminDatabasePath) || exists(ownerDatabasePath); let meetsChallenge = get(userDatabasePath).data.get("passChallenge", false) == true; let meetsKarmaThreshold = get(userDatabasePath).data.get("karma", 1) > 5; return isOwnerOrAdmin && meetsChallenge && meetsKarmaThreshold; } match /products/{product} { allow read: if true; allow write: if privilegedAccess(); } match /categories/{category} { allow read: if true; allow write: if privilegedAccess(); } match /brands/{brand} { allow read, write: if privilegedAccess(); } } }
You can see at a glance that the same conditions grant access to write to documents in the three different collections.
The updated version also uses map.get() to fetch the karma and passChallenge fields from the user data, which helps keep the new function concise. In this example, if there is no karma field for a user, then the get returns false. Keep in mind that Map.get() fetches a specific field, and is separate from the DocumentReference.get() that fetches a document.
map.get()
karma
passChallenge
Map.get()
DocumentReference.get()
This is the first time we've introduced an if/else control flow, and we hope it will make rules smoother and more powerful.
if/else
Here's an example of using a ternary operator to specify complex conditions for a write. A user can update a document in two cases: first, if they're an admin user, they need to either set the field overrideReason or approvedBy. Second, if they're not an admin user, then the update must include all the required fields:
overrideReason
approvedBy
allow update: if isAdminUser(request.auth.uid) ? request.resource.data.keys().toSet().hasAny(["overrideReason", "approvedBy"]) : request.resource.data.keys().toSet().hasAll(["all", "the", "required", "fields"])
It was possible to express this before the ternary, but this is a much more concise expression.
And finally, here's a feature for those of you with longer rules. Until now, rules files had to be smaller than 64 KB. (To be more specific, the compiled AST of the rules file had to be smaller than 64 KB, and you wouldn't know you were within the limit until you tried to deploy the rules.) This limit was holding some developers back, and once you reached the limit, you had to start making tradeoffs in your rules. We definitely wanted to fix this.
Since this is one of the limits that helps rules return a decision in nanoseconds, we wanted to find a way to increase the limit without sacrificing performance. We optimized how we compile and store the Rules file, and we were able to quadruple the limit to 256 KB!
The limits on rules are in place to keep rules fast enough to return a decision in nanoseconds, but we work hard to keep them workable. Let us know if you start to outgrow any of them
All of these features are informed by the feedback we hear from you about what's great, what's hard, and what's confusing about Firestore Security Rules, so keep letting us know what you think!