How Do I Paginate My Data? | Get to know Cloud Firestore #7

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
TODD KERPELMAN: I'm Todd Kerpelman, and this is a five pound gummy bear. Now, I'm going to eat this entire confectionery to make a point. You see, this giant gummy bear is a lot like your data. You might have a lot of it, but chances are, your users only need a small portion of it to satisfy their needs. And by giving them more data than they actually need, you could create problems, either for yourself or for them. But I can tell you're not convinced yet, so let's keep going. [MUSIC PLAYING] This was a terrible idea. [MUSIC PLAYING] So let's dive into pagination-- or, if you're not into technical jargon, how to split your database results into lots of little chunks so that you don't fetch all of your data at once. Now, if you've been following along with our previous episodes, you probably have a good idea of why it's important to paginate your data. I mean, my restaurant review app might have 50,000 restaurants all located in Tokyo, but if my user is doing a search for the best tempura in town, I probably don't want to send overall 50,000 documents. For one thing, that's going to kill their data plan faster than you can say overage charges may apply, and it's going to be costly to you, the database developer. Remember that you get charged per document read, so if you send over 50,000 restaurants and it turns out your user never scans past the first 20, well, hey, guess what you're still getting charged for those other 49,980 database reads. So a much better solution here would be to send your user the first, say, 20 restaurants, then send over another 20 when they indicate they need more, either when they explicitly click a next page button or when you've noticed that they've scrolled down to the bottom of their current results from one of those, like, infinitely scrolling table dealies. So let's look at how we might do this in Cloud Firestore. Now, I think the one important conceptual thing to understand when it comes to pagination is that you're not saying to Cloud Firestore, hey, remember that query I asked for earlier? Well, now send me the next 20 results. Instead, it's really more like you're creating a bunch of different and essentially unrelated queries, but each one of them just so happens to pick up exactly where the previous query left off. Now, luckily, the Firestore libraries have several methods in them that makes it easy to create these unrelated but basically sequential queries, so let's take a look at that now. Suppose my user is searching for the best tempura restaurants in Tokyo. Well, I'll probably start with a query that looks a little like this in the client. We get our tempura restaurants in Tokyo and sort by rating. Now, this query will probably fetch over 1,000 documents. So let's tackle the easy part first of limiting our initial results here. This is essentially done by adding on a limit to your query-- something like this. And there you go. Just like that, I am already saving tons of data by only loading up 20 documents for that first query. But now, what happens when my user decides they want to see the next 20 restaurants? Now, as I said earlier, I can't tell Cloud Firestore, OK, now give me the next 20 records from this exact query-- at least not using the client libraries. So what we're going to need to do instead is basically build a new query. It's going to look almost exactly like the previous query, where we're fetching tempura restaurants in Tokyo sorted by rating, but we're going to ask Cloud Firestore to start at exactly the right place so that we happen to fetch the next 20 records in our query. So we can start by generating a new query from scratch, like so, but then add a start after method. Now, with a start after method, I can pass in an array of values to use where, if they match up with the fields in my query in the right order, Cloud Firestore will grab the next document from the appropriate spot in the index. Now, this call would work for fetching the next 20 documents in my query, but there are two ways I can make this a whole lot easier to implement. For starters, regenerating a query from scratch like this? It can be kind of a pain, particularly if it's a user-generated query and you're relying on some UI elements that are no longer on screen, and having to remember all that information to regenerate a query from scratch every time is kind of a hassle. So instead, you can just take your existing query and simply add on a start after method. This will generate a copy of your original query with the new start after values, and then you can assign that to a new variable or simply overwrite your existing query. And yes, this works even if your old query already had an existing start after parameter. So now all I have to do is keep track of my current query in a property or field, then call start after to fetch new batches, and I am on my way to easier pagination. OK, now, as for the second improvement, see this array of values? It's a little tedious to have to track this for every different query I'm making. It's also a little error-prone. Like, if I were to get the elements in this array wrong, the SDK would throw a fatal error on me or just give me some very weird results. But there's a bigger problem, which is that if I've got 1,000 tempura restaurants in Tokyo, it's very likely that a whole bunch of them are going to have an average rating of 4.9. So asking to search for the document right after Tokyo, tempura, 4.9, might skip over a bunch of 4.9-rated restaurants that just weren't included in that first batch. So instead, what the Firestore library lets you do is pass in an existing document as the start after parameter instead. Now, Cloud Firestore will analyze that document and figure out exactly what values it should be searching for. And it's smart enough that if it sees multiple documents with the exact same values, it will start with the one with the document ID that's right after the document you specified. So you don't need to worry about accidentally skipping documents. And as you can see, the nice thing here is that I no longer need to know anything about the exact query I'm running. If my user is searching for the top tempura restaurant in Tokyo or the cheapest Italian restaurants in Boston, I can use this exact same line of code and everything just works. So the idea here is I can run my query, get back the next batch of data that I need, then use the last document from that previous batch of data to fetch the next batch and so on and so forth. And so I can use that technique to populate my next screen of values, or add on more rows to my table view and my infinite scrolling table, or what have you. So pagination is great, right? But it's not perfect. When you run separate fetches like this, there is some opportunity for weirdness. Specifically, if you have a collection where documents are constantly going to be moving around in your index or you've got data being inserted and deleted willy-nilly, you could end up with some odd-edge cases. Some documents might not be retrieved at all if they were to move from one potential batch to a previous one in between your fetches, and other documents might be retrieved twice if they happen to move the other way. In practice, most of this weirdness tends not to be too big of a deal. But of course, it depends on your app and just how much you expect your data to be moving around. Also, pagination can be a little tricky if you're trying to use it while also updating your data in real time. Let's go back to our infinitely scrolling table here and think about how we could use it alongside our real time data listeners. If we're trying to be responsible app developers, we might want to deactivate our old listeners and only keep one listener active for the most recent batch of data. That's awfully nice and responsible of us, but what's going to happen is that these latest results will update in real time and none of the earlier ones will. And that could be a weird experience if your user starts scrolling back up to earlier data and sees data that's sort of stale or no longer updating real time. Although in something like a chat app, some variation of this might work just fine, because old chat messages, they tend not to change. So fetching only the most recent batch in real time might be the exact right thing to do. Another option? We could keep adding new listeners for every new batch instead of replacing the old one. This means that if our table has 120 rows, we would basically have six listeners set up, each one responsible for a batch of 20 documents. That's nice in that it'll generally keep all your documents up to date, but you're going to have to keep track of all these listeners and make sure you're updating the right set of data when they fire. And unfortunately, inserts and deletes are going to be painful here, right? If a new document is inserted at, like, slot five, well, that means not only is this first batch of results different, but the start value for the second query has changed, so you're going to need to change that, which changes the start value for the next query, and so on and so forth all the way down. And you know, those extra listeners probably means a little bit more overhead for your app. Now, a third option would be to actually not paginate at all, but just rerun the original query to start from the beginning and just keep increasing the limit so we get more and more results every time. This will work, and it's probably the easiest solution that accurately handles inserts, deletes, and documents moving around in your collection, but it also means that every time you request a new batch of data, you're basically requesting all the old data you retrieved previously, and you get charged for all those reads, which kind of defeats the whole purpose for adding pagination in the first place. Still, this isn't a terrible option if you want to keep the real-timey-ness and don't think you're going to go past, say, three or four pages. Now, all these options have pros and cons, and they do tend to make your code more complicated. So for that reason, I often prefer going with the plain old one time get call when you start paginating your data in an infinite table like this, but obviously, that depends on how real-timey your users expect your app to be. Like, in my restaurant review app, for instance, I actually think people would be somewhat weirded out if they saw restaurant results flipping around in real time. So I think I'm better off using some simple one time fetch calls and not using real time fetch. On the other hand, if I had, like, a stock market trading app, then it might be worth the trouble adding in some real time listeners while still paginating my data. But again, think about your user experience and what they would realistically expect. OK. So now that this is all clear, let me reveal one tiny little white lie I might have told you earlier. Turns out that with the server libraries, you can kind of say, hey, give me rows 40 to 60 of a specific query. It's done by using this offset method to whatever query you're building. And while this does work in theory, it's generally not a great option, because it turns out that when you call this offset method in the server libraries, you still get billed for all those reads that you're offsetting. So this call here will charge me for 60 reads. So you're generally better off using the start at or start after methods if at all possible. Those will only get billed for the actual reads you perform and none of the ones you skipped. So there you go, folks. I'm hoping this was enough of a head start for you to start breaking up your data when you expect to get a lot of it. Not only will it save you database charges, but your users will thank you, too. Whew. You know, this episode made me a little hungry. I think it's time for a snack break. Don't you? Yeah. [MUSIC PLAYING] I've learned nothing! Ooh, there's another one. [MUSIC PLAYING]
Info
Channel: Firebase
Views: 94,736
Rating: 4.9192662 out of 5
Keywords: paginate data, cloud firestore, firebase developers, app developers, mobile app developers, data pagination, paginating firebase results, paginate queries, queries, database queries, app database queries, app database, server side, client side, server side apps, app, apps, mobile apps, android, ios app developers, android app developers, app data, pagination, query cursors, query results, simple queries, GDS: Yes;
Id: poqTHxtDXwU
Channel Id: undefined
Length: 10min 16sec (616 seconds)
Published: Tue Feb 19 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.