Extending Firebase to the Web (Firebase Dev Summit 2017)
Articles,  Blog

Extending Firebase to the Web (Firebase Dev Summit 2017)


JAMES DANIELS: Are we all
excited for the summit? Yeah? Great. Well, as we just heard from
Sam, Francis, and Kristen, there are a ton of great
things happening in Firebase to provide you with the
tooling and infrastructure for an easy and powerful
experience for developers. Today, we want to talk
about how to extend great native experiences
to the mobile web. I’m James Daniels, developer
relations for Firebase. ERIK HADDAD: And
I’m Erik Haddad, UX engineer on Firebase. The past decade, we’ve seen
a native first approach to development,
where companies will opt to provide a superior
native mobile experience for iOS and Android while their
web experience suffers. It goes stale, or
it’s nonexistent. We’re here today to
show how simple it is to provide a powerful
and consistent experience for native and web users. In this session, we’ll play the
role of a publishing company exploring Firebase as a solution
to build a compelling website for our passionate users. JAMES DANIELS: So we’re going
to take off our Firebase hats for now and
introduce onSnapshot. onSnapshot is an
Android application that provides news and updates
on all things Firebase. Our parent media
conglomerate sought to disrupt the traditional
Firebase news space, and, boy, has it succeeded. We’re the top app on the
imaginary Google Play Store. Huzzah. Users like you love
the application, but wouldn’t you like
to be able to read the articles at your desk? What if you’re concerned
about space and resources on your phone? Well, we can work on a
mobile web solution for you. Our top concern is consistency
and a great experience. So I’ll be focusing on the
engineering components, and Erik, the UX
man, will be looking at those considerations. ERIK HADDAD: Thanks, James. Today’s most modern of web
apps are Progressive Web Apps, also known as PWAs, and they use
things called service workers to manage network requests and
emulate a native experience without being locked into a
single mobile operating system. So how will PWAs help us
create a great web experience? Our app has to be reliable. It has to be fast, and
it has to be engaging. So what does all of that mean? Reliable– progressive web
apps load instantly regardless of the network state. By pre-caching key resources,
we can eliminate the dependence on the network, ensuring an
instant and reliable experience for our users. Needs to be fast. 53% of our readers
will abandon the site if it takes longer than
three seconds to load. And once loaded, users
expect them to be fast. No janky scrolling or
slow-to-respond interfaces. Lastly, it needs to be engaging. Progressive web apps
are installable, live on the user’s home screen
without the need for an app store. They offer an immersive, full
screen experience with help from a web app manifest file
and can even reengage users with web push notifications. Now, the web app
manifest allows you to control how your app
appears and how it’s launched. You can specify
home screen icons, page to load when
the app is launched, the screen orientation,
and even whether or not to show the browser Chrome. JAMES DANIELS:
Makes sense, right? But how can we at
onSnapshot use Firebase for our progressive
web application? There’s a couple of things that
we need to accomplish here. We need to store
articles, visitor counts, cache the content, get some
good performance setup. So we won’t need a
back end engineer if we use Firebase, right? That’s the great thing about it. We don’t have one. We’re just two JavaScript guys. So we can just do front end. So, up first, we have the
storing and the comments in a scalable way. We have these
little nice mock ups of what we’re trying
to accomplish here building this application. So there is, of course, the
Firebase Realtime Database. That’s Firebase’s first product. It’s a JSON store. Clients connect
through web sockets. SDKs are provided, so
no back end needed. The validation, all the rules,
are there on the server. Now, all of that complexity
of synchronization, conflict resolution,
infrastructure is gone, and it binds directly to the UI. There is also Google
Cloud Datastore. So we’re storing articles,
text, and a bunch of comments underneath of it. Well, what if we get a spec
to add replies to comments, and then replies
to those replies, and replies to
replies to replies? We also need to have
an author view, right? So that’s going to
be complex querying. That’s very difficult in
the real time database. We have to adjust our
schema, move around our data, and how we’re thinking about it. So with Cloud Datastore, it’s
more like a traditional data store. But now we go back to the
problem of needing a back end developer. It has some properties
that are useful, though, so the shallow queries. And then, also, we can
do things like ands, so we could put together
that article page, and pagination’s easier. So what if we took the
best of both worlds? And Firebase has just done
that with the new product Cloud Firestore. ERIK HADDAD: That’s
right, James. Cloud Firestore for Firebase is
quite possibly the best cloud database for mobile and web. It’s easy to use. It’s real time, highly
scalable, and has rich querying capabilities. We’re able to use a
collection document model, structure our data
hierarchically, and use those rich data types. Here’s a sample document. A document can consist
of many key value pairs with available types as
arrays, booleans, bytes, date and times, floating point
numbers, geographical points, integers, maps, nulls,
references, and, of course, text strings. And here’s a sample collection. A collection is a
grouping of documents, and a document itself
can have subcollections. Hey, James. I think this will work nicely. JAMES DANIELS: I think so, too. So that’s storing our
articles and comments. Now, what about our
live viewer count? We chose Firestore for
the texts, the comments. But is it really suitable
for this live visitor count, or could we actually use
the best of both tools and go back to the
real time database? So there’s a couple
of properties here that make it really awesome
for this live viewer account, namely, presence indicators. So when we establish
this WebSocket connection to the database and query
the data, get the updates, we can provide on disconnect. And I think this will
really help our problem. So how do we reliably, you
know, decrement the counter when someone leaves the page? When you do on
disconnect, the action that you tell it to
take on the database is stored on the server. So when that socket disconnects,
the action is executed, and you can update the database. So what are we storing? Well, there’s Firebase
Anonymous Authentication. I think that will work. So here, you know, we
tend to think of users as needing a
username, a password, or to go through an
OAuth flow or maybe XML if you’ve got some
corporate stuff. But here, any visitor
that hits our site is going to get a user ID. And we can use that
in the database. We can use that in
our database rules. And if they do log in to
create a full account, link their username, password,
then those identities will merge. So I think, for our use
case, this would be perfect. Now, caching the contact,
making it fast, right? So the thought here is
that we could actually use Cloud Functions for Firebase. So this would allow us to serve
our JavaScript application– we’re just front-end
engineers, right? We don’t need to build servers. We can just have a
JavaScript application. We can use Cloud Functions to
serve that dynamic content. We can pre-render the pages. We can improve our SEO. One of the problems
with search engines is when they hit a
JavaScript page, that’s a lot of complexity that’s
not right there in the HTML straight away. So if we pre-render it, those
spiders can crawl our site. And then social sharing, right? So when you post something
on Twitter or Facebook, then you want those images. You want the description
to come across. And that won’t happen if it’s
a JavaScript application that doesn’t have that rendering. And then the integration
with Firebase Hosting– well, that’ll let us hook up a
domain name for free with SSL. ERIK HADDAD: And that ties into
what I’ll be speaking about– serve the web site securely. So once our site is
ready for production, we can connect our
own custom domain name to Firebase Hosting. And Firebase handles
that configuration for us and automatically provisions
an SSL certificate, so all of our content is
served securely. And, remember, PWAs need
to be served over HTTPS. This is a big win. Lastly, persist
data for offline. Now, PWAs are the
new hotness in part due to their offline
capabilities. Cloud Firestore was built
for offline persistence, so an interim loss
of connection– say, while you’re going
through a tunnel– or an extended
amount of time– say, a long haul flight–
will not interrupt the complete
functionality of your app. The service worker uses
knowledge of our application files to efficiently
serve code and content from the offline cache
and act as a proxy to all these requests and ensure
that unnecessary traffic is avoided. JAMES DANIELS: So Firebase has
provided us with the solutions that we need to build this. So let’s get coding. I am actually fond of AngularJS. I really like its
integrations with ARCS.js. I like functional programming. So lets put together our app. First off, we, of course,
have our home page, where we have that
list of articles. And then we jump in, and then
we see an individual article. So let’s walk through this. So the first thing
we’re doing is we’re actually pulling in
Angular Firestore from Angular Fire. Angular Fire is a plug-in. It’s the official Angular
plug-in for Firebase. So what it’s going
to do is it’s going to very lightly wrap all those
calls to the Realtime Database to Firestore. It’s going to wrap them in
the concurrency primitives that work well in Angular,
namely, observables. ERIK HADDAD: Are the
SDKs pretty close? JAMES DANIELS:
They’re super close. They’re super close. So, here, we have an array
of document change actions, and that’s just wraps, that
doc, the data snapshot. And it’s an observable. We inject Angular Firestore
into our constructor. And then we grab the collection. So it’s really, really
similar to the SDK. We just have this
extra snapshot changes and a little bit different
syntax around the query. So we’re grabbing the
collection of articles that’s going to wrap this. We then pass the ref. Add all the parameters that
we want into this query. And this is a great
thing about Firestore is this could be
very complex, right? We could go over many,
many different fields. And then we call
.snapshot changes. So .snapshot changes is what’s
going to hook up the listener and then wrap it
in an observable. Here, we’re doing a similar
thing for the article view. This, though, if you see, is
an observable of document chain action, not an array of
them, so a single article. Now, we’re using
the Angular router so that each one
of these articles has an individual, sharable URL. So you can copy, paste it. You could reload the page. You could put it into your
favorite social network and share it. So we pull in the
activated route from there. And then this is RxJS. Who here has used RxJS? A couple of yeahs? ERIK HADDAD: Some hands. JAMES DANIELS: Who
really groks RxJS? There’s a lot of
complexity there. Awesome. So this is a switch map. So let me jump into a
switch map real quick. So a switch map takes a data
source, an observable– say, in this case, A– and it
takes a second observable, and it maps them in such a way– so, you see, there’s the
first one, A, 1, 2, 3, so red 1, 2, 3. Blue 1, 2, and then
yellow triggers. Well, where’d 3 go? So this is really
important for our router. Because if we
navigate to a page, we establish a connection
to the real time database or Firestore, and
data is streaming at us. We want to abort that stream
if the user switches URLs. We don’t want that data to be
streaming onto another article and getting all mixed up. So that’s what the switch
map accomplishes for us. So you’ll see that’s a lot. Route params– so
you’re pulling out the parameters from the
router, and then you’re switch mapping to
something else. Now, like I said,
switch map is a bit different than a regular
map, because you’re mapping it to a new observable. And, here, we’re actually
querying that doc in Firestore, and then, again, the
snapshot changes onto it. Now, you could do this in
any web framework, right? I’m just partial to Angular. So in the view, we
have this async pipe. So this is the article view. Very basic. We’re going to be
showing the title. We’re going to be showing
the author details and the date and the body. So this async pipe is going
to wait for that observable to trigger. So that kind of lets us idle. We can put a loader on
there or what be it. Then we grab the
payload data from it once the async has resolved. And if we are going over
all of the attributes here, all the keys, that’s a lot of
code to copy, paste all over. What if we change one of those? So we can actually assign
that to the variable article within the scope. So that’s it. We go over. We print the article title. Then we go again
over the author. The author’s a reference to
another point in the database. So we actually do that async. And, of course, we link to
the article page from here. And the cool thing is these
all magically are hooked up to the real time
database or Firestore, and these are going
to be tripping, and the view is going to
be changing as the document changes. ERIK HADDAD: So what
about the comments? Now, Firestore has proven
great for our articles. Let’s use subcollections
to the comments from our passionate users. I think I’ve seen enough code
and watched enough Firebase YouTube videos to handle this. JAMES DANIELS: All right. So, here, we’re going to be
introducing a new RxJS concept called the behavior subject. So we’re actually going to
be grabbing the collection, going on a behavior
subject for it. And how a behavior subject works
is it’s like an observable, but it has an
additional property. Namely, it has a concept
of a current value. Normally with an observable,
if it’s been firing, and then you subscribe
to it, you’re not going to get
the previous events. So, here, with this
marble diagram, you can see I initialize
a new behavior subject with the green. And when I subscribe,
I get that, even though no event has fired. Same with the yellow, right? So a second subscriber attaches,
and then it trips that. It fires down in. So this is very
valuable for when we want to keep around
a reference to something that we may need later that
we haven’t subscribed to yet. Also, things like authentication
keep around the user. So we have this behavior subject
for the comment collection. So we’re just doing a map here. So we’re taking
those parameters. We’re mapping the parameters,
and we’re pointing to a path in the database. We’re not calling
snapshot on it yet. We’re not getting
that observable. It’s just pointing
somewhere in the database, because we will need
that reference later. I then take that map and
start pushing the data into that behavior subject. Now we can actually switch
map on that and do the .value changes so that we can
render our comments. Finally, we’re going
to do sign in, right? We want people who are using
the application to comment. We want them to sign in first
so we know who they are. So, here, that’s
very easy, very much like the regular Firebase SDK. It is. It’s just wrapping it lightly. Now, when we log in
here, it’s actually going to trip this
observable, afAuth auth state. So that’s going to trip
whenever the auth state changes. So we can map that to
actually get the user’s profile somewhere in Firestore. And then, again, we
can start piping that to a behavior subject. So that’s great so that,
anywhere we need that profile, we don’t have to be
already listening for it, and we don’t have to assign it
to a temporary variable, stuff like that. Like I said, if you
navigate away from the page, you can get into a bad state if
you’re doing stuff like that. And, finally, we
can add a comment. So, here, we actually
get the value. So we’re actually getting the
current value of that behavior subject. Now, this is a lot
better than assigning it to some instance variable. And here’s what our
Firestore console looks like. So we have, here on
the left, the document. So this is our article. Then we have a collection of
comments underneath of it, all the comments and then the data. And the cool thing
about Firestore is this is all shallow. So with the real
time database before, this would have– every time
we asked for the article, it had to come with
all the comments. Now we can do things like limit
the comments and page them. ERIK HADDAD: That seems
pretty straightforward. A lot of complexity,
very little code. And we didn’t have to
worry about any back end infrastructure. Now, for live view
counts, let’s say we use Anonymous Authentication
to get a unique ID for our readers to
sum up how many are viewing at one given time. And we’ll use Cloud
Functions to be the true source for
incrementing and documenting those view counts. And we’ll use the Realtime
Database for storing our values and providing presence. James, can you show me
how easy this will be? JAMES DANIELS: I can
take a stab at it. So, here, what
I’m actually doing is I’m listening, again,
to that auth state. I have Angular Fire
auth auth state. Here, I’m doing a filter on it,
so I only care about the events where the user ends up
being null, so no user. I then subscribe to that. And whenever that fires– the user has been signed out,
or the user doesn’t exist– then we will sign
them in anonymously. The cool thing is we then
get the user identifier. And if they go to
comment, and they hit that Sign In
with Google button, then it will merge
those two identities. So that will, again, trip
the Angular Fire auth state observable. So, here, we’re actually going
to be introducing a new RxJS concept. So we really need the
route parameters, right? We need that ID of the article,
and we need the auth state. So with the combined
latest, what we’re doing here is we’re waiting for
both of those to fire, and then we’re
mixing them together. Right? So the first one– say
our router resolves before the auth state does. That doesn’t fire an event. When both of them resolve
they start firing an event every time one changes, but
it includes the previous value of the other. So once we’re in here,
we now have fully resolved the parameters So
we can get the article ID, and we have the
user’s auth state. And we filtered it,
if you noticed before, so that we’re only listening
for when the user logs in. When we say sign in anonymously
or a Sign In with Google, it trips us again. So we’re going to
map that, and we’re going to look up the
ref in the database. And then we’re going to store
that as a visitor ref behavior subject. Then, on that visitor ref,
we’re going to filter it, and we’re looking for it
not being empty, right? So then we subscribe. We set the value as true
in the real time database, and that’s a promise. So then we put that on
disconnect and remove the data if the client disconnects. So what this
accomplishes is the user gets logged in anonymously if
they’re not already logged in. They get a user identifier. That user identifier
is then written to a list in the real time
database underneath the article ID. And then, if the web
socket ever terminates– say they close the web browser– then it reliably deletes
that user identifier from that database list. Now, we have one more case. What if they navigate away? Well, the socket to the real
time database is still there. It’s not going to trip
that on disconnect. So I have an onDestroy
on that component so that if they navigate
away from this page then it’s deleted. So this is what it ends up
looking like in the real time database. We have the article visitors,
the article ID, and then all the user IDs
that are currently looking at that article. ERIK HADDAD: So it
truly is anonymous. JAMES DANIELS: Indeed. So my fingers are tired
from typing all that. Could you take over for this? ERIK HADDAD: I
think I can get it. All right. So let’s try using two cloud
functions for incrementing and decrementing. Now, one that is triggered on
a create event in increments and one that
decrements on delete. Using rules, we’ve locked
down the admin ref. So only functions can
write a view count value. We don’t trust the client. We’re going to use the
server as the true voice. From here, we get
the root reference. We get the child article by the
article ID and transactionally increment the current value. Now, decrementing is very
similar but transactionally reducing the current
value by one. And there we have it– an event
driven transactional counter. James, does that work for you? JAMES DANIELS: Yeah. Works for me. And best of all, we didn’t
need to spin up servers for it. We don’t need that back end
engineer that we don’t have. We’re just JavaScript
guys, right? ERIK HADDAD: Great. JAMES DANIELS: And this is
what it ends up looking. So we have those
visitors on the article, and then it bubbles up to
this article view count. And that’s the
real time database. So we’re able to
subscribe to that, and this is what it looks like. So, again, we switch map
on the route parameters, and our component’s getting
a little messy here. Maybe we’ll do a refactor pass
where we’ll pull these out into other services. But we just listened to
the value changes on that. Value changes are just like
snapshot changes in Angular Fire, but they
include less metadata so we don’t have to unwrap
the payload and the ID and all that. It’s just going to subscribe
to that value in the database in Fire every time. Now, adding it to the
view is super simple here. We don’t have to
unwrap anything. So we just say being
read by view count async, so we use that async pipe to let
it resolve, and then viewers. So now let’s render
our application. So we built it. It’s working. ERIK HADDAD: Wait,
wait, wait, James. Will this approach be fast and
optimized for search engines? JAMES DANIELS: Oh, well, unless
we’re doing any server side rendering, no. We have a good web site. It works locally. ERIK HADDAD: OK. JAMES DANIELS: So, well,
let’s go ahead and pre-render our content. Let’s use Angular Universal
so that our pages are really, really fast, pre-rendered, and
Google and other search engines can actually crawl the content. And when we share
those links, we’ll get those image previews,
the description, page title. So here’s how we’ll do it. So, here, we’re writing
a little server file. Here, we’re actually pulling
in the Angular Express Engine. We’re providing the module map. We’re pulling everything
from our main bundle that Angular has compiled down. So we just created a server
version of our application, gave it a different
injection point, and then we’re actually
using Express JS here. So we make a new Express app. We attach the HTML engine,
provide all the Angular stuff. And then, finally,
on any GET request– well, this is our cache
control header right here. So we’re actually using Firebase
Hosting’s distributed CDN. And then we render our index. So how do we get that
into Cloud Functions? Well, there’s a lot here. So if we’re just
pulling this in, and we put this into
Cloud Functions, my Angular project
has, what, like, 100, 120 npm dependencies
right out of the box? And most of them aren’t used. So what I’m going
to do is actually compile it down with Webpack
so that I have one big file. Once I have that
file in the top here, I have my Cloud Functions
code, so I’m just going to grab the application
from that compiled down server file– call it Firebase–
and then I’m going to export that as
Angular Universal and as an HTPS function. And then, in my
Firebase JSON, I’m going to put the proxy to
that function on any request. So I just deployed that. It’s up. Why don’t you give it a view? Pull out your phones. Check it out. We got onSnapshot.com. Let’s see how high we can
get that live viewer count. ERIK HADDAD: Now, I
saw a lot of code. And I’m a visual guy, so let’s
dive right into the web page. Now, as I start here, I’m
going to kick off an audit so I can take you through local. Now, this is going to go through
and rate the progressive web app, the performance,
accessibility, and best practices. And it’ll take about
30 to 40 seconds. So let’s switch over
to the other tab. Now, here you can
see the articles in line on our home component. Let me refresh the page,
and you’ll see it’s loading and asynchronous. It was so fast, you couldn’t
see that it was asynchronously loading in the
author, et cetera. Now I’m going to click through
to one of the articles, and you see how fast that was. And then we have some
comments down here. I’m going to sign in with
my Google account here. And you see auth kicked
in, recognizes who I am. Hello, audience. And I’m going to add
my comment there. All right. And up here, you’ll notice
there are two viewers currently on this page. OK. Now I’m going to
switch over here. And now we’ve seen this
all in online mode, and let’s take a risk here. Let’s go to offline. All right. We’re now offline. If you don’t believe me,
there’s the disconnect, OK? So our network is offline. All right. I’m going to click through,
and wouldn’t you know it? It works. All the content is still there. OK. And we’re going to go back here. Click through. And even the one that we
haven’t visited shows up. OK. And we’ll go back
to online mode here. Refresh it. And if you ever want to toy with
the service workers, et cetera, and see the values of the
manifest in this Application tab in Chrome, you can
view all the values here and clear it, et cetera. All right. So in the results, with
our remaining time, we’ll talk about some of the
results from this Lighthouse. So it will go through,
and as we said, we want the app to be fast,
reliable, and engaging. So here we are testing
it on 3G connections. It needs to be fast, no matter
where in the world you are and what kind of latency
you have on your network. So it’ll go through
and check the latency. It’ll check the
service worker and all that it’s served over HTTPS. It will check the painting. So it’s engaging, like we said. If it doesn’t load within
three seconds, 53% of our users will abandon the site. So it will go through
the meaningful paints. And it will check
the accessibility, because we want as many users
to use our app as possible. So that’s looking good. And best practices–
there are other things like using WebP for your
images, or compressing your text, et cetera. So Lighthouse is a really great
way to really meter your app and make sure you’re
following best practices. OK. And then we have an
author view, as well. So have fun playing
around with that. OK. So let’s review. So here we’ve used Cloud
Firestore, Cloud Functions, real time database,
hosting, and we use Angular and Angular Universal. You can use React,
Preact, whatever, in order to accomplish your three way
data binding and your server side rendering. JAMES DANIELS: There’s
all kinds of stuff that I am really excited to
add before I go on my refactor. I’d really like to
get FCM in there, send notifications
when users comment or reply on your comment, or
when new articles come up, really engage things. I really want to get an RSS feed
in there, maybe an AMP version so that we can get a little
higher in the search engines, and subscribe users
to our mailing list. ERIK HADDAD: Whoa, whoa, whoa. I think we’re about out of time. These folks have a
busy schedule today and have other talks to get to. So let’s leave it there for now. [APPLAUSE] JAMES DANIELS: So
for those interested, we actually open-sourced
this right before this talk. So you can check out the
unrefactored code base. Very, very messy. A lot of code in one component. [MUSIC PLAYING]

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *