How do I Enable Offline Support? | Get to know Cloud Firestore #9

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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) ♪
Info
Channel: Firebase
Views: 60,642
Rating: 4.9494028 out of 5
Keywords: Offline mode, offline support, offline, support, apps, web, android, ios, Google, developers, user, connectivity, disconnect, app disconnection, network connectivity, enable offline, Firestore, Get to Know Cloud Firestore, Firebase, Firebase developers, Firebase devs, how do i enable offline mode support, offline mode cached data, offline mode firestore, offline mode support for firestore, enable offline mode, enable offline support, cloud firestore, Todd Kerpelman, GDS: Yes;
Id: oDvdAFP6OhQ
Channel Id: undefined
Length: 14min 10sec (850 seconds)
Published: Mon Jun 10 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.