Hello Firebase Developers!
We recently launched a major refresh for the Remote Config SDK in our v2 release, and it includes a few changes that will help you keep your app responsive and snappy.
The tl,dr; is that in the v2 SDK we’ve improved Remote Config along the following themes:
Read on for more details on each of these new updates!
One of the biggest requests we’ve had is to add non-blocking initialization and fetch calls for Remote Config params, and for good reason.
Let’s say you’re a developer for a travel app, and you want to show a travel deal based on whether a customer is predicted to spend on hotels at some destination (possibly using Firebase Predictions). With Remote Config, you can surface such travel deals to just those users. Now, let’s say that these deals are surfaced alongside the main parts of your app, where your users find and book travel. What you don’t want is for your users to have to wait for these main parts of your app to get blocked on Remote Config initialization for those travel deals. With the v1 SDK, unless you were very careful about where you placed your Remote Config initialization call, your users would have to wait for Remote Config initialization before being able to use the main parts of the app. In the v2 SDK, this is no longer the case.
By introducing non-blocking initialization, it is now possible to load Remote Config without affecting the load time of the rest of the app. This can improve startup times across the board, but is especially important for users who are in regions of limited or slow network connectivity.
We’ve also added a new convenience method in the V2 SDKs to both fetch and activate your Remote Config values in a single call, which streamlines the activation code flow. Here are how the new calls look for fetching and activating Remote Config parameters before and after the v2 SDK updates:
iOS:
// BLOCKING remoteConfig = RemoteConfig.remoteConfig() // Fetch with a completion handler. remoteConfig.fetch { status, error in if status == .success { remoteConfig.activateFetched() let value = remoteConfig[“myKey”]; } }
// Non-blocking initialization. remoteConfig = RemoteConfig.remoteConfig() remoteConfig.fetchAndActivate { (status, error) in if status == .successFetchedFromRemote || status ==.successUsingPreFetchedData { let value = remoteConfig[“myKey”]; } }
Android:
// BLOCKING FirebaseRemoteConfig frc = FirebaseRemoteConfig.getInstance(); frc.fetch() .addOnSuccessListener(new …<>() { frc.activateFetched(); readFrcValues(); }); void readFrcValues() { value = frc.getInteger(“myKey”); }
// Non-blocking initialization. // Loads values from disk asynchronously. FirebaseRemoteConfig frc = FirebaseRemoteConfig.getInstance(); frc.fetchAndActivate().addOnSuccessListener((unusedVoid) -> readFrcValues()); void readFrcValues() { value = frc.getInteger(“myKey”); }
Another important update for smarter fetching is the introduction of the new FetchTimeoutInSeconds and MinimumFetchIntervalInSeconds parameters, which can be used together to provide smarter fetching policies for your app.
The FetchTimeoutInSeconds parameter is useful when you don’t want your application to wait longer than X seconds to fetch new Remote Config values. It sounds like a small change, but this can actually make a considerable difference in how you build a responsive app for your users.
For example, let’s go back to the travel app. The travel deals portion of the app is conditioned on Remote Config values, but this is for a non-critical part of the app. It doesn’t get in the way of the main booking flow in the app, so it’s not as important if the travel deals load up and show immediately after the user opens the app.
Now let’s say we’ve designed a new booking flow for our app, and want to roll it out to a test group to get some feedback. In this scenario, users will need to wait for a network roundtrip to the Remote Config backend to determine if they’ll get the new booking interface for the test group, or the current booking interface instead.
Waiting for one roundtrip for the interface to load isn’t generally a huge deal, but what if our user is in an area of limited or spotty connectivity? In such cases, the reliability or availability of network infrastructure can dictate the user experience we can provide.
This situation is where being able to set the FetchTimeoutInSeconds becomes really useful. Let’s say we don’t want the test group to experience any Remote Config fetch delays greater than three seconds. Well, using this parameter we can specify exactly that. So if our user does happen to be in an area with spotty network connectivity, we can still ensure they can have a great experience by falling back to either the previously fetched values or the default values.
The new MinimumFetchIntervalInSeconds parameter, as the name implies, allows you to set the minimum interval for which you want to check for any new Remote Config parameter values.
The new parameter name better describes how Remote Config handles retrieving new parameter values from the RC backend over the previous caching terminology, but does not change how Remote Config handles caching. If the default minimum fetch interval of 12 hours is too long for your app’s needs, you can adjust it to a value that’s more appropriate for your app, such as once per hour.
Keep in mind that going beyond that might cause your app to run into rate limits. In case you do hit a rate limit, though, the new error response codes can help deal with that (read the section below for more details).
Setting up these new parameters is straightforward. On Android, you can set both the minimum fetch interval and the fetch timeout parameters directly with the Settings Builder API:
Android
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder() .setMinimumFetchIntervalInSeconds(fetchRefreshRate) .setFetchTimeoutInSeconds(fetchTimeout) .build(); mFirebaseRemoteConfig.setConfigSettings(configSettings);
On iOS, you can set similar values on the configSettings object.
configSettings
iOS
let remoteConfigSettings = RemoteConfigSettings() remoteConfigSettings.minimumFetchInterval = 1200 remoteConfigSettings.fetchTimeout = 3 remoteConfig.configSettings = remoteConfigSettings
To enable development mode using the new MinimumFetchIntervalInSeconds parameter, just set the parameter value to 0. Just be sure to set this back before you ship to production!
MinimumFetchIntervalInSeconds
For the Android SDK, we’ve updated how the client SDK communicates with the Remote Config backend. The Remote Config SDK’s previous implementation sometimes resulted in error responses getting eaten up along the way. This would sometimes make it difficult to understand why fetch requests were failing. Providing more informative and meaningful error messages is now improved by replacing networking calls through direct REST API calls to the Remote Config backend. So you’ll now be able to get more meaningful error responses when something goes wrong.
Upgrading is easy! Just update your Remote Config dependencies to the latest version, rebuild your app, and you’ll be set up to use the latest Remote Config V2 SDKs.
We’re always trying to improve and make Remote Config, as well as each of our other Firebase products, more helpful and easier to use. These changes are an effort towards that, so let us know what you think about them! Also, if you discover any bugs with the new updates, we’d love to hear about those too. Please reach out to us on StackOverflow or the official Firebase support site.
That’s right, the Android SDK and the iOS SDK for Remote Config are now both open source! You can check out the GitHub repo for the Android SDK in all its open source goodness here, and the repo for the iOS SDK here.
Firebase Performance Monitoring provides detailed insights into how your app performs in the hands of real users, giving visibility into bottlenecks that could be causing churn and revenue loss.
We've received positive feedback about the richness of performance data Firebase Performance Monitoring surfaces. However, a common complaint has been that it's difficult to determine the cause of these issues from the data we surface, forcing developers to spend a lot of time investigating the root cause of performance issues.
For example, it's great to know that your app launches slowly for 40% of your users, but why is this happening? Even though developers can use attributes such as app version, OS version, and geography to filter data in the dashboard, the data still may not give enough detail to pinpoint the exact issue at hand.
To address the need for actionable insights, we are pleased to launch the ability to dig deeper into an individual session of a trace, so you see attributes and events that happened leading up to a performance issue. With this feature, developers can see three new categories of information:
Surfacing these extra details in the context of a trace will help improve debuggability and issue resolution for performance issues.
Let's see how sessions works with a concrete example. Imagine you are an e-retail app developer using a custom trace, productImageLoading, to measure how long it takes to load an image of an item in your catalogue. You notice that an issue appears in the Firebase Performance Monitoring console for this trace because the product images are loading slower than the defined threshold of 200ms.
Performance Monitoring surfaces emerging issues.
Previously, if you clicked on the issue to get more details, you would see information like the median time for the trace, and you could slice the data by various segmentations, such as country, device, etc.
The issue details page shows more information and allows for data slicing
While the details page is helpful, it shows the information aggregated among all trace samples, which doesn't give enough context about other factors that may have contributed to the issue.
This is where drilling down into a session of a trace becomes powerful. With this new feature, you can now examine device properties, system usage, traces and network requests that happened around the same time as the specific trace instance being investigated. You can access all sessions for the metric from the top bar of the metrics detail page. If you're already segmenting your data by an attribute like app version or country, then you can click through to a pre-filtered view of sessions.
Sessions has 2 entry points
In the sessions view, you can narrow down sessions corresponding to a particular percentile range of the trace duration and look at the details of the trace instances for that range. The percentile range groups sessions into cohorts based on their performance making it easier to find the sessions with the worst performance:
Sessions view showing CPU, memory, traces and network requests for a percentile range
Looking at a product image loading session in the 90-95 percentile range, you can see the following:
Based on the above trace session data, you can see that requesting a large image impacted memory and CPU, and subsequently slowed down loading of the product image. This helps you pinpoint where in your code to investigate the issue further.
This is just an example of the powerful debuggability that comes with the new feature. We hope that developers are able to use this new feature in a myriad of use cases to bridge the gap between cause and effect and greatly reduce time spent debugging trace issues.
To get started on iOS or Android, please see our docs here. If you have any feedback, feel free to share with us through our support channel. Happy building!
Hey, welcome to part 3 in this series about using lifecycle-aware Android Architecture Components with Firebase Realtime Database. In part 1, we started with a simple Activity that uses database listeners to keep its UI fresh as data changes in the database. We converted that to use LiveData and ViewModel to remove the boilerplate of dealing with the listeners during the Activity lifecycle. Then, in part 2, we completely refactored away all mention of Realtime Database from the Activity, and implemented a performance enhancement. This optimization uses MediatorLiveData and some threading, for the case where data manipulation might be too expensive operation to perform on the main thread.
MediatorLiveData
There's one more optimization that can be applied in the code. It could have a large impact on performance, depending on how much data your database listeners are receiving. It has to do with how our FirebaseQueryLiveData implementation deals with the database listener during its onActive() and onInactive() methods. Here it is again:
FirebaseQueryLiveData
onActive()
onInactive()
public class FirebaseQueryLiveData extends LiveData<DataSnapshot> { private static final String LOG_TAG = "FirebaseQueryLiveData"; private final Query query; private final MyValueEventListener listener = new MyValueEventListener(); public FirebaseQueryLiveData(Query query) { this.query = query; } public FirebaseQueryLiveData(DatabaseReference ref) { this.query = ref; } @Override protected void onActive() { query.addValueEventListener(listener); } @Override protected void onInactive() { query.removeEventListener(listener); } private class MyValueEventListener implements ValueEventListener { @Override public void onDataChange(DataSnapshot dataSnapshot) { setValue(dataSnapshot); } @Override public void onCancelled(DatabaseError databaseError) { Log.e(LOG_TAG, "Can't listen to query " + query, databaseError.toException()); } } }
The key detail to note here is that a database listener is added during onActive() and removed during onInactive(). The Activity that makes use of FirebaseQueryLiveData executes this code during its onCreate():
onCreate()
HotStockViewModel viewModel = ViewModelProviders.of(this).get(HotStockViewModel.class); LiveData<DataSnapshot> liveData = viewModel.getDataSnapshotLiveData(); liveData.observe(this, new Observer<DataSnapshot>() { @Override public void onChanged(@Nullable DataSnapshot dataSnapshot) { if (dataSnapshot != null) { // update the UI here with values in the snapshot } } });
The observer here follows the lifecycle of the Activity. LiveData considers an observer to be in an active state if its lifecycle is in the STARTED or RESUMED state. The observer transitions to an inactive state if its lifecycle is in the DESTROYED state. The onActive() method is called when the LiveData object has at least one active observer, and the onInactive() method is called when the LiveData object doesn't have any active observers. So, what happens here when the Activity is launched, then goes through a configuration change (such as a device reorientation)? The sequence of events (when there is a single UI controller observing a FirebaseQueryLiveData) is like this:
Activity
LiveData
I've bolded the steps that deal with the database listener. You can see here the Activity configuration change caused the listener to be removed and added again. These steps spell out the cost of a second round trip to and from the Realtime Database server to pull down all the data for the second query, even if the results didn't change. I definitely don't want that to happen, because LiveData already retains the latest snapshot of data! This extra query is wasteful, both of the end user's data plan, and and counts against the quota or the bill of your Firebase project.
There's no easy way to change the way that the LiveData object becomes active or inactive. But we can make some guesses about how quickly that state could change when the Activity is going through a configuration change. Let's make the assumption that a configuration change will take no more than two seconds (it's normally much faster). With that, one strategy could add a delay before FirebaseQueryLiveData removes the database listener after the call to onInactive(). Here's an implementation of that, with a few changes and additions to FirebaseQueryLiveData:
private boolean listenerRemovePending = false; private final Handler handler = new Handler(); private final Runnable removeListener = new Runnable() { @Override public void run() { query.removeEventListener(listener); listenerRemovePending = false; } }; @Override protected void onActive() { if (listenerRemovePending) { handler.removeCallbacks(removeListener); } else { query.addValueEventListener(listener); } listenerRemovePending = false; } @Override protected void onInactive() { // Listener removal is schedule on a two second delay handler.postDelayed(removeListener, 2000); listenerRemovePending = true; }
Here, I'm using a Handler to schedule the removal of the database listener (by posting a Runnable callback that performs the removal) on a two second delay after the LiveData becomes inactive. If it becomes active again before those two seconds have elapsed, we simply eliminate that scheduled work from the Handler, and allow the listener to keep listening. This is great for both our users and our wallets!
Handler
Runnable
Are you using lifecycle-aware Android Architecture components along with Firebase in your app? How's it going? Join the discussion of all things Firebase on our Google group firebase-talk.
If you haven't tried Firebase Performance Monitoring yet, many Firebase developers have found it to be a helpful way to get a sense of some of the performance characteristics of their iOS or Android app, without writing many extra lines of code. To get more detailed information beyond what's collected automatically, you'll eventually have to write some custom traces and counters. Traces are a report of performance data within a distinct period of time in your app, and counters let you measure performance-related events during a trace. In today's perf tip, I'll propose a way to add potentially many more traces to your Android app without writing very much code at all.
Android apps are typically made up of a collection of activities that present some task or data to the user. For the purpose of hunting down potential performance problems, it can be handy to define a trace for every Activity in your app, so you can study the results later in the Firebase console. If your app has lots of activities, it might be kind of a pain to write the code for all of them. Instead, you can write a little bit of code that instruments all of them with their own trace.
Android gives you a way to listen in on the lifecycle of every single Activity in your app. The listeners are implementations of the interface ActivityLifecycleCallbacks, and you can register one with the Application.registerLifecycleCallbacks() method. For measuring performance, I suggest creating a trace that corresponds to the onStart() and onStop() lifecycle methods. When an activity is "started", that means it's visible on screen, and when it's "stopped", it's no longer visible, so I think this is a good place to define a trace that tracks an activity while it's actually doing things. Here's the start of an implementation of ActivityLifecycleCallbacks that keeps track of traces for each of your activities. First we'll make it a singleton so it can be easily accessed everywhere (or you might want to use some form of dependency injection):
ActivityLifecycleCallbacks
Application.registerLifecycleCallbacks()
onStart()
onStop()
public class PerfLifecycleCallbacks implements Application.ActivityLifecycleCallbacks { private static final PerfLifecycleCallbacks instance = new PerfLifecycleCallbacks(); private PerfLifecycleCallbacks() {} public static PerfLifecycleCallbacks getInstance() { return instance; } }
Then, inside that class, I'll add some members that manage custom traces for each Activity:
private final HashMap<Activity, Trace> traces = new HashMap<>(); @Override public void onActivityStarted(Activity activity) { String name = activity.getClass().getSimpleName(); Trace trace = FirebasePerformance.startTrace(name); traces.put(activity, trace); } @Override public void onActivityStopped(Activity activity) { Trace trace = traces.remove(activity); trace.stop(); } // ...empty implementations of other lifecycle methods...
This will start a trace when any activity is started, and stop the same trace when the activity is stopped. For the name of the trace, I'm using the simple class name of the activity object, which is just the class name without the full java package. (Note: if you do this, make sure that your Activity class names are unique, if they're spread across Java packages!)
I'll add one more method to it that will return the trace of a given Activity object. That can be used in any activity to get a hold of the current trace so that counters can be added to it:
@Nullable public Trace getTrace(Activity activity) { return traces.get(activity); }
This class should be registered before any Activity starts. A ContentProvider is a good place to do that. If you're not familiar with how that works, you can read about how Firebase uses a ContentProvider to initialize.
public class PerfInitContentProvider extends ContentProvider { @Override public boolean onCreate() { context = getContext(); if (context != null) { Application app = (Application) context.getApplicationContext(); app.registerActivityLifecycleCallbacks( PerfLifecycleCallbacks.getInstance()); } } }
Don't forget to add the ContentProvider to your app's manifest! This will ensure that it gets created before any Activity in your app.
Once this ContentProvider is in place, your app will automatically create traces for all your activities. If you want to add counters to one of them, simply use the getTrace() method from the PerfLifecycleCallbacks singleton using the current Activity object. For example:
getTrace()
private Trace trace; @Override protected void onCreate(Bundle savedInstanceState) { trace = PerfLifecycleCallbacks.getInstance().getTrace(this); // use the trace to tally counters... }
Be sure to think carefully about the counters you want to log! You'll want to measure things that will give you information that helps inform a decision about how the user experience could be improved in your app. For example, you could record the ratio of cache hits to misses to help tune the amount of memory for the cache. And be sure to follow Firebase on Twitter to get more Firebase Performance Monitoring tips.
Testing your application is a great way to help maximize its quality, and many of you know that Firebase Test Lab for Android has some useful tools for testing Android apps. If you're the type of engineer who likes to maximize test coverage by writing instrumented tests (and regular unit tests), you can send those to Test Lab for execution. Even if you don't like writing tests, you have some options. You can record instrumented tests by interacting with your app using Espresso Test Recorder in Android Studio. And there's almost no effort required at all to run a Robo test that automatically crawls your app.
These tests are helpful for data-driven apps because there are robust test frameworks that understand how to navigate the Android platform widgets used to receive input and display data on screen. However, most games don't work with platform widgets. Games typically take over the screen using their own UI elements, and provide their own touch controls. As a result, it's extremely difficult to write instrumented tests for testing games, and Robo test won't know how to navigate the game at all.
To help deal with these challenges with testing games, the Test Lab team has come up with a way for game developers to test their games effectively across many of the devices that Test Lab offers. It's a new type of test called a Game Loop Test, and it's available today in beta.
If you've seen arcade video games operate, you know that they're always showing something on screen, typically some automated demo of the game, called "attract mode". With Firebase Test Lab, game developers can now use this concept of attract mode to construct test scenarios, and Test Lab will arrange for those scenarios to be invoked in sequence. This gives developers the opportunity to test a wide variety of game levels and situations in a single test run on all the devices provided by Test Lab. If there are any problems with a scenario, you'll find out which ones are problematic, so you can focus your efforts on improving that specific case.
Even better, Test Lab now provides performance data with every report. Of particular interest to game developers is a graph of the rendered frame rate over time throughout the test, tied to a video of the device display. This helps you quickly identify the parts of your game that aren't rendering at an acceptable FPS.
In addition to FPS, there are other performance metrics available for all apps. You'll be able to see your app's CPU utilization, memory usage, and network ingress and egress. Notice how you can jump directly to the point in the video where you're observing performance problems.
If you're a game developer, now is a great time to check out Firebase Test Lab for Android to see what it can do to help the quality of your game.