So, let's talk offline support. It turns out we don't live in a world
of perfect network connectivity. Your users live in a world of
elevators, tunnels, long plane flights and countries where roaming
data charges are crazy expensive and if you want your app to be great,
it should perform well even in situations where
your network connection might be spot... Wow, uh, this is taking a while. Um, how about let's roll the intro
while this is buffering, huh? ♪ (upbeat music) ♪ Okay, so if you're watching this video
because you're wondering how to add offline support to your app,
turns out that part's easy. On Android and iOS devices,
you don't need to do anything. Offline support is enabled by default. On the web, you'll need
to explicitly enable it and that's for a couple of reasons
I'll talk about later in this video but still, like a few lines of code. And if you're using the server SDKs, well, there is no offline mode. Honestly, if you're putting your server
into airplane mode, you might have bigger problems. I'm sorry he needs to go
on the plane with me. He's a licensed emotional-support server. And that's about it, just like that
you have offline support enabled. So are we done with this video?
Heck no. Let's talk about what it actually means
when you go into offline mode because that's probably
the more interesting part. So first off, let me back up and explain the general philosophy here
around offline support, which is that Cloud Firestore
is meant to work smoothly when your user goes offline for a few seconds, a few hours,
or maybe a couple of days. It's not meant to work
in offline mode forever. So if you're looking for a solution
that's offline first or you're expecting your user to stay offline for months
or years at a time and then occasionally go online
to back up their data, Cloud Firestore probably
isn't the right solution for you. So with that in mind, let's talk
about how offline support works. As your user interacts
with your app normally, they're downloading
all sorts of documents either through queries
or individual document fetches and those documents generally
stay cached on their device. So now let's say our user
goes through a tunnel and is disconnected from the internet. Well, they can still retrieve
any of this cached data that's been stored on their device, so your app will continue to work even if it sometimes has
to use old data. But how they get this data depends
a little on how you're fetching it. If you have a real-time listener setup, you'll receive this cached data
probably right away, but in the meantime,
your client library will still go out and try to fetch the most recent data
from the database. Now if you're online, you'll get back
that fresh data from the server and if that data is any different, your real-time listener gets triggered
a second time with this new data. But if you're offline,
this second callback, it just won't happen, and for a real-time listener,
this works just fine. I mean, if you think about it, these callbacks are designed
to be called multiple times. So getting called twice in succession, once with the cached data
and then again with the new server data should work perfectly fine
in your app. But if you're doing a single get call,
well here things get tricky. Get callbacks are supposed
to only be called once, so the Firebase SDK has to determine
whether you're offline or not and either give you the cache version
or try to fetch from the server first and then give you the cache version
only if that times out. And this is not as easy as it sounds because there are different levels
of being offline. I mean, there's obvious stuff
like airplane mode offline but then there's also, "I'm kind of going in and out
of WiFi range" offline, or "I'm going into a tunnel" offline, or "I'm connected to hotel WiFi but haven't accepted
the terms of service yet" offline. So depending on your situation,
you might get data back immediately or you might get it back
after several seconds. Joke's on you, hotel internet! This license agreement
is all the internet I wanted. Note that if you don't
like this behavior, you can always specify the source
of your data in the get call. For example, you can tell your app to always fetch the local cache data or to always fetch the server data. Oh, and one note
about fetching cache data whether you're doing it with a get call
or a real-time listener, is that you can query this cache data even if it's in ways that
you never queried it before. For example, let's say I ask
for the top 30 sushi restaurants in San Francisco, sorted by price, and then I also ask for the top 30
most expensive burger places. Later on if I go offline, I could still
ask for a different query like all restaurants in San Francisco
sorted by rating and that query will work
across my cache data. Now I won't necessarily get
the most accurate results. I'm only going to be able
to get at this cache data, so my top restaurants will consist of nothing but like expensive sushi and burger places but it'll still work. And then I can go ahead
and run any other query I want to against this cached data. So those are reads. Let's talk about writes. Now writes are interesting. Let's say you ask to make
a change to a document. Well, that data is stored locally
on the device, and, in fact, whether
you're online or not, your data will immediately
give the appearance that your change has gone into effect. Any real-time listeners
that are watching this data will trigger with your updated data. It's a little optimization
we do on the device so that from the user's perspective most writes look like they happened
with no latency. But, in the meantime, Firebase will still take
that write you've made and attempt to send it to the server. Now if you're online, then that write
will be confirmed by the server. Once it is, the Firebase library
will update your data for reals, it'll remove that pending write
and all is good with the world. But if you're offline, then that write
will stay there on the device and that's fine too. You can continue
to make changes to your data and these changes will all stay
locally queued up on the device. And anytime you want
to query your data locally, Firebase will take your original data
and just kind of replay all those queued up writes on top of it. Now you might think
this would slow down your app and you'd be right, but it generally
takes a few thousand writes for this slowness
to be noticeable to your users. So for the case where a user is offline
for the duration of a plane trip or a three day hiking trip
in the wilderness, you should be fine. But, like I said at the beginning,
don't think you can go offline for like several months at a time
and have a silky smooth app. So what happens when your user
goes back online? Well, all those writes
that you have queued up, will be sent over to the server. Now the database is smart enough
to apply these writes in the order that they were performed on the device. But when multiple devices
write to the same document, Firebase simply goes with a strategy
with the last write it receives wins. So, let's say my friend Pam and I are both writing
to the same restaurant document, I hop on a plane, put my phone
into airplane mode and I change my restaurant's name
to Todd's Tacos. Now I'm offline, so my change just
stays queued up on my local device. An hour later, Pam decides to change
our name to Pam's Pozole. She's online and so her changes
are made right away. A few hours after that, my flight lands, and I turn on my network access again. My changes are then sent down
to Cloud Firestore. Now when Cloud Firestore
receives my changes, it will overwrite Pam's, and our restaurant
will now be named Todd's Tacos, which is the much better name, even though technically speaking
my changes occurred before Pam's. Now honestly, this simple
conflict resolution is probably good enough
in the majority of situations. But if you do need something
more sophisticated than that, there's some strategies you can try
with security rules or Cloud functions and may be that's something
I can cover in a blog post one day. Make sense? Okay, let's talk about a few
lingering questions you might have and then mention a few gotchas. Okay, first question:
how large of a cache do we actually keep
to store all that offline data? So now this is something
you can change in your settings but the default value is large enough that you probably won't need
to worry about garbage collection but also small enough that
your users probably won't notice. I mean, here's the thing, all this data that you're loading
from Cloud Firestore, it might seem like a lot, but remember that we're primarily
dealing with strings, numbers, and JSON-y looking objects. These pale in comparison to binary data like images, sound,
and compiled code. Like if I cached 500 restaurant reviews
of 200 words each, I'm still probably talking about
say two-thirds of a megabyte. If I cached about 3,500 reviews, then it would be at the size
of a single photo. Still, if you're streaming
in data like mad, you will run up against
this maximum cache size and when you do we follow
the least recently used strategy. You probably hear this called
an LRU cache because as engineers we all love acronyms. So you're telling me it's an initialism and not an acronym
because I say the letters out loud? Well it just so happens I call it
a "Laroo" cache, so there. An LRU cache works the way
its name suggests. When we're deciding what documents
to remove from the cache, we'll look at the least
recently used ones and by used here I mean anything
from accessing it in a query, to writing to it, to fetching it directly. Whatever document
has gone the longest time without being accessed, that gets booted from the cache and we keep doing that until
you've got enough space again. Now as far as pending writes go,
those are never expunged. We'll keep them around
until you get connected again because that's important information
we don't want to lose. Okay, second question: why are you seeing
a lot of duplicate data calls? So there are two times
when you might expect your real-time listeners to get pinged
with duplicate data, particularly when you're online
and everything is working fine. First off, when you activate
a listener on a document, you get back
the cache document right away but then a short while later you get back the version from the server. Often this will be the same
as the cache version and you'd expect your listener
to get triggered twice, but it doesn't. Second, when you write to a document,
that change is made locally and triggers any real-time listeners
on that document, and then a short while later, when that change
has been confirmed on the server, you basically get back a copy
of the "new document" that's exactly the same
as the local version. But again, your real-time listener typically doesn't get triggered
that second time. So now in both cases,
Firebase is storing some metadata about your snapshot, fromCache, to indicate if this data is being served
from the cache or not and hasPendingWrites to indicate if this data still needs
to be written to the server. And, by default, Firebase
is smart enough to know that if your data looks
exactly the same but only this little bit
of metadata has changed, then it won't ping your listener
a second time and that's why you're not getting
a lot of duplicate calls. And most of the time this is probably
the correct behavior for your app. Now, you can change
this behavior if you want-- like maybe it's really important
for you to show on your UI that a write has been confirmed
by the server-- but, in general, I'd recommend
sticking with the default unless you have a good reason not to. Okay, third question:
what's up with the web, why is an offline mode enabled by default? That's two questions, should we--
All right, it's two questions, but we've agreed
we're going to answer it anyway. So probably the biggest reason web offline mode isn't enabled by default is that it's much more common
to see web pages available on shared devices, like your everyday
public library computer, and you don't want to be in a situation where you're accidentally caching
sensitive user data. So by making sure
it's not enabled by default, we're giving you a chance
to really consider whether this is what you want
and maybe potentially add one of those "Hey remember my data
on this device" kind of checkboxes before you go ahead
and enable offline mode. But the other reason is
that you still have to deal with issues like browser compatibility
and offline support in multiple tabs, which actually is something
we support now but only in an experimental flag
in the settings. So you might have noticed that,
unlike native mobile apps, when you turn on
offline support on the web it returns a promise that tells you if you are able
to successfully activate it. Now, if it doesn't succeed, then you get back
an error object explaining why either because it's not supported
by this browser or the app is already
open in another tab and you haven't enabled
that experimental setting. And then you can do something
like display a big old message box telling the user that they already
have this app open in another tab and they should go there, or they should go
and use a different browser. Okay, finally, let me call out
a couple of gotchas to look out for. First off, with document writes,
the biggest issue I've seen out there in the wild is that the callback to a document write doesn't get called until the write has actually
been confirmed on the server. And this is a big deal,
because I've seen a lot of cases where developers will bring up forms to add a new document
or edit an existing document and then dismiss that form
in the callback. They test this while they're connected
and everything works fine. But if their user goes offline,
this callback isn't called, the form never goes away, and it looks like the application is hung because your app is still waiting
for a response from the server. Now, I'd say most of the time
the right thing to do here is take that code you've put
into this callback and simply call it immediately
after your write. Remember, thanks to some clever work
in the client library, Cloud Firestore acts as if the data
in your write is applied immediately so you don't need to wait for a callback to be working with fresh data. Code that lives in these callbacks
should be reserved for the times when you want to be sure your write
has been confirmed by the server and it probably shouldn't
be blocking anything. Gotcha number two: as we noted
in the previous episode, transactions will fail
when you're offline, and this is kind of an intentional
feature of transactions. Remember, the point of most transactions is that you want to make sure you're dealing with up-to-date data
from the database-- when you're doing things
like transferring money between a checking
and a savings account-- and honestly this is just
one of those situations where it's safest to just fail rather than try
and get overly clever here. Batch writes on the other hand,
don't have this issue, go bananas. Finally, gotcha number three: you know how we say that every query
in Cloud Firestore is fast because we index everything? Well, that's not actually true
when it comes to the offline cache. On the cache write now
there are no indexes, so queries can take some time. The speed here depends a bit
upon the device you're using, the size of your documents, and the number of documents
in your collection that you've got to go through. Now again, if you use the cache
the way it was intended to be used essentially as a fallback
for new user goes through a tunnel, say, you're still okay. I mean, even a slow local search
is going to be faster than, say, trying to download data
over a nonexistent or flakey connection. But I've seen situations
where developers will download hundreds of thousands of documents when their app first starts up, then try to perform all the rest
of their fetches only through the cache either because they think
they'll save time or billing costs, and, in general, they just end up creating a worse experience
for their users. So this is not a strategy I would
recommend for most apps. So there you go, that's just about
everything you've ever wanted to know about offline mode in Cloud Firestore. Like I said, offline mode
is already enabled in native mobile apps, and the default behavior
generally does the right thing, so there's not really
any work needed on your part to ensure a good offline experience. But I still think it's probably
a good idea to know what's going on underneath the hood, or at least appreciate all the work that Cloud Firestore is doing for you. Good job there, Sport! Thanks for watching,
and we'll see you soon on another episode
of "Get to Know Cloud Firestore." ♪ (upbeat music) ♪