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.
We're happy to announce that the Realtime Database has integrated with Google Stackdriver! This integration allows you to monitor your Realtime Database in powerful new ways. Here are some of the highlights:
Several metrics are already available in your Firebase Console under simpler names. For example io/utilization is "Load", storage/total_bytes is "Storage", network/sent_bytes_count is "Downloads", and network/active_connections is "Connections". These metrics form a great base, but now we you can go further so can can closely monitor your application as it scales with a whole collection of new, in-depth insights.
io/utilization
storage/total_bytes
network/sent_bytes_count
network/active_connections
To get started, check out https://console.cloud.google.com/monitoring to create a Stackdriver account for your Firebase projects.
To set up a graph of a new Realtime Database metric, go to Dashboards > Create Dashboard in Stackdriver, then click on the Add Chart button in the toolbar.
This example is replicating the "Load" graph in your Firebase Console, except we've improved it by also breaking down the data by "operation type". With this detailed view, you can see how long it takes your database to respond to REST "get" requests, versus how much REST "put" or realtime "set" operations are taking up your database's capacity.
We also have a few other key metrics we think you'll love, such as network/https_requests_count which tells you how many requests to the database require a full SSL handshake, network/sent_payload_and_protocol_bytes_count which is a measure of the raw bandwidth from the database (excluding encryption and SSL handshakes), and many others. Check out our list of all metrics for a more in-depth explanation and stay tuned for follow up blog posts where we'll dive into more complex examples of alerts and charts in Stackdriver.
network/https_requests_count
network/sent_payload_and_protocol_bytes_count
Welcome to part 2 of this blog series on using lifecycle-aware Android Architecture Components (LiveData and ViewModel) along with Firebase Realtime Database to implement more robust and testable apps! In the first part, we saw how you can use LiveData and ViewModel to simplify your Activity code, by refactoring away most of the implementation details of Realtime Database from an Activity. However, one detail remained: the Activity was still reaching into the DataSnapshot containing the stock price. I'd like to remove all traces of the Realtime Database SDK from my Activity so that it's easier to read and test. And, ultimately, if I change the app to use Firestore instead of Realtime Database, I won't even have to change the Activity code at all.
ViewModel
DataSnapshot
Here's a view of the data in the database:
and here's the code that reads it out of DataSnapshot and copies into a couple TextViews:
// update the UI with values from the snapshot String ticker = dataSnapshot.child("ticker").getValue(String.class); tvTicker.setText(ticker); Float price = dataSnapshot.child("price").getValue(Float.class); tvPrice.setText(String.format(Locale.getDefault(), "%.2f", price));
The Realtime Database SDK makes it really easy to convert a DataSnapshot into a JavaBean style object. The first thing to do is define a bean class whose getters and setters match the names of the fields in the snapshot:
public class HotStock { private String ticker; private float price; public String getTicker() { return ticker; } public void setTicker(String ticker) { this.ticker = ticker; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public String toString() { return "{HotStock ticker=" + ticker + " price=" + price + "}"; } }
Then I can tell the SDK to automatically perform the mapping like this:
HotStock stock = dataSnapshot.getValue(HotStock.class)
After that line executes, the new instance of HotStock will contain the values for ticker and price. Using this handy line of code, I can update my HotStockViewModel implementation to perform this conversion by using a transformation. This allows me to create a LiveData object that automatically converts the incoming DataSnapshot into a HotStock. The conversion happens in a Function object, and I can assemble it like this in my ViewModel:
HotStock
ticker
price
HotStockViewModel
Function
// This is a LiveData<DataSnapshot> from part 1 private final FirebaseQueryLiveData liveData = new FirebaseQueryLiveData(HOT_STOCK_REF); private final LiveData<HotStock> hotStockLiveData = Transformations.map(liveData, new Deserializer()); private class Deserializer implements Function<DataSnapshot, HotStock> { @Override public HotStock apply(DataSnapshot dataSnapshot) { return dataSnapshot.getValue(HotStock.class); } } @NonNull public LiveData<HotStock> getHotStockLiveData() { return hotStockLiveData; }
The utility class Transformations provides a static method map() that returns a new LiveData object given a source LiveData object and a Function implementation. This new LiveData applies the Function to every object emitted by the source, then turns around and emits the output of the Function. The Deserializer function here is parameterized by the input type DataSnapshot and the output type HotStock, and it has one simple job - deserialize a DataSnapshot into a HotStock. Lastly, we'll add a getter for this new LiveData that emits the transformed HotStock objects.
Transformations
map()
Deserializer
With these additions, the application code can now choose to receive updates to either DataSnapshot or HotStock objects. As a best practice, ViewModel objects should emit objects that are fully ready to be consumed by UI components, so that those components are only responsible for displaying data, not processing data. This means that HotStockViewModel should be doing all the preprocessing required by the UI layer. This is definitely the case here, as HotStock is fully ready to consume by the Activity that's populating the UI. Here's what the entire Activity looks like now:
public class MainActivity extends AppCompatActivity { private TextView tvTicker; private TextView tvPrice; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvTicker = findViewById(R.id.ticker); tvPrice = findViewById(R.id.price); HotStockViewModel hotStockViewModel = ViewModelProviders.of(this).get(HotStockViewModel.class); LiveData<HotStock> hotStockLiveData = hotStockViewModel.getHotStockLiveData(); hotStockLiveData.observe(this, new Observer() { @Override public void onChanged(@Nullable HotStock hotStock) { if (hotStock != null) { // update the UI here with values in the snapshot tvTicker.setText(hotStock.getTicker()); tvPrice.setText(String.format(Locale.getDefault(), "%.2f", hotStock.getPrice())); } } }); } }
public class MainActivity extends AppCompatActivity { private TextView tvTicker; private TextView tvPrice; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvTicker = findViewById(R.id.ticker); tvPrice = findViewById(R.id.price); HotStockViewModel hotStockViewModel = ViewModelProviders.of(this).get(HotStockViewModel.class);
hotStockLiveData.observe(this, new Observer() { @Override public void onChanged(@Nullable HotStock hotStock) { if (hotStock != null) { // update the UI here with values in the snapshot tvTicker.setText(hotStock.getTicker()); tvPrice.setText(String.format(Locale.getDefault(), "%.2f", hotStock.getPrice())); } } }); } }
All the references to Realtime Database objects are gone now, abstracted away behind HotStockViewModel and LiveData! But there's still one potential problem here.
All LiveData callbacks to onChanged() run on the main thread, as well as any transformations. The example I've given here is very small and straightforward, and I wouldn't expect there to be performance problems. But when the Realtime Database SDK deserializes a DataSnapshot to a JavaBean type object, it uses reflection to dynamically find and invoke the setter methods that populate the bean. This can become computationally taxing as the quantity and size of the objects increase. If the total time it takes to perform this conversion is over 16ms (your budget for a unit of work on the main thread), Android starts dropping frames. When frames are dropped, it no longer renders at a buttery-smooth 60fps, and the UI becomes choppy. That's called "jank", and jank makes your app look poor. Even worse, if your data transformation performs any kind of I/O, your app could lock up and cause an ANR.
onChanged()
If you have concerns that your transformation can be expensive, you should move its computation to another thread. That can't be done in a transformation (since they run synchronously), but we can use something called MediatorLiveData instead. MediatorLiveData is built on top of a map transform, and allows us to observe changes other LiveData sources, deciding what to do with each event. So I'll replace the existing transformation with one that gets initialized in the no-arg constructor for HotStockViewModel from part 1 of this series:
private final FirebaseQueryLiveData liveData = new FirebaseQueryLiveData(HOT_STOCK_REF); private final MediatorLiveData<HotStock> hotStockLiveData = new MediatorLiveData<>(); public HotStockViewModel() { // Set up the MediatorLiveData to convert DataSnapshot objects into HotStock objects hotStockLiveData.addSource(liveData, new Observer<DataSnapshot>() { @Override public void onChanged(@Nullable final DataSnapshot dataSnapshot) { if (dataSnapshot != null) { new Thread(new Runnable() { @Override public void run() { hotStockLiveData.postValue(dataSnapshot.getValue(HotStock.class)); } }).start(); } else { hotStockLiveData.setValue(null); } } }); }
Here, we see that addSource() is being called on the MediatorLiveData instance with a source LiveData object and an Observer that gets invoked whenever that source publishes a change. During onChanged(), it offloads the work of deserialization to a new thread. This threaded work is using postValue() to update the MediatorLiveData object, whereas the non-threaded work when (dataSnapshot is null) is using setValue(). This is an important distinction to make, because postValue() is the thread-safe way of performing the update, whereas setValue() may only be called on the main thread.
addSource()
Observer
postValue()
setValue()
NOTE: I don't recommend starting up a new thread like this in your production app. This is not an example of "best practice" threading behavior. Optimally, you might want to use an Executor with a pool of reusable threads (for example) for a job like this.
Executor
Now that we've removed Realtime Database objects from the Activity and accounted for the performance of the transformation from DataSnapshot to HotStock, there's still another performance improvement to make here. When the Activity goes through a configuration change (such as a device reorientation), the FirebaseQueryLiveData object will remove its database listener during onInactive(), then add it back during onActive(). While that doesn't seem like a problem, it's important to realize that this will cause another (unnecessary) round trip of all data under /hotstock. I'd rather leave the listener added and save the user's data plan in case of a reorientation. So, in the next part of this series, I'll look at a way to make that happen.
/hotstock
I hope to see you next time, and be sure to follow @Firebase on Twitter to get updates on this series! You can click through to part 3 right here.
MightySignal, a mobile intelligence startup based out of San Francisco, just published a new report examining the fastest growing Android SDKs of 2017. This fascinating report sheds light on which app development tools are taking off and what trends they signal for the year ahead. We're humbled and excited to see that 8 out of the top 20 fastest growing SDKs are part of Firebase!
This positive reception from the community validates and fuels our commitment to helping developers succeed. It also motivates us to continue making Firebase even better. Over the past year, we've made numerous improvements to our SDKs, including the ones highlighted in MightySignal's report.
Source: MightySignal's 2017 report on the fastest growing SDKs
For example, Firebase Realtime Database is number three on MightySignal's list, and it continues to be one of our most used and trusted products. We understand how important storing and syncing data is for your mobile business, and to further help you with this, we introduced another database product this year. If you're a Realtime Database customer, we think you'll love Cloud Firestore, our latest realtime, scalable NoSQL database that we built in collaboration with the Google Cloud Platform team. It allows you to sync and store data like Realtime Database, while also addressing its key limitations like data structuring, querying, and scaling. Cloud Firestore is available in beta today!
Another notable mention is Firebase Remote Config. Remote Config gives you the power to customize your app's interface and behavior for different audiences so you can deliver personalized app experiences without requiring users to update their apps. Now, Remote Config can be used with Firebase Predictions' dynamic user groups. This means you can change the look and feel of your app for users based on their predicted behavior (such as churn or in-app purchase). Wondering how this works? Learn how Halfbrick Studios grew their 7-day retention rate from 25% to 30% by combining Predictions with Remote Config.
And that's not all that's new with Remote Config! In the past, Remote Config allowed you to perform simple A/B testing, but now, we've gone ahead and added an entirely new experiment layer in Firebase that works wonderfully with Remote Config so you can set up, run, and measure sophisticated A/B tests.
We were also delighted to see that Firebase Auth and Firebase Crash Reporting are experiencing high growth as well, according to MightySignal's findings. After welcoming the Fabric team to Firebase, we worked together to add new features to Auth (such as phone number authentication), which we unveiled in June. More recently, we launched a beta version of Firebase Crashlytics, a powerful realtime crash reporting tool that will help you track, prioritize, and fix issues that erode app stability. Firebase Crashlytics is now our primary crash reporter. If you want to learn more about how app stability can lead to growth in user engagement and retention, check out how Doodle used Crashlytics to grow user engagement by 42%.
MightySignal's data on the fastest growing SDKs is available here. We're very thankful to be part of the developer community and committed to helping you build better apps and grow your business. Stay tuned for more product updates next year and, in the meantime, happy building!
This year at Google I/O 2017, the Android platform team announced the availability of Android Architecture Components, which provides libraries that help you design robust, testable, and maintainable apps. Among all the tools it offers, I'm particularly impressed by the way it helps you manage the lifecycle of your app's activities and fragments - a common concern for Android developers.
In this blog series, I'll explore how these libraries can work together with the Firebase Realtime Database SDK to help architect your app. The way client apps read data from Realtime Database is through listeners that get called with updates to data as it's written. This allows you to easily keep your app's UI fresh with the latest data. It turns out that this model of listening to database changes works really well Android Architecture Components. (Also note that the information here applies equally well to Firestore, which also delivers data updates to client apps in real time.)
Android apps that use Realtime Database often start listening for changes during the onStart() lifecycle method, and stop listening during onStop(). This ensures that they only receive changes while an Activity or Fragment is visible on screen. Imagine you have an Activity that displays the ticker and most recent price of today's hot stock from the database. The Activity looks like this:
onStart()
onStop()
Fragment
public class MainActivity extends AppCompatActivity { private static final String LOG_TAG = "MainActivity"; private final DatabaseReference ref = FirebaseDatabase.getInstance().getReference("/hotstock"); private TextView tvTicker; private TextView tvPrice; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvTicker = findViewById(R.id.ticker); tvPrice = findViewById(R.id.price); } @Override protected void onStart() { super.onStart(); ref.addValueEventListener(listener); } @Override protected void onStop() { ref.removeEventListener(listener); super.onStop(); } private ValueEventListener listener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // update the UI here with values in the snapshot String ticker = dataSnapshot.child("ticker").getValue(String.class); tvTicker.setText(ticker); Float price = dataSnapshot.child("price").getValue(Float.class); tvPrice.setText(String.format(Locale.getDefault(), "%.2f", price)); } @Override public void onCancelled(DatabaseError databaseError) { // handle any errors Log.e(LOG_TAG, "Database error", databaseError.toException()); } }; }
It's pretty straightforward. A database listener receives updates to the stock price located at /hotstock in the database, and the values are placed into a couple TextView objects. For very simple cases like this, there's not a problem. But if this app becomes more complex, there's a couple immediate issues to be aware of:
TextView
1. Boilerplate
There's a lot of standard boilerplate here for defining a DatabaseReference at a location in the database and managing its listener during onStart() and onStop(). The more listeners involved, the more boilerplate code will clutter this code. And a failure to remove all added listeners could result in data and memory leaks - one simple mistake could cost you money and performance.
DatabaseReference
2. Poor testability and readability
While the effect of the code is straightforward, it's difficult to write pure unit tests that verify the logic, line by line. Everything is crammed into a single Activity object, which becomes difficult to read and manage.
Digging into the libraries provided by Architecture Components, you'll find there are two classes in particular that are helpful to address the above issues: ViewModel and LiveData. If you haven't read about how these work, please take a moment to read about ViewModel and LiveData to learn about them. I'll also be extending LiveData, so take a look there as well. It's important to understand the way they interact with each other, in addition to the LifecycleOwner (e.g. an Activity or Fragment) that hosts them.
LifecycleOwner
Extending LiveData with Firebase Realtime Database
LiveData is an observable data holder class. It respects the lifecycle of Android app components, such as activities, fragments, or services, and only notifies app components that are in an active lifecycle state. I'll use it here to listen to changes to a database Query or DatabaseReference (note that a DatabaseReference itself is a Query), and notify an observing Activity of those changes so it can update its UI. These notifications come in the form of DataSnapshot objects that you'd normally expect from the database listener. Here's a LiveData extension that does exactly that:
Query
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() { Log.d(LOG_TAG, "onActive"); query.addValueEventListener(listener); } @Override protected void onInactive() { Log.d(LOG_TAG, "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()); } } }
With FirebaseQueryLiveData, whenever the data from the Query given in the constructor changes, MyValueEventListener triggers with a new DataSnapshot, and it notifies any observers of that using the setValue() method on LiveData. Notice also that MyValueEventListener is managed by onActive() and onInactive(). So, whenever the Activity or Fragment associated with this LiveData object is on screen (in the STARTED or RESUMED state), the LiveData object is "active", and the database listener will be added.
MyValueEventListener
The big win that LiveData gives us is the ability to manage the database listener according to the state of the associated Activity. There's no possibility of a leak here because FirebaseQueryLiveData knows exactly when and how to set up and tear down its business. Note that we can reuse this class for all kinds of Firebase queries. This FirebaseQueryLiveData class is a very reusable class!
Now that we have a LiveData object that can read and distribute changes to the database, we need a ViewModel object to hook that up to the Activity. Let's take a look at how to do that.
Implementing a ViewModel to manage FirebaseQueryLiveData
ViewModel implementations contain LiveData objects for use in a host Activity. Because a ViewModel object survives Activity configuration changes (e.g. when the user reorients their device), its LiveData member object will be retained as well. The lifetime of a ViewModel with respect to its host Activity can be illustrated like this:
Here's a ViewModel implementation that exposes a FirebaseQueryLiveData that listens to the location /hotstock in a Realtime Database:
public class HotStockViewModel extends ViewModel { private static final DatabaseReference HOT_STOCK_REF = FirebaseDatabase.getInstance().getReference("/hotstock"); private final FirebaseQueryLiveData liveData = new FirebaseQueryLiveData(HOT_STOCK_REF); @NonNull public LiveData<DataSnapshot> getDataSnapshotLiveData() { return liveData; } }
Note that this ViewModel implementation exposes a LiveData object. This allows the Activity that uses HotStockViewModel to actively observe any changes to the underlying data under /hotstock in the database.
Using LiveData and ViewModel together in an Activity
Now that we have LiveData and ViewModel implementations, we can make use of them in an Activity. Here's what the Activity from above now looks like after refactoring to use LiveData and ViewModel:
public class MainActivity extends AppCompatActivity { private TextView tvTicker; private TextView tvPrice; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvTicker = findViewById(R.id.ticker); tvPrice = findViewById(R.id.price); // Obtain a new or prior instance of HotStockViewModel from the // ViewModelProviders utility class. 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 String ticker = dataSnapshot.child("ticker").getValue(String.class); tvTicker.setText(ticker); Float price = dataSnapshot.child("price").getValue(Float.class); tvPrice.setText(String.format(Locale.getDefault(), "%.2f", price)); } } }); } }
It's about 20 lines of code shorter now, and easier to read and manage!
During onCreate(), it gets a hold of a HotStockViewModel instance using this bit of code:
HotStockViewModel viewModel = ViewModelProviders.of(this).get(HotStockViewModel.class);
ViewModelProviders is a utility class from Architecture Components that manages ViewModel instances according to the given lifecycle component. In the above line, the resulting HotStockViewModel object will either be newly created if no ViewModel of the named class for the Activity exists, or obtained from a prior instance of the Activity before a configuration change occurred.
ViewModelProviders
With an instance of HotStockViewModel, the Activity response changes to its LiveData by simply attaching an observer. The observer then updates the UI whenever the underlying data from the database changes.
So, what's the advantage to doing things this way?
ValueEventListener
If you look at the new Activity implementation, you can see that most of the details of working with Firebase Realtime Database have been moved out of the way, into FirebaseQueryLiveData, except for dealing with the DataSnapshot. Ideally, I'd like to remove all references to Realtime Database altogether from the Activity so that it doesn't have to know or care where the data actually comes from. This is important if I ever want to migrate to Firestore - the Activity won't have to change much, if at all.
There's another subtle issue with the fact that each configuration change removes and re-adds the listener. Each re-add of the listener effectively requires another round trip with the server to fetch the data again, and I'd rather not do that, in order to avoid consuming the user's limited mobile data. Enabling disk persistence helps, but there's a better way (stay tuned to this series for that tip!).
We'll solve these two problems in future posts, so stay tuned here to the Firebase Blog by following @Firebase on Twitter! You can click through to part 2 right here.
Firebase helps you create better games faster without needing to build and maintain a backend infrastructure. We are really excited to announce the availability of our demo application MechaHamster for iOS on the App Store. If you're an Android user you can check out our prior release on the Google Play Store.
MechaHamster is built on our easy to install Unity SDK, and takes advantage of several powerful Firebase features, including:
Want to see how easy it was to plug Firebase into MechaHamster yourself? Check out the Unity project over at Github: https://github.com/google/mechahamster
We can't wait to see what amazing iOS games you build with Firebase. To find out more about how Firebase can power up your games, grow your business and create better experiences for your players head to https://firebase.google.com/games/
It's December, folks, and you know what that means: holiday cheer!
The Firebase Test Lab team is fully invested in making this season the greatest of seasons. Remember Halloween? What a hoot! Also, this happened:
(Note: these are actual Test Lab engineers, in costume, actually beating each other up with foam sticks at a Halloween party. Both get lumps of coal this year.)
We're getting ready for the holidays! So, sit back, pour yourself some eggnog, and read about what's new for your Android testing enjoyment.
Many of you are using Robo to automatically test your apps in Test Lab. Since you don't have to write any code to make it work, it's the gift that keeps on giving. Even better, you can have it fill in specific form fields and push buttons with some easy configuration.
We've found that some apps require more of a nudge to navigate into the parts that need the most testing. (Hey, even Santa needs help from a few elves!) Now, with Robo scripts, you can record a series of actions to take in your app, and play that back before Robo takes over. It works a lot like Espresso Test Recorder, except the output is a JSON file that you upload along with your APK when running a Robo test. With these extra instructions, you can guide your app past introductions or login screens.
Of course, your best bet to maximize the test coverage is writing Espresso tests that drive your app. I heard that it's easier than driving a team of reindeer!
Do you use the screenshots in Test Lab results to check if your app displays correctly? It's a great way to see if you app renders "naughty or nice" on many different types of screens. But if you test with lots of devices in a single test matrix, it can be kind of a pain to sort through all the results to compare the same screen among multiple devices. Now, Test Lab will cluster them together in your test results, so you can see all the different screen sizes, densities, and orientations from your test in a single place.
The Test Lab team is always busy at the North Pole (located at a data center in Atlanta) bringing you new devices to test with. The latest additions are the Sony Xperia XZ Premium, the Moto G4 Play, and the Huawei P8lite, delivered straight to your digital stocking. However, sometimes old toys break and need to be recycled. At the Test Lab workshop, we call that "device deprecation", which means we take old devices out of commission as they become unreliable. To see a (twice-checked) list of devices that are currently available, in addition to those being deprecated, click through to this page. Once a device is marked as "deprecated", it'll remain available for a month, then removed.
Deprecated devices look like this in the Firebase console:
And like this in the gcloud command line (note the "deprecated" tag in red):
You better not pout, you better not cry ‐ these devices served longer than their expected lifetime!
Or, just join us on the Firebase Slack in the #test-lab channel. We're all friendly there, so be good, for goodness sake!
If you've seen any of my recent Firebase talks, you know I'm a huge fan of TypeScript. At this year's Firebase Dev Summit in Amsterdam, I recommended TypeScript to improve the quality of your Cloud Functions. Today, we're making it easier to use TypeScript when writing Cloud Functions.
TypeScript is an extension of JavaScript to help build apps more quickly and correctly. It helps us build apps quickly by giving us early access to the newest JavaScript features like await and async. TypeScript also adds optional static typing to your code. Static typing lets IDEs give better code complete, linters catch more complex bugs, and compilers catch all syntax errors. Many developers have expressed interest in using TypeScript with Cloud Functions. Now starting with 3.16.0, the Firebase CLI gives first-class support to TypeScript. Get the latest version of the CLI with the command:
await
async
npm install -g firebase-tools
The new version of the Firebase CLI will ask you to pick a language when you create a new Firebase project with firebase init and choose to use Cloud Functions. If you choose TypeScript, it will set up a TypeScript-ready project structure and compiler options for Cloud Functions.
firebase init
Because Cloud Functions runs JavaScript, you need to "transpile" your TypeScript into JavaScript before it can run on Node.js. The Firebase CLI understands this, and all TypeScript projects you initialize with the new CLI are automatically compiled as part of every code deployment.
When you initialize your TypeScript project, the Firebase CLI recommends you use TSLint. We combed through every rule in TSLint to pick a set of safe defaults. We try not to enforce our coding style, but we will prevent deploys if we're fairly certain your code has a bug. This includes the most common error when writing Cloud Functions: forgetting to return a promise!
TSLint can detect warnings and errors. Warnings are shown during deploys and errors will block deploys to protect you from breaking production. If you're absolutely sure that your code is bug-free, you can disable the linter on specific lines with rule flags:
/* tslint:disable:<rule> */ myCode(); /* tslint:enable:<rule> */
Or you can disable the rule globally by removing the rule from tslint.json.
The Firebase CLI is able to automatically transpile and lint your TypeScript code thanks to another new Firebase CLI feature: lifecycle hooks. These hooks let you add code that should run automatically before. The first two hooks, "predeploy" and "postdeploy", run before and after a feature is deployed. These hooks work with all Firebase features (Cloud Functions, Hosting, Storage, Database, etc). With these hooks you can:
To add a lifecycle hook, add either "predeploy" or "postdeploy" as a subkey in that feature's stanza of firebase.json. For example, this is the predeploy hook that compiles typescript before deploying:
{ "functions": { "predeploy": "npm --prefix functions run build" } }
The following postdeploy hook tags the current git commit as production (warning: this assumes you don't have a branch named 'production-functions').
{ "functions": { "postdeploy":""git tag -f production-functions && git push -f origin production-functions" } }
We've extended our Cloud Functions docs with information about TypeScript.
Let us know how TypeScript affects your development process by tweeting @Firebase. Has it helped catch bugs early? What linter rules did we miss? What are your favorite lifecycle hooks?
A long while back, David East wrote a handy blog post about using the Firebase CLI to read and write your Firebase Realtime Database. The CLI has evolved a lot since then, so I'd like to share some of what's changed (and new!).
When I first started working with Realtime Database, I'd spend a fair amount of time in the Firebase console manually entering some data to work with. It's kinda fun to make changes there, then see them immediately in my app! But I soon discovered that it's kind of repetitive and time consuming to test like that. Instead, I could write a program to make the changes for me, but that wasn't a very flexible option. For easy reading and writing of data in my database, I found that the Firebase CLI is the best option for me. So, I'll share some of what it does here and how it can come in handy. All my examples will be using the Bash shell - you may have to modify them for other shells.
The Firebase CLI requires you to set aside a project directory, log in, and select a project that you want to work with, so be sure to follow the instructions to get set up with your existing project.
To write data from the command line use the firebase database:set command:
firebase database:set
firebase database:set /import data.json
The first argument to database:set is the path within the database to be written (here, /import), and the second is the JSON file to read from. If you don't have a file, and would rather provide the JSON on the command line, you can do this also with the --data flag:
/import
--data
firebase database:set /import --data '{"foo": "bar baz"}'
Notice that the JSON is quoted for the command line with single quotes. Otherwise, the space between the colon and "bar" would fool your shell into thinking that there are two arguments there. You can't use double quotes to quote this JSON string either, because JSON uses those quotes for its own strings. Escaping JSON for a unix command line can be tricky, so be careful about that! (For further thought: what if there was a single quote in one of the JSON strings?)
Also, you can pipe or redirect JSON to stdin. So, if you have a program that generates some JSON to add to your database, you can do it like this:
echo '{"foo": "bar baz"}' | firebase database:set /import --confirm
Notice that the --confirm flag is passed here to prevent the command from asking if you're OK potentially overwriting data. Piping to stdin won't work without it!
--confirm
The database:set command is great for initially populating your database with a setup script. If you run automated integration tests, the CLI is a handy way of scripting the initialization of your test environment.
database:set
It's also super handy for quickly triggering Cloud Functions database triggers, so you don't have to type in stuff at the command prompt every time you have something complicated to test.
Reading data from your database with the Firebase CLI is similarly easy. Here's how you fetch all the data under /messages as a JSON blob:
/messages
firebase database:get /messages
To save the output to a file, you can use a shell redirect, or the --output flag:
firebase database:get /messages > messages.json firebase database:get /messages --output messages.json
You'll notice the JSON output is optimized for space, which makes it hard to read. For something a little easier on the eyes, you can have the output "pretty-printed" for readability:
firebase database:get /messages --pretty
You can also sort and limit data just like the Firebase client APIs.
firebase database:get /messages --order-by-value date
To see all the options for reading and sorting, be sure to see the CLI help (all Firebase CLI commands share their usage like this):
firebase database:get --help
You've probably used the Realtime Database push function to add data to a node in your database. You can do the same with the CLI:
firebase database:push /messages --data '{"name":"Doug","text":"I heart Firebase"}'
This will create a unique push id under /messages and add the data under it. (Did you know that push IDs recently switched from starting with "-K" to "-L"?)
If you want to update some values at a location without overwriting that entire location, use database:update:
database:update
firebase database:update /users/-L-7Zl_CiHW62YWLO5I7 --data '{"name":"CodingDoug"}'
For those times when you need to remove something completely, there is database:remove. This command will blow away your entire database, unconditionally, kinda like rm -rf /. Be careful with this one:
database:remove
rm -rf /
firebase database:remove / --confirm
Sometimes you might want to simply copy the contents of your database from one project to another (for example, your development environment to staging). This is really easy by piping the stdout of database:get to the stdin of database:set:
database:get
firebase --project myproject-dev database:get / | \ firebase --project myproject-staging database:set / --confirm
Note here the use of --project to specify which Firebase project is to be used for reading and writing. This is your project's unique id in the Firebase console.
--project
If you find yourself repeating a set of commands, it's probably time to make a bash function. Save your function to your .bash_profile and you'll be able to access them from anywhere in your shell command line.
Do you often copy data between databases? The function below makes it easy:
function transfer_to() { local src_db="${1}" local dest_db="${2}" local path="${3:-/}" firebase database:get --project "$src_db" "$path" | firebase --project "$dest_db" database:set "$path" --confirm }
To use the function just call the transfer_to command (as if it were any other command) with the names of the project to copy to and from:
transfer_to myproject-dev myproject-staging
The command line is one of the most versatile tools in an engineer's toolbox. What are you doing with the CLI? We'd love to hear from you, so please shout out to us on Twitter. If you have any technical questions, post those on Stack Overflow with the firebase tag. And for bug reports and feature requests, use this form.
Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that reliably delivers messages at no cost. FCM sends over 400 billion messages per day. Today we are excited to announce the launch of a new RESTful API, the FCM HTTP v1 API, that makes it safer and easier to send messages to your cross-platform applications. All existing FCM clients can receive messages sent via the new FCM API -- it does not require any changes on the client side.
Security
The new FCM API uses the OAuth2 security model. In the event that an access token becomes public, it can only be used for an hour or so before it expires. Refresh tokens are not transmitted as often and are thus much less likely to be captured.
Sending messages to multiple platforms is possible with the legacy API. However, as you add functionality to messages, sending to multiple platforms becomes difficult. With the new FCM API, sending messages to multiple platforms is very easy.
You can still send simple messages to multiple platforms using the common top level fields. For example, you can send this message informing users about a sale:
{ "message": { "topic":"sale-watchers", "notification": { "title":"Check out this sale!", "body":"All items half off through Friday" } }
When you send a notification like this one to devices subscribed to a topic, you probably want them to be taken to the description of the item. On Android you would compose a message including a "click_action" field indicating the activity to open. On iOS, APNs relies on a "category" indicating the action to take upon clicking, including which view to show.
"click_action"
"category"
Before, since these keys were unique to their respective platforms, developers would have to create two separate messages. Now, we can use platform-specific fields together with common ones in a single message:
{ "message": { "topic":"sale-watchers", "notification": { "title":"Check out this sale!", "body":"All items half off through Friday" }, "android":{ "notification"{ "click_action":"OPEN_ACTIVITY_3" } }, "apns": { "payload": { "aps": { "category": "SALE_CATEGORY" } } } } }
Note: In this case web apps subscribed to the 'sale-watchers' topic will receive a notification message with the defined title and body.
The new FCM API fully supports messaging options available on iOS, Android and Web. Since each platform has its own defined block in the JSON payload, we can easily extend to other platforms as needed. If a future IoT messaging protocol requires a security_key field we could easily support an iot block within the FCM payload.
iot
{ "message": { "topic":"sale-watchers", "notification": { "title":"Check out this sale!", "body":"All items half off through Friday" }, "android":{ "notification"{ "click_action":"OPEN_ACTIVITY_3" } }, "apns": { "payload": { "aps": { "category": "SALE_CATEGORY" } } } "iot": { "security_key": "SECURITY_KEY" } } }
The new FCM API is the more secure, cross platform, future proof way of sending messages to FCM clients. If you are currently using the FCM legacy API, or if you are interested in using FCM to send messages to your apps, give the new FCM API a try. See the FCM guides and reference docs for more.
About FCM
Authorize requests
Build message requests
Migrate from GCM to FCM on Android
Migrate from GCM to FCM on iOS
When the folks at Fabric joined Firebase earlier this year, we aligned around a common mission: provide developers like you with a platform that solves common problems across the app development lifecycle, so you can focus on building an awesome user experience.
One area — among many — where Fabric excelled was in its console and dashboards. We've been hard at work for the last several months, working with them to bring together the best parts of Fabric and Firebase. Today, we're excited to share some improvements to the Firebase console.
We started by redesigning the navigation, to more accurately reflect the way your team works. We've clustered Firebase products into four groups: Develop, Stability, Analytics, and Grow. All of the products that you're used to seeing in the Firebase console are still there; we've simply reorganized them in a way that makes it simpler to navigate between them as you use more products across the Firebase platform.
We've also redesigned the Project Home screen in the Firebase console to bring a few important metrics front and center. Now, when you first open a project in Firebase, you'll see four key metrics: 30-day crash-free user rate, 30-day crashes, daily active users, and monthly active users, along with graphs that displays these trends over time. In research, we found that the vast majority of the time, developers are looking for one of those four metrics, so we made them easily accessible from the Project Home landing page.
Another well-loved Fabric console feature is the Latest Release section. This dashboard gives you all the most important insights from your most recent app release, so you can get a quick snapshot of what's going well and what might need to be rolled back. We've brought this section into the redesigned Firebase Console; you'll find it under the Analytics section of the navigation bar.
Starting today, you'll also see an Analytics dashboard that is organized into simple cards underneath an easy to understand question. Organizing data around jargon like "DAUs" or "retention cohorts" is difficult to navigate, so we've restructured the dashboard around questions you have about your app, like "where are my users engaged?" Or "how well do I retain users?" Our research confirmed that 90% of users preferred this design and we hope you find it helpful too!
Another thing we've learned from our friends at Fabric and heard from all of you is that having information in realtime is critical. Whether you're tracking a new app release or monitoring the status of a bug fix, you need to understand what's happening in your app in realtime, so you can make changes and prioritize work accordingly.
To help with this, we've added realtime data on crashes and active users to a card that you'll see in the new Analytics dashboard, as well as the Latest Release section of the console. This is just the first step and, over time, we plan to make realtime data more prevalent across the rest of the Firebase console.
As always, you can contact us at https://firebase.google.com/support/ with feedback and questions. Happy building!
We're delighted to announce the beta launch of Firebase Predictions. With this we are bringing the power of Google's machine learning systems to every developer that uses Firebase. Predictions is a product that can build dynamic user groups based on predicted behavior, determined using a machine learned model, and these user groups can then be targeted using Firebase Cloud Messaging, Remote Config and other technologies. User groups are updated daily to keep your predictions fresh.
Out of the box, there are four predicted groups:
You'll see these three groups right away when you select Predictions in the left nav bar of Firebase Console.
You'll notice that each of these cards has actions that you can take upon them.
Tolerance: The tolerance slider gives you the ability to tolerate low, medium or high risk of false positives. So, with a low tolerance, your population of users will be smaller, but so also will your risk of false positives. Similarly, with a high tolerance, you'll have a larger population of users, but at a risk of some of them being false positives. In the case of 'churn', a false positive would be a user who is predicted to churn, but in fact continues to use your app.
Target Users: This gives you a drop-down on which you can select Remote Config or Notifications for that user group. It also links to some handy guidance for offering in-app incentives.
Selecting Remote Config will take you to a new screen where you can specify the remote config parameter that you want to set up, and then the value for it for that population. So, for example if you've been building a game, and a lot of people have churned and you see from feedback that it's too difficult to play, you could set a remote config variable for the difficulty, so that likely churners could get a lower default value set for them, and thus would have an easier experience playing the game.
Selecting Notifications will take you to the familiar composer for messages to be sent using Firebase Cloud Messaging, but in addition to the usual options for picking target audience, you'll also get the predicted user group pre-populated as a user segment.
This allows you to target notifications at that user group. So, for example, for users at a risk of churning, you could send a notification with an enticement to continue using the app.
Creating your own predictions. You aren't limited to the built-in predictions cards, of course, and can create your own based on custom events that you set up in your app. In this case, you'll see a card that allows you to create a prediction.
And when you select it, you can then create a prediction for when your event will, or will not happen. This helps you identify users who are likely to engage in that conversion event:
So, for example, in the above case, whenever a user levels up in the game, the level_up conversion event is logged. Thus, you could create a prediction for players who may level up, and incentivize them to continue playing.
Then, once you've saved your prediction, over time a card will populate on the Firebase Console in the same way as the built-in ones.
And this card can be used in the same way as the others -- including targeting users with notifications and Remote Config.
Firebase Predictions is a Beta product, and we're continuing to work on it and improve it. If you have any questions or feedback, please reach out -- and for bugs and product suggestions, you can reach us at firebase.google.com/support.
Learn more about Firebase Predictions at firebase.google.com/products/predictions/ or dive straight into our docs right here.
We're just days away from the Firebase Dev Summit in Amsterdam! This means that all across the company, dozens of engineers and product managers are hopping on planes and saying to themselves, "Oh, whoops. I guess I better start working on that presentation."
Just kidding! As your conference organizer, I've had a chance to see a sneak preview of what's in store for you at the Firebase Dev Summit. And I feel confident telling you that this year's Dev Summit will be full of exciting product announcements, instructor-led codelabs, and way too many jokes about wooden shoes.
Now if you can't attend the Firebase Conference in person, never fear! We'll be livestreaming all of the talks from the main track in our YouTube channel, starting at 10:00 AM (Amsterdam time). And if this ends up being inconvenient for your time zone, that's okay. All of those talks, along with the in-depth sessions from our secondary track, will be recorded and posted within hours. So you can stay informed of the latest Firebase news while also getting a good night's sleep.
So while it's always hard to pick my favorite talks out of all of the great ones we've got lined up, here are a few that I'm pretty excited about:
Firebase overview and announcements: If you just tune in to one presentation, make it this one. This is where we'll be covering all of the major announcements around what's new in Firebase. Thought we were done with the announcement of Cloud Firestore? Nope! We've got even more on the way and you can find out about it here!
Actionable insights with Firebase: Speaking of new and exciting announcements, this suspiciously-vaguely-worded presentation might contain a few details about some exciting new developments with Firebase. Or it might not. You'll just have to tune in to find out!
Automating your app's release process using fastlane: fastlane has been one of the most popular open-source tools among mobile developers, helping to automate many of the tedious parts of releasing an app, so you can focus on the fun bits. If you're not familiar yet with fastlane, this is a great way to find out everything that it can do for you.
BigQuery for Analytics: There's a lot of amazing stuff you can perform with BigQuery and Google Analytics for Firebase, but it can also be incredibly overwhelming for mobile developers whose SQL skills might be a little rusty. Todd will be showing you some really useful tricks that you can do with BigQuery to make tackling those SQL queries a little more useful.
Write production quality Cloud Functions code Cloud Functions for Firebase is a powerful tool that you can use to help realize your goal of creating a truly serverless app. But they can sometimes be tricky to get right. And while Jennifer's videos are a great way to get started with Cloud Functions, Thomas and Lauren's presentation can help you move your Cloud Functions from "neat little parlor trick" to "production-level toolkit".
Of course, these are just my thoughts; you're the ones who want to know more about Firebase, so feel free to check out the talk that's of most interest to you! My only strong opinion is about where you can get the best stroopwafels. (Honestly, you should just get the cheap packs at the supermarket, because the fancy ones just aren't worth it.)
Today we're excited to launch Cloud Firestore, a fully-managed NoSQL document database for mobile and web app development. It's designed to easily store and sync app data at global scale, and it's now available in beta.
Key features of Cloud Firestore include:
And of course, we've aimed for the simplicity and ease-of-use that is always top priority for Firebase, while still making sure that Cloud Firestore can scale to power even the largest apps.
Managing app data is still hard; you have to scale servers, handle intermittent connectivity, and deliver data with low latency.
We've optimized Cloud Firestore for app development, so you can focus on delivering value to your users and shipping better apps, faster. Cloud Firestore:
As you may have guessed from the name, Cloud Firestore was built in close collaboration with the Google Cloud Platform team.
This means it's a fully managed product, built from the ground up to automatically scale. Cloud Firestore is a multi-region replicated database that ensures once data is committed, it's durable even in the face of unexpected disasters. Not only that, but despite being a distributed database, it's also strongly consistent, removing tricky edge cases to make building apps easier regardless of scale.
It also means that delivering a great server-side experience for backend developers is a top priority. We're launching SDKs for Java, Go, Python, and Node.js today, with more languages coming in the future.
Over the last 3 years Firebase has grown to become Google's app development platform; it now has 16 products to build and grow your app. If you've used Firebase before, you know we already offer a database, the Firebase Realtime Database, which helps with some of the challenges listed above.
The Firebase Realtime Database, with its client SDKs and real-time capabilities, is all about making app development faster and easier. Since its launch, it has been adopted by hundred of thousands of developers, and as its adoption grew, so did usage patterns. Developers began using the Realtime Database for more complex data and to build bigger apps, pushing the limits of the JSON data model and the performance of the database at scale. Cloud Firestore is inspired by what developers love most about the Firebase Realtime Database while also addressing its key limitations like data structuring, querying, and scaling.
So, if you're a Firebase Realtime Database user today, we think you'll love Cloud Firestore. However, this does not mean that Cloud Firestore is a drop-in replacement for the Firebase Realtime Database. For some use cases, it may make sense to use the Realtime Database to optimize for cost and latency, and it's also easy to use both databases together. You can read a more in-depth comparison between the two databases here.
We're continuing development on both databases and they'll both be available in our console and documentation.
Cloud Firestore enters public beta starting today. If you're comfortable using a beta product you should give it a spin on your next project! Here are some of the companies and startups who are already building with Cloud Firestore:
Get started by visiting the database tab in your Firebase console. For more details, see the documentation, pricing, code samples, performance limitations during beta, and view our open source iOS and JavaScript SDKs on GitHub.
We can't wait to see what you build and hear what you think of Cloud Firestore!