Hey there, iOS Developers!
We wanted to let you know that Firebase version 3.6 is now available for iOS. This contains a number of important bug fixes and features needed for iOS 10 support, and we encourage you to run a pod update (or manually update your frameworks) and recompile your projects at your earliest convenience.
pod update
If you want to see a full list of fixes and improvements, you can review the release notes, but here's a quick summary of what's new.
Firebase Cloud Messaging now has support for the new iOS 10 user notifications. If your app is running on iOS 10, you can handle incoming notifications using the userNotificationCenter:willPresentNotification: withCompletionHandler method. And don't worry -- if your app only has the older application:didReceiveRemoteNotification: completionHandler methods supported, APNs will call those instead if it can't find the newer ones. Need more info? Refer to the updated FCM documentation for more information.
userNotificationCenter:willPresentNotification: withCompletionHandler
application:didReceiveRemoteNotification: completionHandler
With the iOS 10 update, Apple made a number of changes to their App Store review guidelines. The latest version of Firebase has made several changes in response to these new guidelines. Most importantly, you should no longer encounter iTunes Connect errors asking you to provide text for things like NSCalendarsUsageDescription and NSBluetoothPeripheralUsageDescription.
NSCalendarsUsageDescription
NSBluetoothPeripheralUsageDescription
One consequence of following these guidelines is that we have removed the technology which up until recently gave you the ability to measure iOS Search app install ads from Safari.
For those of you who are using Firebase Invites, you will need to supply some content for NSContactsUsageDescription in your plist file. Firebase Invites uses this contact information to populate the list of friends that your user might want to send an invitation to.
NSContactsUsageDescription
plist
Of course, this is an ongoing process. We will monitor the impact of these changes closely, and publish further updates if it ever becomes necessary.
You may recall in a recent blog post that Firebase Auth was encountering errors in Xcode 8 due to it not being able to write values to the keychain in the simulator. While that issue still exists, we have developed a workaround where we use NSUserDefaults in the simulator, and continue to use the keychain on the device. This means you can now develop and test out Firebase Auth in the simulator and everything should be working again.
NSUserDefaults
You found bugs; we fixed 'em! Please continue to report any issues or feature requests you might have to our online form, and we'll make sure they get handled appropriately.
And if you have any questions, you can always ask them on Stack Overflow with the Firebase tag, or send them to our Google group.
Thanks again for being a Firebase developer! Now go forth and update your apps!
Sometimes, when using the Firebase client APIs for Android, it's required that Firebase perform some work at the request of the developer in an asynchronous fashion. Perhaps some requested data is not immediately available, or work needs to be queued for eventual execution. When we say some work must be done asynchronously in an app, that means the work needs to happen at the same time as the app performs its primary job of rendering the app’s views, but not get in the way of that work. To perform this asynchronous work correctly in Android apps, the work can't occupy time on the Android main thread, otherwise the app may delay rendering of some frames, causing "jank" in the user experience, or worse, the dreaded ANR! Typical examples of work that can cause delays are network requests, reading and writing files, and lengthy computations. In general, we call this blocking work, and we never want to block the main thread!
When a developer uses a Firebase API to request work that would normally block the main thread, the API needs to arrange that work to run on a different thread, in order to avoid jank and ANRs. Upon completion, the results of that work sometimes have to make it back to the main thread in order to safely update views.
That's what the Play services Task API is for. The goal of the Task API is to provide an easy, lightweight, and Android-aware framework for Firebase (and Play services) client APIs to perform work asynchronously. It was introduced in Play services version 9.0.0 along with Firebase. If you've been using Firebase features in your app, it's possible that you may have been using the Task API without even realizing it! So, what I'd like to do in this blog series is unpack some of the ways the Firebase APIs make use of Tasks, and discuss some patterns for advanced use.
Before we begin, it's important to know that the Task API isn't a full replacement for other threading techniques on Android. The Android team has put together some great content that describe other tools for threading, such as Services, Loaders, and Handlers. There's also a whole season of Application Performance Patterns on YouTube that discusses your options. Some developers even opt into third party libraries that will help you with your threading in Android apps. So, it's up to you to learn about those and determine which solution is the best for your particular threading needs. Firebase APIs uniformly use Tasks to manage threaded work, and you can use those in conjunction with other strategies as you see fit.
If you're using Firebase Storage, you'll definitely encounter Tasks at some point. Here's a straightforward example of fetching metadata about a file that's already uploaded to Storage, taken directly from the documentation for file metadata:
// Create a storage reference from our app StorageReference storageRef = storage.getReferenceFromUrl("gs://"); // Get reference to the file StorageReference forestRef = storageRef.child("images/forest.jpg"); forestRef.getMetadata().addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(StorageMetadata storageMetadata) { // Metadata now contains the metadata for 'images/forest.jpg' } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception exception) { // Uh-oh, an error occurred! } });
Even though we never see a "Task" anywhere in this code, there is actually a Task in play here. The last part of the above code could be rewritten equivalently like this:
Task task = forestRef.getMetadata(); task.addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(StorageMetadata storageMetadata) { // Metadata now contains the metadata for 'images/forest.jpg' } }); task.addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception exception) { // Uh-oh, an error occurred! } });
Ah, it looks like there was a Task hidden in that code after all!
With the sample code rewritten above, it's now more clear how a Task is being used to obtain file metadata. The getMetadata() method on the StorageReference has to assume that the file metadata is not immediately available, so it will make a network request to get a hold of it. So, in order to avoid blocking the calling thread on that network access, getMetadata() returns a Task that can be listened to for eventual success or failure. The API then arranges to perform the request on a thread it controls. The details of this threading are hidden by the API, but the returned Task is used to indicate when the results become available. The returned Task then guarantees that any added listeners will be invoked upon completion. This form of API to manage the results of asynchronous work is sometimes called a Promise in other programming environments.
Notice here that the returned Task is parameterized by the type StorageMetadata, and that's also the type of object that gets passed to onSuccess() in the OnSuccessListener. In fact, all Tasks must declare a generic type in this way to indicate the type of data they generate, and the OnSuccessListener must share that generic type. Also, when an error occurs, an Exception is passed to onFailure() in the OnFailureListener, which will probably be the specific exception that caused the failure. If you want to know more about that Exception, you may have to check its type in order to safely cast it to the expected type.
The last thing to know about this code is that the listeners will be called on the main thread. The Task API arranges for this to happen automatically. So, if you want to do something in response to the StorageMetadata becoming available that must happen on the main thread, you can do that right there in the listener method. (But remember that you still shouldn’t be doing any blocking work in that listener on the main thread!) You have some options about how these listeners work, and I'll say more in a future post about your alternatives.
Some Firebase features provide other APIs that accept listeners that are not associated with Tasks. For example, if you're using Firebase Authentication, you've almost certainly registered a listener to find out when the user successfully logs in or out of your app:
private FirebaseAuth auth = FirebaseAuth.getInstance(); private FirebaseAuth.AuthStateListener authStateListener = new FirebaseAuth.AuthStateListener() { @Override public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { // Welcome! Or goodbye? } }; @Override protected void onStart() { super.onStart(); auth.addAuthStateListener(authStateListener); } @Override protected void onStop() { super.onStop(); auth.removeAuthStateListener(authStateListener); }
The FirebaseAuth client API makes two main guarantees for you here when you add a listener with addAuthStateListener(). First, it will call your listener immediately with the currently known login state for the user. Then, it will call the listener again with all subsequent changes to the user's login state, for as long as the listener is added to the FirebaseAuth object. This behavior is very different than the way Tasks work!
Tasks only call any added listener at most once, and only after the result is available. Also, the Task will invoke a listener immediately if the result was already available before that listener was added. The Task object effectively remembers the final result object and continues to deal it out to any future listeners, until it has no more listeners and is eventually garbage collected. So if you're using a Firebase API that works with listeners on something other than a Task object, be sure to understand its own behaviors and guarantees. Don't assume that all Firebase listeners behave like Task listeners!
Consider the active lifetime of your added Task listeners. There are two things that can go wrong if you don’t do this. First, you can cause an Activity leak if the Task continues beyond the lifetime of an Activity and its Views that are being referenced by an added listener. Second, the listener might execute when it’s no longer needed, causing wasteful work to be done, and possibly doing things that access Activity state when it’s no longer valid. The next part of this blog series will go into these issues in more detail, and how to avoid them.
We've taken a brief look at the Play Services Task API and uncovered its (sometimes hidden!) use in some Firebase sample code. Tasks are the way that Firebase lets you respond to work that has an unknown duration and must be executed off the main thread. Tasks can also arrange for listeners to be executed back on the main thread to deal with the result of the work. However, we've only just scratched the surface of what Tasks can do for you. Next time, we'll look at the variations on Task listeners so you can decide which one best suits your use cases.
If you have any questions, consider using Twitter with the #AskFirebase hashtag or the firebase-talk Google Group. We also have a dedicated Firebase Slack channel. And you can follow me @CodingDoug on Twitter.
In my last post I covered the five components under Pirate Metrics - Acquisition, Activation, Retention, Referral and Revenue - and their importance to the success of a product. In this post, I will focus on the first metric, i.e Acquisition, and demonstrate how one can use the Firebase suite to not only track, but also improve it.
At Google, our key offering over the years for acquisition has been Adwords. Through an Adwords campaign, you can already reach out to users on not only search results, but also places like YouTube and Google Play. With the new Firebase integration for Adwords, you can turbo charge your acquisition workflow even further.
Firstly, you can automatically ensure if your campaign are getting the right users by tracking the app open events being fired by them.
Say you have created a game, and have multiple campaigns running. Through this integration, you not only know which campaigns are bringing you more users at better rates, but also which ones are providing more engaged users.
You can also attribute acquisitions from more than 30 other networks, and track the campaign performances in Firebase Analytics directly. And, as you’d expect, you can segment users acquired from these different sources into dedicated Audiences.
Additionally, you can also specify which of your in-app events are important, and Adwords would automatically target those users likely to perform them. Continuing with the example of the game from before, let's assume your game has both single and multiplayer modes. Simply by letting Adwords know of the event for starting a multiplayer game, you could increase the likelihood of acquiring users who want to play multiplayer.
And, lastly, you can target the audiences you have created in Firebase Analytics. This can be tremendously powerful for retargeting, such as bringing back users who might quit after struggling at a certain level with a deal for a special power up.
The Firebase integration with Adwords helps you get the best bang for your buck. Do check out the official documentation for complete details.
Besides Adwords, another nifty tool that we provide as part of Firebase is Dynamic Links. Dynamic Links allows you to create a single URL to share with potential users, who would be redirected to the appropriate stores to download them on either Android or iOS. You can also add some custom data to a link, which will survive the app installation process. You could use this to considerably improve your acquisition from channels such as social media.
For example, say you want to highlight a product that is available for sale on your E-commerce application. Simply create a Dynamic Link, add some information such as an ID that your app can then consume and deeplink straight to the product. Users who have the app would be taken to the product page. Those who don’t would first be taken to either the Play Store or App Store, and can then be taken straight to the product when they open your application for the first time.
We’ll be covering more about Dynamic Links again in a future post, but do go ahead and check out the documentation for yourself as well.
In our next post, we will be covering Activation.
We’re happy to see many developers experimenting with Firebase! Some of you have been transitioning your experiments to production, and that’s raised some questions about how to manage the builds of your Android projects. The most common question goes something like this: "How do I keep my everyday development data separate from my release data?"
We'd all like our analytics to reflect only the actual usage of the app (as opposed to artificial usage during development). Also, it's not useful to see crashes from moment-to-moment development interleaved with those from our publicly released versions. On top of that, as your team grows and your app becomes more complex, you may want to separate each team member’s working space into separate sandboxes so that their work won’t collide with each other.
So, let’s explore some ways to configure your project to best handle these cases. For Android app builds, the preferred path is to take advantage of some configuration features of the Android Gradle plugin. These can be applied alongside some configurations in the Firebase Console.
But before I discuss these configurations, let's get some terminology settled! Here are some terms we'll use:
Firebase project
Firebase app
Android Gradle build type
Android Gradle build flavor
Android Gradle build variant
Android Application ID
The key concept, for effective configuration of an app with Firebase, is to assign a distinct application ID to each build variant of an app that requires its own collection of data. This is something you do in your app's build.gradle first, then mirror in the Firebase Console. But first, to make a decision about configuration that's best for your app, there are some more things you need to know about how the different Firebase features work between your Firebase projects and apps.
Some Firebase features share their data between all the apps (Android, iOS, and web) within the same project. You could say that the data for these features are "scoped" to an entire Firebase project:
Some Firebase features have independent data for all the apps within the same project. You could say that these features are scoped to the individual apps in your Firebase project:
You'll notice that the dashboards for both Analytics and Crash Reporting have an app selector near the top of their dashboards that let you select the individual app (of all those created in the project) whose data you want to view.
Some Firebase features have a hybrid scope, where any number of apps may be affected by a particular operation:
Firebase Test Lab for Android has its own special case because it requires a project with billing enabled, but it can be used with any APK without any constraint in a single project. So, if you want to develop with Firebase on a free plan, but test the APK using Test Lab on a paid plan, it's recommended to create a whole new project and enable billing just for use of Test Lab. You can test any app in this project, with or without Firebase integrated.
Now, this is a all good to knowledge to have, but why don't we make this more practical with some actual examples? I'll share some recipes for configuration next. The best case for your situation might be one of these exactly, or some hybrid.
Let's say you're an individual developer or on a small team, your app is relatively simple, and you just need to separate your analytics and crash reports between your daily debug and published release builds. In this case, it would suffice to configure your app to have a different application ID for debug and release. Here's a bit of a Gradle configuration that might help:
defaultConfig { applicationId "com.company.project" // etc... } buildTypes { debug { applicationIdSuffix ".debug" } release { // etc... } }
Here, the application ID is "com.company.project", which gets applied to the release build. But the application ID for the debug build becomes "com.company.project.debug". You don't have to use a suffix like this - instead you could specify a whole new value with applicationId.
Then, in the Firebase Console, you would create a single project, and within that project, create two apps, one for each build type. The debug app would use the application ID "com.company.project.debug", and the release app would use "com.company.project". The SHA-1 hashes would also have to reflect the different keys used to sign each build, if you're using Firebase features that require it.
After both apps are created, download a google-services.json file from the console and place it into your app. If you look inside that file, you'll notice that both apps will appear in there. The Google Services plugin will figure out which set of configurations to use during the build of each variant.
"client_info": { "mobilesdk_app_id": "...", "android_client_info": { "package_name": "com.company.project.debug" } }, "client_info": { "mobilesdk_app_id": "...", "android_client_info": { "package_name": "com.company.project" }
google-services.json will contain info for all Android apps in a project.
It's important to know that if this project is on a billing plan, you'll be billed for all bandwidth and storage generated by both apps. So if you're pulling lots of data during development, that may result in additional charges. Be sure to understand the [pricing plans] to plan for this so you're not surprised by the bill.
It's also important to note that, with this configuration, you will be working against all the same data during development as your active users on your fully released app. This may not be the safest thing, if you intend to disrupt your Realtime Database data or experiment with Remote Config values during development!
The prior recipe of doing development against your live data may be problematic. If you have a large team with lots of people making unsafe updates to the data, or you generally want to prevent the risk of corrupting production data, you'll need to set up multiple projects to isolate development data from production data. In fact, you could have everyone on the team use their own individual "sandbox" projects on the free tier so they can experiment safely without affecting others or incurring any billing.
To set this up, you don't really need to do anything special in your build.gradle. Everyone can use the same application ID to create an app in their sandbox project. However, they'll each need their own unique debug key to sign with. The Android SDK tools create a unique debug key for each user of the SDK, so normally that shouldn't be a problem. But it should be known that the Firebase Console will not allow an app to be created that has a duplicate pair of application ID and SHA-1 key as any other app in any project in any account. So if your team members were sharing a debug key, that won't work with this setup.
This arrangement is great to keep everyone isolated, but there's one caveat. Since all the developers will be creating their own project, they may also have to duplicate some configurations to make the project work correctly. For example, the database for a new project may need to be bootstrapped with some useful data. And the correct security rules should be duplicated. Remote Configs may need to created with appropriate values. Authentication may need to be configured as well. And, of course, every developer will need to use the google-services.json file generated for their own project, and should not be checked into source control, in order to avoid conflicts between team members.
If you have a situation where you need data isolation between different environments, the best way to set that up is similar to the large team setup above. You'll definitely need to create different projects for each environment. They can all be owned by the same account or by different accounts.
To make it easy to select the environment to build for, you can take advantage of build flavors to configure each version of the app. For example, if you need isolation for development, QA, and production, you could define three build flavors in the productFlavors block, which goes next to the buildTypes block in the app’s build.gradle:
productFlavors { dev { } qa { } prod { } }
Here, we're not indicating that there's anything different between the variants except that they exist separately. They'll all have the same application ID, which is OK. Or you could assign them distinct IDs if that helps your situation. In either case, you'll need to use a flavor-specific directory to contain the google-services.json file from each project. By default, the Android Gradle plugin recognizes the following convention for organizing the directories for each flavor as defined above:
app/ src/ main/ dev/ google-services.json (for dev only) qa/ google-services.json (for qa only) prod/ google-services.json (for prod only)
Notice that each named flavor becomes the name of a directory that sits in src adjacent to the main directory where your project code typically lives. With this structure, you can drop the google-services.json for each project directly into its dedicated directory. Now, if you want to build the “dev” flavor of the app, you can select "devDebug" from the build variants window in Android Studio, or target the variant's build task "assembleDevDebug" on the Gradle command line.
If you have an unusual situation with your app build that isn't really helped by the information here, please don't hesitate to ask a question at the firebase-talk Google Group. And, for more urgent support matters, please file an issue on the Firebase Support site. And please follow me on Twitter as CodingDoug!
Generating insights from data and acting upon it appropriately can often make or break a company. In the past few years, the field has grown leaps and bounds with a lot more app developers tracking a lot more events. Yet, truth be told, it’s easy to get overloaded with data and, hence, make it tougher for yourself to find what you were looking for.
So, what metrics should you be tracking to help improve your product?
Analytics is a vast field, with several popular practices and frameworks being employed across the world in every industry. One of the most popular frameworks, particularly in the tech products sector, is Pirate Metrics. The term was coined by Dave McClure, founder of 500 Startups, and is very popular amongst product managers and growth hackers across the world for how it simplifies and breaks down a product’s user lifecycle.
In this post, we’ll cover the five metrics that are collectively called Pirate Metrics and understand their importance. In following posts in the series, we will see how we can use Firebase products to boost these metrics and, hence, build more engaging products.
: The first element in user lifecycle of any product is the acquisition, meaning user install your app. Users are acquired in many different ways, either organically, such as through social media communications, App Store optimization, search, news, content marketing etc., or inorganically which is usually ads.
Congratulations, you have acquired a user! However, your job is less than half done and there is still a need to convince the user that the app is worth keeping around. To give yourself the best chance, you want to activate the user by getting them to experience what makes your app special. For example, a game might want the user to go through a training level or a photo filter application might want the user to experiment with any one image.
The biggest challenge most products face is to retain the users that they acquired. A typical person only uses about 26% of their installed apps daily. People tend to either uninstall or simply forget about the applications they downloaded after the first few days. The goal for any new healthy product is to find a strategy to retain them, and have them keep coming back.
You also want your users to be your biggest advocates. No acquisition strategy is as powerful as an organic, referral campaign. If users love your product, they genuinely want their friends to try it out as well, and you want to really encourage your users to do so, and reduce any friction that might stop a user.
Ultimately, you want to monetize your app in order to grow your company and reach more users.
These five stages - Acquisition, Activation, Retention, Referral, and Revenue are collectively called Pirate Metrics. These are fairly general and can be applied to most digital products. Product managers use a wide variety of tools to track these different metrics. However, with the new Firebase, not only can you track these in one single place, you can also use the suite of different tools to boost them.
We’ll see how in the coming posts. Stay tuned!