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.
LiveData
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:
MediatorLiveData
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.
FirebaseQueryLiveData
onInactive()
onActive()
/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()
Activity
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/