Whoa! Is it the end of the year already? It looks like 2018 will be upon us
before you know it; but I'm sure like most of you, it's going to take me three
months before I remember to stop writing 1513684800 on all of my timestamps.
Looking back at 2017, I can say it's been an exciting year for all of us here in
Firebase land. We launched a whole boatload of new features, attended dozens of
conferences, welcomed several dozen new team members, met hundreds and hundreds
of developers from all over the world, and had one crazy dance party.
New Stuff!
I don't know if any year could have topped everything we accomplished in 2016 in
terms of new features, but 2017 certainly gave it a run for its money. Here's a
quick overview of just some of the great new products we released over the past
year.
We released the beta version of Cloud Functions for
Firebase, which lets you run server-side code in response to changes that
happen within your app. I know the team who worked on this was very excited to
see it launch, and this really helps us realize the whole "serverless computing"
vision that Firebase has been working towards for the last several years.
For all you game developers out there, the C++ and Unity SDKs graduated from
Beta to official General Availability status. Woo hoo!
We also announced support for games for Firebase Test Lab for Android, so you
can help squash a few more bugs before your game hits the Play Store.
Over in the world of Analytics, we released StreamView
and DebugView to all of our developers, along with the ability to create
reports for custom parameters, and worked closely with the AdMob team to help
you see important data about your ad revenue directly within the Analytics
console.
We made several dozen new best friends in San Francisco and Cambridge by joining
forces with the Fabric team.
This has resulted in some nice improvements to Firebase, including adding Crashlytics to the
Firebase Console, and creating some new reports for the Analytics dashboard. We
were also able to add Phone Authentication for
Firebase, which allows your users to sign in to your app through a simple
text message.
We began the process of open
sourcing many of our client and server SDKs. We also released the Admin SDK
for Go, for all of you Go fans out there (who, if they're not calling themselves
Goficionados, are missing out on a golden opportunity here1).
We announced Firebase Performance
Monitoring, so you can make sure your app is performing well out in the real
world, not just at work with your fancy office wifi.
We introduced Cloud
Firestore to the world, our highly scalable document database in the cloud.
Cloud Firestore contains many of the same features that made the original
Realtime Database successful, along with a number of improvements -- including a
more sophisticated querying language, shallow queries, and a number of
scalability improvements on the backend.
We added platform
specific overrides to Firebase Cloud Messaging, so you can customize
messages for your iOS, Android, or web users while still writing one single call
to the FCM service.
To help you make smarter and more data-driven decisions around your app, we
announced the beta version of Firebase
A/B testing. This feature, in conjunction with Remote Config and
Notifications, allows you to A/B test both app features and push notifications,
so you can drive growth in the metrics that matter to your team.
And what Google product would be complete without machine learning? Just a few
months ago, we announced Firebase
Predictions. Predictions allows you to create dynamic groups of users who
are predicted to churn, spend money in your app, or trigger special conversion
events, so you can deliver unique experiences just to them, either through
Firebase Remote Config or Notifications.
Conferences!
Phew! That was a lot! And while all of these new features and product
announcements are great, 2017 wasn't just about new launching new products; it
was about meeting all of you, too!
Some of our favorite moments came from attending developer events around the
world. For a lot of our product team, getting the chance to meet with real
developers, hear about how they were using Firebase, and all of their
experiences, both good and bad, was thrilling and educational.
The Firebase team went to over 136 events in over 120 countries all around the
world. And this included everywhere from Kuala Lumpur to New York City...
...to Manilla…
...to Google I/O down here in Mountain View...
….to Japan…
Yes, I still have all my fingers
...to Washington DC2,
to, of course, our big Developer Summit in
Amsterdam. Fun fact: One attendee decided to bike all the way from Poland to
Amsterdam just to see us. I think he earned an extra stroopwafel for that.
How were we able to accomplish this amazing task while still getting our work
done? Mostly because we had Casey stay home and do all the work for us while we
were gallivanting around the world. Thanks, Casey!
Sure, he looks sad and downtrodden, but deep down inside, he's really quite
happy to be doing all of our work for us.
More Favorite Moments!
And some of our favorite moments didn't have anything to do with official work
duties at all! Here's a few other highlights from Firebasers all around the
world.
The San Francisco team continued its traditional annual observation of Take Your
Pineapple to Work Day, because we believe it's important for all pineapples to
discover their true potential.
Sadly, most of their dreams were crushed by Piña Colada Day, which was
scheduled for the following afternoon.
Firebase hosted its second annual hack week, where Firebasers from around the
company worked on feature improvements or fun side projects using the Firebase
platform. We saw a number of fun hacks, including a coffee shop kiosk, a video
doorbell, and some nifty improvements that might make their way into Firebase
security rules someday soon.
Sam Stern dressed as an elf. And somehow managed to still look intimidating.
Amber from the Cambridge office had the privilege of hosting the Cambridge Women
Techmakers conference and emceeing a panel with some of the area's top female
entrepreneurs.
We all made it through the eclipse with minimal eye damage.
Alex -- our Cloud Firestore Product Manager -- had a baby! Also, Alex -- our
Cloud Firestore Technical Writer -- is expecting a baby! Basically, if you're
named Alex and working on the Cloud Firestore team… you might want to invest in
some onesies.
Speaking of which, Jake on the Fabric team also had a baby! So cute3!
And Jake isn't alone. The folks in the Cambridge office had 7 babies total in
2017!
The Crashlytics and Performance Monitoring teams went to an offsite where they
learned about street art and made a kick-ass Crashlytics logo. Because when
you're building a crash reporting tool, the most important thing is to have
street cred.
In cute animal news, the team in Cambridge got a new puppy, allowing us to meet
our ambitious belly-rubs-per-employee goal for Q4.
Abe rescued a stray kitten he found in an alley behind his house. He's hosting
the kitten right now at his apartment, and while he claims this is only
temporary, it's been that way for three months. So, to summarize: Abe adopted a
new kitten and it's his forever.
And Annum in Cambridge adopted Juno! Juno is a Maine Coon cat, which is
generally known as the largest domesticated cat breed.
As a cat owner myself, I'm just going to say: Invest in some of those
sticky pet-hair rollers. You'll thank me later.
Anit on the Crashlytics team, worked the Firebase tent at I/O and got
married! He won't tell me which event was more exciting, but let's be honest;
you don't get a cool "Staff" T-shirt at a wedding, amirite?
The Cloud Functions team built a fireplace in the office. Although after some
discussions with the fire marshall, who's a total wet blanket4 when it comes to things like open flames,
they had to settle with a video fire.
Rich shaved his beard for charity. I'm pretty sure he raised a dollar for each
whisker shaved.
Rich #3 looks like he's about to sing the entire Gilbert and Sullivan
catalogue
Firebase hosted its very own Gingerbread House contest. Along the way, we
learned that gingerbread baked specifically for constructing houses is only
slightly less tasty than actual drywall.
That's a Wrap!
With that, most of us are taking several days off this holiday season to spend
some quality time with family and loved ones. We look forward to starting up
again in 2018 with renewed vigor and more ways for you to build successful apps.
Until then, we hope you take some time off as well to relax and recharge, and
have a Happy New Year, from all of us on the Firebase and Fabric teams!
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.
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:
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():
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 is started.
LiveData is observed and becomes active, invoking its
onActive() method.
Database listener is added.
Data is received; UI is updated.
Device is reoriented, Activity is destroyed
LiveData is unobserved and becomes inactive, invoking its
onInactive() method.
Database listener is removed.
New Activity is started to take the place of the original.
LiveData is observed and becomes active again, invoking its
onActive() method.
Database listener is added.
Data is received; UI is updated.
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.
How do we prevent this unnecessary query?
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!
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:
Set alerts for
when usage reach certain thresholds
Create
graphs of various metrics for better insight into performance
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.
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.
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.
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:
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:
// 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.
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:
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.
What if a LiveData transformation is expensive?
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.
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.
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.
There's still room for improvement!
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.
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:
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:
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.
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.
What can Android Architecture Components do to help?
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.
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:
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.
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:
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.
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?
There's no chance of forgetting to remove the
ValueEventListener on the DatabaseReference, which
would cause an Activity leak. The LiveData object is
lifecycle-aware, so it knows how many observers are watching it, and will
automatically remove the database listener when there are no more observers (it
has become "inactive").
If there's a configuration change in the Activity, it will
immediately observe the most recent DataSnapshot from the
LiveData used in the prior Activity instance. It will not
have to wait for another round trip with the server in order to start rendering
that data. There's no need to use
onSaveInstanceState() with LiveData.
Increased testability and readability of the individual classes, because
they now have distinct purposes:
MainActivity is responsible for knowing how to draw the UI.
ViewModel holds all the UI data for the MainActivity.
FirebaseQueryLiveData maintains the actual data, while also
deciding whether or not the app is listening to that data in the database,
depending on whether or not our UI is active.
How can we improve this?
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:
Analytics:
Track valuable player data, including how they're interacting with your game,
how much time they're playing, how long they take to complete levels, how many
in-app purchases they're making and much more
Dynamic
Links and App
Invites: Allow your players to create and share content with their
friends
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.
Like Robo tests? Then you'll love Robo scripts!
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!
Screenshots of a feather cluster together
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.
In with the new toys, out with the old
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!
Stay in touch - send us a holiday postcard!
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 support for 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:
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.
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.
Linting TypeScript Code
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:
Or you can disable the rule globally by removing the rule from tslint.json.
Lifecycle Hooks in the Firebase CLI
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:
Compile markdown, API docs, or Babel code in Firebase Hosting before deploys
Prevent deploys if git has outstanding changes
Maintain a git tag for the current version running in production
Announce changes to production in your team's Slack channel
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:
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.
Before you begin
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.
Writing data
To write data from the command line use the firebase database:set
command:
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:
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:
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!
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.
It's also super handy for quickly triggering Cloud Functionsdatabase
triggers, so you don't have to type in stuff at the command prompt every
time you have something complicated to test.
Reading data
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:
firebase database:get /messages
To save the output to a file, you can use a shell redirect, or the --output
flag:
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
Pushing data
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:
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:
firebase database:remove / --confirm
Copying data between projects
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:
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.
Taking a shortcut with bash functions
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
Be creative!
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.