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) ♪