The Firebase Realtime Database and Flutter - Firecasts

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Yes, Todd, join the dark side.

👍︎︎ 2 👤︎︎ u/azuredown 📅︎︎ Jul 19 2021 🗫︎ replies

MILF Man I love Flutter

👍︎︎ 1 👤︎︎ u/burzacoding 📅︎︎ Jul 20 2021 🗫︎ replies
Captions
hi there flutter developers interested in adding the real-time database to your flutter app well let's find out how on today's episode of firecasts [Music] so you might have noticed that normally for these types of videos we give you like the 10-minute through where we show you how to get the equivalent of hello world up and running really fast but then we leave you to go read the documentation for just about everything else now today i'm going to try something a little different we're going to spend a little more time covering a few more topics in this video so that you have more of a foundation in place before we send you off to the documentation i mean you know you should still read the docs they're quite useful but hopefully this video will cover a lot more than just getting to hello world so let me know what you think of this video format in the comments below do you wish these were like even longer and covered more stuff do you wish they were shorter and i would just get to the point well let me know and it might change how we make videos in the future so the first thing you might be asking yourself is is the realtime database the right database for me or i guess for you so as you probably noticed firebase has two databases available for you to use the realtime database and firestore now both of these are nosql databases that live in the cloud and provide you with results in real time so which one should you use and what's the difference between them well at the risk of using an analogy that will probably tick off both the product teams i'll say that i like to think of firestore as like an suv and the real-time database like a sports car now let me explain firestore has more sophisticated querying capabilities it has a different data structure that tends to work better with large amounts of data better transaction logic at the time of this video anyway and it has multi-reaching capabilities which gives you extra super duper reliability while also being strongly consistent which is the thing that database aficionados really appreciate the real-time database while a little simpler does tend to be a bit faster for writing and retrieving data both databases have different types of limits and while a single instance of firestore tends to be a little more scalable both of them can handle pretty large groups of users the realtime database supports 200 000 simultaneous client connections firestore supports 1 million clients but 10 000 writes per second to the entire database and one write per second to the same record additionally you can have multiple instances of the real-time database in the same project that's not something you can do right now with firestore so if you're okay doing a little sharding of your database the real-time database can scale really nicely but i think for a lot of developers the biggest factor to consider is pricing with the real-time database your pricing is primarily going to be driven by the amount of data that is transferred and stored whereas with firestore the pricing is primarily driven by the number of read and write operations you perform that means that the real-time database tends to work better in situations where you're transferring lots of little pieces of data one recent example when the google doodle team created loturia the bingo type game for the google home page they used the real-time database to share all those player moves with each other because these were basically small chunks of data that were updated pretty frequently other good use cases for the real-time database might be chat messages that's a very common situation or like imagine you were creating a rideshare app and wanted to let a rider know when their ride was coming well you could use the real-time database to update their driver's location and to be clear there's no reason you can't use the two databases together in the same project that ridesharing application could use the realtime database to update your ride's location but maybe use firestore to store user profiles or the history of your users previous rides or that chat app could take older chats from the realtime database and archive them in firestore where your storage costs are lower and you're not going to be retrieving them as often there's a lot of factors to consider and if you want more details you can always check out the link to the database decider page in the description below so david did a really good video series called the real-time database for sql developers and i encourage you to check that out if you want a better idea of how to think about data in the real-time database but here's the quick summary you can think of the realtime database as a big old json object or maybe a tree now when you're accessing data within this tree you're generally going to be doing it by referring to a particular key if the value of that key is another nested object you can reference values in that child key and so on and so forth now that means that in this example here i can find the cost of that first drink order by looking at the value located at order slash abc price and that will be equal to the numeric value of 299 oh by the way this order slash abc price thing is usually referred to as a path just like say a path in your os but i could also look at order slash abc and that would retrieve this entire record or i could look at the value of just orders which would include all of the orders located in this section of the tree so note that when you grab a value located at a particular path you also grab the contents of everything that's nested below that's sometimes what you want but often it isn't as a result you'll find that in practice the contents of your real-time database tree tend to be a little more shallow than you'd initially think a typical example is an app with a bunch of chat rooms in them you might initially think that you'd store all your chat messages as like child branches of each of these chat rooms but what happens if your app just wants to query a list of all your chat room names well suddenly you'd be downloading every single message in every single chat room and that's probably not what you want so a more common solution would be to have your list of chat rooms in one branch of the tree and the actual messages in another branch kind of like this now i can easily get some metadata about these rooms like their name but not have to grab all the messages in each individual chat room the other point i'll mention is that you should organize your data in a way that lends themselves to simple and fast queries for the most part you're going to query data by specifying a path and then grabbing the data that's located there now if this data contains lots of child nodes you could sort these children by keys or values they might have and grab a subset of those but that's about it don't expect more sophisticated logic and definitely don't expect the kinds of joins you might see in the sql world like if our chat rooms had a list of members in them like this i could grab that list of members no problem when i were to grab a room but what we do if we wanted to show say the user's display name or avatar image well let's suppose we have that information in a separate users branch of our tree here well if you're looking for some kind of join statement we're like i could automatically grab the name and avatar url from our users tree and merge it into our users list that's not the kind of thing we can do here instead we would add duplicate data what's known as denormalized data into our database maybe a little something like this so now i can grab the relevant portions of our user's profile information when i'm getting information about a room or even better maybe something like this so i can still query for my list of chat rooms without necessarily retrieving all the users in each of those rooms and then grabbing information about the chat room participants when i need it by querying the appropriate path in chat room users and yes this means if barry changes his avatar i'd have to do it in three places here here and down here now if you're coming here from a sql world these might seem like strange hacks but in a no sql database this is considered normal and a pretty standard way of doing things like i said there's a lot more to the real-time database when it comes to structuring your data thinking about querying and dealing with topics like denormalized data and when you're getting ready to plan your architecture for real i would recommend checking out david's video series on the topic but for now i think understanding that you're dealing with a broad but shallow json object slash tree thingy will get you pretty far at least for the purpose of this video now i'm going to assume you've already created a project in the firebase console added the ios and android versions of your app to the project added firebase core to your yaml file and properly initialized firebase in your code hopefully sometime after you've called widget flutter binding dot insure initialized and if you don't know what i'm talking about go check out this video first and then come on back you can pause us it's okay i won't be offended so once you've done all that installing the package for the realtime database is pretty straightforward first i'll go to firebase.flutter.dev to see the list of all the flutter fire packages then i'll click on the pub.dev icon for the realtime database and over here in the installing section i can see the exact line i need to copy and paste into my pubspec.yaml file so i'll do that make sure i run flutter pubget and we are done the other thing you're going to want to do is go to the firebase console and enable the real-time database for your project now i'm going to keep us central as my database location i'm going to go ahead and start mine up in test mode which does mean that anybody can read or write to the database for the next 30 days or so now clearly that is a terrible idea from a security standpoint so do make sure you change this later with say adjusting your security rules and we'll talk about that later in this video okay let's go ahead and show you how to perform some common actions within flutter if you want to follow along with me i am starting with a pretty empty project here first let me just start by creating a welcome page that is a nearly empty scaffold widget with like a column i'll add in two buttons one that will bring up some read examples and one that will bring up some right examples and we'll leave these on pressed empty for the moment give it some padding add in a little header text on the top and a sized box and we are done okay let's go ahead and create two placeholder screens i will start with the right operations first so i'm going to go ahead and create a new file called write underscore examples i made it a stateful widget that in turn is another scaffold widget although i kind of think in retrospect this could have been stateless and as for the body i'm going to add a center widget with a column in there and let's give it some padding too all right nothing special so far i'm pretty sure you can create your own placeholder screens as long as you have a place where you can start adding like buttons and text widgets you should be just fine and then you know what let's go ahead and create another placeholder screen for our read examples that looks nearly the same thanks to the magic of cutting and pasting all right then from the main welcome screen let's set up our buttons so that they send our users to these two screens okay this all runs nicely and we have a nice framework in which to start putting in some working code so we've got a nearly empty project that we can start adding some functionality to love that new project smell so little technical debt so let's start making an app for a hypothetical coffee shop and my first task is going to be storing a record for our daily special it'll probably look a little something like this where i have a branch of the tree called daily special and then we can include like say the description and a price so i'm going to jump into our right example screen and let's start by grabbing a reference to our database so up at the top of our class i'm going to declare that final database equals firebase database.instance.reference and this gives me a reference to the root of my database you can think of a reference as like a path to a location in our database and right now it's pointing to the top of our tree typically though you're not going to be writing to the root of the database directly you're going to be writing to your children so here down in my build method i'm going to create another reference called daily special and that's going to equal the child here in my tree the one called daily special and you know if i want to go several layers down i could add slashes to my path something like this by the way those slashes at the beginning and the end are kind of optional in fact all these paths are essentially the same thing but i would recommend picking one style and just staying with it for consistency's sake although i personally am guilty of not always being consistent with these things it's kind of a do what i say not what i do kind of situation sorry so how will we write data to this daily special well let's look at the first way the set call set is used to set a value somewhere in your database now for the set call you're generally going to pass in a single argument which is the thing you're going to write at this position in the database sometimes that is a simple value like a string but sometimes that's a map of all the keys and values that you want to store at this location in my case since i do want to set both a description and a price of this position i'll go ahead and do that so let me add in a quick elevated button here and then in the on pressed call i would call daily special dot set and then i'll add in the values i want let's say description and price and actually i think that'll do it now this is a network call so if you want to do anything after the call has been made you could capture that in a then callback this would be called after the write operation has been confirmed by the server in my situation i'll just print out some debug text do keep in mind that if you're offline this call will not get made so like don't block the ui or anything here and of course you should always try to catch errors and print them out something like this but if you are a fan of a single weight and wanted to rewrite the call that way it would probably look a little something like this this is fine too although now i have to admit our catch error block looks a little weird we'd probably be better off putting this in a try catch loop like this that's probably a little more readable but again watch out for any blocking calls here if your app is offline this await call won't finish until the write is confirmed by the server and now we can try this i'm going to stop and do a full recompile here and then let's give this thing a call i'm going to press this button and then let me jump to the firebase console open up the realtime database tab and here's our new data right here in the realtime database nice and like i stated earlier yes i did write out a map of keys and values at this location but i could also write a single value if i wanted for instance let's say i found out that our daily special isn't selling and i want to lower the price to 299. here's one way i could do it i will once again grab my daily special reference i'll grab the child called price and set the value there to this particular number and if i were to run this you can see that it only changes the price of my daily special but what if i were to do something like this let's remove this child and instead i'm going to set my daily special with just the single key value pair of price set to 2.99 what do you think is going to happen now why don't you pause and think about it before we continue well if we run this and head on over to the console we can see that yes it set my price but it also blew away my description it's not there anymore so that brings us to the second operation we can perform here and that's update now both set and update work similarly in that they will write data to a path if it doesn't already exist and replace data if it does the big difference between the two calls is that set will completely clear out that node in your tree including any children before it writes this new data whereas update will keep any existing children that are already there and attempt to merge this new data on top of it so let me go back to my app here and i will add the description back in my console then i'll change the set call to an update call instead now you can see that if i were to run this code it updates the price value but it leaves the description as is and this call also makes it easy to update multiple values at once let's say i had an inventory field that kept track of how many of these things i have left well with the update call i could update the price and the inventory field at the same time and would change both of these values while still leaving the description unchanged now which call you want to use depends on the situation and whether you think you might want to leave those children hanging around for instance in a user record you might want to store things like their last login time and device type when they sign in while still keeping other values like their in-app purchase history intact in that situation an update call would work quite nicely on the other hand there may be times you don't want those extra children hanging around if my daily drink special included optional children like milk type syrup flavor and i don't know sprinkles i probably don't want those kept around when it's time to change the description in that case i might want to just completely clear out the entire node and replace it and i would do that with a set call finally the other important feature to note about update calls is you can use them on completely different paths inside your database one characteristic of nosql databases like the real-time database is that you're dealing with a lot of denormalized data that means data that exists in more than one location and you will find you'll want to update this data in all these locations at once and that's something that you can do with an update call so going back into my app let's say i want to update the price now i could do that by calling database dot update and then my first pass would be daily special slash price and my other path would be some other daily special location slash price and i can set these both to 399 and when i click my button to make the change you can see that both of these values get updated right away this is officially known as a multi-path update and it's a nice way of updating data that exists in multiple paths now three notes about this call by the way one is that you'll notice i had to switch my database reference here to database that is the root node of the database so i could add paths to these two different locations two is that yeah i'm hard coding my paths inside of a string which is generally a bad idea if this weren't a code sample in a screencast i would probably replace most of these paths with constants and three is that this call is atomic meaning that all these updates are applied to my database right away so i don't have to worry about any inconsistencies where like one path of my database has changed and the other hasn't okay let's talk about adding new nodes to the database you might have noticed that in most of these situations so far when i wanted to add a new path i just went ahead and typed in the name of one and that will work in a lot of situations when you're looking to add a user record for instance you probably want to use your user's id as the name of the child in that path but sometimes you want to add a lot of repeated records like let's say my coffee shop app wants to keep a list of recently placed orders now your initial instinct might be to make this an array but for reasons i won't get into but there is a very good blog post about we generally recommend not using arrays if you can help it instead we're going to go with something that still looks more like a hash or a dictionary specifically i will create a tree of values that looks like this and i don't really care too much what this id is here now for something like that you can use the push call this will essentially generate a new node onto the database with the unique string and then set your database reference to that node here let me show you in code because it'll probably make more sense so first i am going to revert my simple set function next uh well i guess you don't need to do this but just for making my app more fun i'm going to create a simple function to generate some more drink names that looks like this and you know let's pull a few people's names out of a hat too okay now let's add another button here to add another order to my list of orders so let's create our new order notice that i'm not casting this up here as a string to dynamic map also i'm adding in a timestamp here which is usually best stored in a millisecond since epoch format now to append this onto our order tree i'm going to call database.child orders dot push this basically says go ahead and create a sorter random id and use that as our new reference for this child node and then i can go ahead and call set here with that map i created earlier and uh yeah i'll still follow this up with then and catch error blocks but you could go with an async await inside a try catch block if that's your thing however you do it you know do try to catch your errors don't just drop them on the floor it will probably save you hours of debugging time at some point in the future so what happens when i call this well as you might expect it adds on another node to the database and adds in my new somewhat random drink order notice as i'm doing this though each one of these items appears at the bottom of this tree these auto-generated ids always appear after the other one alphabetically and that is one important characteristic about the push call the auto-generated id is always increasing in value i'm kind of guessing it's some form of hashed timestamp and that means two things one you don't need to necessarily add in a created by time stamp if you want these things to appear in order you can basically just use their default order in the database but two that also means these ids aren't entirely unguessable so like if you were planning on using any kind of security scheme based on users not being able to guess these ids i wouldn't do that you'd probably want to generate your own uuid in that situation or you know come up with a better security scheme okay so that's rights mostly taken care of let's talk about how we can read in this data so let's go ahead and start filling out our read example screen here add a text widget into our column we'll create a string variable here for the text to show and put that in our text widget and uh i guess that'll work for now we'll add in some more widgets later also up at the top let's add a reference to the root level of our database since i'm pretty sure we'll need that shortly and should i be making these things private let's do that that's probably better form i guess so with the real-time database while we do kind of support one-time fetching of data and i'll talk about that in a minute you should really get into the habit of reading your data in well real time so let's start by showing you how to do that as a general rule you're going to read in data by once again creating a reference to a path in the database then you're going to listen for a stream of events that happen at that location either when data is added removed changed or so on although maybe the most common event type you're going to listen to is the on value event now this will fire when you first set it up and then anytime anything changes at this location or any of its children here let me show you what i mean so let's start by listening for the description of our order in my init state method i'm going to create a function called activate listeners and in there i'm going to create a child reference that listens to daily special slash description and by the way there's lots of ways of doing what i just did here you could have written this with like two child nodes like this or you could have made the path a constant like this or you could have made this entire reference a local variable like this all these are perfectly fine ways of creating a reference to our database and i'm not sure any one way is really any better than another just you know maybe try to stay consistent in your app for better readability so here's what i'm going to do i'm going to reference the on value stream this will fire not only when this value changes but it will also fire the first time if any data exists which means i'll be able to read in this value right away now this is a stream of events so i'm going to add an alisson method and in the callback here i'll grab the event that's attached to the stream and then to get our actual data i'm going to grab the snapshot from the event now this is a data snapshot object which contains a key that's the path of this data and a value and it's this value property that contains our data in fact let's show this on the screen let's grab our results by setting the description to event.snapshot.value then we can add a little set state call and we can set result text to today's special with our description now if we run this we can see that our daily special appears on screen and this happens immediately because our value event triggers right away and if i were to go ahead and change this value in the database you can see this is updated right away before i go any further though i should point out that it's good practice to remove listeners when you're done using them as a general rule i tend to deactivate listeners at the corresponding point in my application's life cycle to when i activate them or i guess to put it another way if i'm going to create my listener during init state i should probably remove it during deactivate so uh let's do that now you might have noticed that when we call descriptionreference.onvalue.listen that returns a stream subscription basically this is a reference to the real-time stream that we can keep around to deactivate later so the first thing i'll need to do is create a private field to track this thing i can do that by creating a stream subscription called underscore daily special stream now down here i'll set daily special stream equal to what gets returned from my drink special reference dot on value dot listing call and then i'll override deactivate and then within that method i will just say daily special stream dot cancel and with that we're done uh by the way what happens if you don't do this well if you were to leave this widget come back and then change the data you'd run into some weird errors where set state is being called on a widget that's no longer in the widget tree like this basically because you kind of have these free floating listeners that's no longer attached to widget but you know hasn't properly been cancelled so like i said make sure to clean up listeners when you're done with them in the appropriate point in your life cycle and nobody gets hurt oh by the way it's at this point that i realized hey i'm not using null safety in my project and i really should luckily it's early enough in my project that migration was really easy the biggest difference and i guess the reason i realized i wasn't using null safety at this point is that stream subscription should be late so if you're following along and wondering why you had to add in a couple of null checks that i didn't that's why also i think i had to force unwrap title in my home page widget anyway sorry with that conversion done i am back to being null safe and my code still works as before now this is nice but nine times out of ten you're probably not querying single values like this in the database you're probably going to look one level up where you're going to be retrieving several key value pairs as a json object so let's go ahead and do that i'm going to go ahead and change my reference here to look at daily special so i'll not only get the description but i will get the price too and if i were to run this again i would get an error specifically internal linked hashmap object object is not a subtype of type string right and uh well actually this makes sense i had cast event.snapshot.value as a string earlier but really it's a dynamic type because it changes depending on what you're looking at and when you're grabbing something that looks more like a jsony object you're going to be getting back a map so in general i like explicitly casting the type here based on what i know i'm going to get from the database so i'll usually say something like this i will say final data equals a new map of type string dynamic from myevent.snapshot.value then we could say something like this we'll let our description equal the description field in our map and our price will be the price field from our map explicitly cast to a double and then i could go ahead and change our set state call to show both our description and price and running this again gives the results i'm looking for nice now of course if you're building a real app you probably don't want to be hard-coding key strings in maps like this you probably want to use actual objects with properties that autocomplete in your ide and so on so in fact let me show you how to do that i'm going to create a new file called daily special and in there i'm going to create a daily special class that has a description and a price now i'll make these required in the initializer because i know these will always exist and that will resolve any nullability issues and then let's add a factory method called from rtdb we're passing in our data as a map of string dynamic then i can go ahead and simply return my daily special object by looking at the description and price values in my map that gets passed in oh and while i'm at it let's add in some nil coalescers i love those okay while i'm here let's add a fancy description method that does all the formatting work for us okay so now let's go back and refactor this code up here i'm going to create a new daily special object using this factory method then i can go ahead and simply set my result text to the daily special fancy description and there we go let me exit and come back in and look at that this is starting to feel a little more like proper coding now with actual objects and type safety and all that fun stuff and i realized i probably should have done this earlier but let's give our text object a wee bit of formatting all right better now there may be times you don't want to set up a real-time listener you just want to grab a value once and be done with it like what if instead of setting up this whole real-time listener i just wanted to fetch the data when my screen loaded and that was it to be honest this used to be somewhat awkward with the real-time database they have this method called once which basically was saying go ahead and create an on value listener but then as soon as you retrieve any kind of value deactivate this listener and it worked okay until we introduced local caching then it worked a little strangely in that it would give you your locally cached data even if newer data was available on the network it was a little bit awkward so as of like a few months ago the real-time database team added a new get call and this works a lot more like the way people expect it gets data from the network if it's available and otherwise falls back to whatever you have cached if you're offline that's a bit of an oversimplification but i think that'll do for now there's just one thing this call was not added to the flutter fire libraries at the time that i recorded my screencast but it definitely will be available by the time you see this video so i'm going to go ahead and use this once method but you should grab the latest version of the realtime database library and use get instead okay so let's see here i'm going to disable this call to activate listeners for the moment and i'm going to call a new method called perform single fetch oh yeah i'll also need to disable this cancel call down here because i didn't make my daily special stream nullable okay back up here i'll once again look at the daily special path in my database and i'll call once on it but again you should try using get now this is an asynchronous call so i could make this in async await or i could just add a then callback afterwards either way is fine and then my code looks pretty similar to before the biggest difference is that for my real-time listeners i receive events from which i need to grab the snapshot property whereas in my once callback i get the snapshot directly and then the rest of the function is very similar i'll grab the value from the snapshot read it in as a map convert it to a daily special object and change our display text and so now when i run this it works i can see my value here but if i were to change this value in the console it doesn't change in my app i would need to leave the screen and come back to fetch the new data and i also want you to notice that like i'm not really saving myself a ton of effort here sure i don't have to keep track of my listeners and deactivate them when i'm done but i've also lost all that real timey magic and that's sad so i would encourage you to default to getting data in real time and really only switching to one time gets when they make sense from a user experience perspective if it would be weird or unexpected for your data to change so i'm going to remove this call and bring back my activate listeners function okay let's get a little more sophisticated here this method of listening to changes in my database updating a local variable and setting state is perfectly fine but flutter already has a built-in widget that kind of does this work for me the stream builder now if you've never heard of stream builder before you should check out this video here but the general idea is a stream builder is a widget that takes in a stream of data potentially some starting data and a builder function that basically turns that incoming data into a widget so let's try using one of these in our sample app as well let's say in addition to retrieving my single record of the daily special i also want to retrieve and display my list of customer orders remember those so now i'm not just retrieving a single record i'm retrieving a variable number of records now the code for the real-time database side of things wouldn't look all that different than what we had before i could use an on-value call or an on child added call either of these would give me initial list of orders and then keep updating me when new items get added the on value call would also let me know when an item is changed or removed so that's what i'm going to use here but to display this let's use one of these stream builders so first things first underneath my label here i'm going to add in a little space through a sized box and then let's start adding our stream builder so for our stream we're going to grab our database instance get the orders child and i guess i'll order these by their key and then we'll limit our orders to the last 10 that come in limits are a nice way to keep my client from downloading more data than i think they'll need so i have the last 10 children stored by their key in their database which by nice coincidence means that we're going to grab the 10 orders that were added most recently then i will listen to the on value stream now then in our builder we're going to pass in the context and the snapshot now the name snapshot here is a little confusing it's totally different from the real-time database data snapshot this is more a snapshot that tells you the state of the stream specifically if it has any data or if there's any kind of error here let me show you let's start by creating a list of tiles that we can display here for our orders then we're going to look and see does our snapshot have any data if so great we can get at the actual firebase database snapshot by looking at the snapshot.data.snapshot object here and then i can call value on that to get at the map so i'm going to say that my orders is a map of type string dynamic from this value now the compiler is complaining that data might be null since i've just confirmed that my snapshot has data above i'm going to cast away nullability with the exclamation point or bang marker here we're now getting another error here that dart doesn't know where this second snapshot is coming from so i'm going to fix that by explicitly casting snapshot data as a type of event now remember if we go to the console here we can see that my orders is actually a map of maps that is each of these individual children are themselves smaller maps with keys and values so what i could do in my code is iterate through each of the keys and values on my map of customer orders and then i can grab my individual order as a map of these values once i've done that i can go ahead and create a simple little list tile based on whatever's in this map so maybe i'll do a little something like this where i can add an icon a title where i can grab the value of the description key and we can do the same thing for our subtitle and then i'll add it to my tiles list and then i'll just return that list view where my children is that tile list i just added and then i guess to make our layout happier i'll put this inside of an expanded widget and uh whoops looks like that should be icon not icons and just like that when i run my application i can now see a list of the orders that were recorded in the database and they still get updated in real time which is pretty neat and of course there's plenty of ways to refactor this the same way i converted my daily special into a custom class i'd probably want to create an order class so uh i can create an order.dart file i'll create an order class i'll add in my fields i'll add in my constructor and make these all required then most importantly add in the factory method to convert this from a map we might get from the database i guess the only interesting bit here is that we have to convert our timestamp into a datetime object by using the from millisecond since epoch method and then i can go back and convert this map data into an actual order object which would make this a little more type safe i am now accessing predefined fields instead of arbitrary strings and i guess instead of using a for each here i could do a map like i can do a little myorders.values.map like this and then we'll just make this say tileslist.all and if i were more comfortable with using the spread operator i'm sure that would make this code even cleaner but i'm pretty happy with what we've got here and to be clear yes i'm using the stream builder down here for dealing with like my big list but you could use a stream builder anywhere that you have streaming data that you want to display in the form of a widget so i could also use it up here for my daily special and i could keep this text widget up to date without having to use a set state call with a separate private variable or anything there's nothing special about stream builders that only work with lists and if we were only interested in performing a one-time fetch we could use a future builder instead it's pretty similar just notice that we're using the once or hopefully get call up here and we're retrieving snapshot.data.value instead of snapshot.data.snapshot.value by the way you might be wondering hey don't you need to like cancel all these real-time listeners when you're done with them here and the answer is no one nice side effect of using a stream builder is that it takes care of cleaning up event streams for you so that's one less thing to worry about gee i guess another nice side effect here is that if i switch to all stream builders or future builders i'm no longer calling set state anywhere which means i could go ahead and refactor this entire thing into a stateless widget if i want it in fact i'm going to do that feel free to follow along if you want but i i guess this is kind of optional so first i'm going to get rid of my future builder and keep the stream builder instead because you know i like real timey things let's move the formatting over from our text widget and then we can get rid of that widget we can then also get rid of our call to activate listeners and perform single fetch we can remove the call from init state and get rid of our display text variable and our display special stream reference which also means we can remove our cancel call in our deactivate method then uh i guess i can just make this class a stateless widget and remove that earlier class oh yeah and i guess that means removing a nit state and deactivate two and there you go my screen still works just like before with real time updates and everything but it's doing it all now in a stateless widget and my understanding is that defaulting to stateless widgets whenever possible is generally considered a good thing to do in the flutter world so uh yay for good coding practices but of course the biggest refactoring task we haven't really tackled yet is separating out our display logic and our talking to the real-time database logic we really should be doing that for proper app architecture and to avoid having massive widget files so let's take a crack at that and the first approach i'm going to use is taking this work of listening to the real-time database and converting that into a list of order objects and i'm going to move all that off into a separate file like in theory i'd be able to replace the real-time database with a completely different backend and my read examples class the one that's displaying these widgets on the screen shouldn't even care so here's what i'm going to do here let's create another class and we'll call it order stream publisher and in there i will initialize my database and then i'm just going to create a single method here called get order stream and what this is going to do is return a stream of data what data specifically a list of order objects so then in the implementation here i can go ahead and create my real-time database stream and then what i will do is i'm going to take the stream and map these incoming events into something more usable so first i'm going to convert my event.snapshot.value which is my entire tree of orders to a map like this then i'll go through each of the elements inside this map and since these themselves are little jsony objects i can call my order from rtdb factory method on these values like so after i explicitly convert them to a map of type string dynamic and then i'll convert this to a list and then return it in my map function let me rename this variable to something a little clearer and then i can return the entire stream so what i have now is a stream that gives me a list of order objects okay that's looking pretty good then back in my stream builder instead of referencing the real-time database like this i'm going to change my stream into order stream publisher dot get order stream and what this means now is that my snapshot.data is just going to be a list of orders so i can actually simplify this builder function a little bit i know now that my orders is going to be a list of order objects no more dealing with maps that need to be converted which is nice and that means that down here i don't need to look at values i'm accessing the my orders list directly so i can just replace value here with next order like so and this is nice right like all the database work is abstracted away that means if i were to ever change like the path of where i'm keeping my orders or like the names of these various key value pairs or even the underlying database itself that logic is all contained within my order stream publisher class and my main widget here never has to know and again this is something we could do with the daily special two but you know what i'll leave that as an exercise for the reader or viewer i guess now if you're really interested in separating out your display logic from your underlying data model one of my favorite ways of doing this is using the provider pattern i won't go into all the details around using a provider there's plenty of tutorials out there already on this and chances are that you seasoned flutter developer understand this better than i do but i guess the overly simplified version is that you have a class for simplicity's sake i'm going to call this our model that extends a change notifier and it contains data that widgets within your app care about when that data changes it calls a very special notify listeners function in the meantime somewhere usually near the top of your widget tree you're going to create a change notifier provider that creates this change notifier within its create method this provider then has the ability to pass this change notifier to any of its children and let them know when notify listeners is called so then within that widget tree you're going to have widgets that are known as consumers of this change notifier these have a builder function that reads in this change notifier and creates a widget based on the data contained inside of it so when your data changes your model calls notify listeners and then all the children of your provider in this tree get notified and have the opportunity to rebuild their widgets did that make sense i i hope so let's try this here and you can see it in action so i'm going to recreate the same screen here where we have a special of the day and a list of orders but i'm going to now do it all using the provider pattern so the first thing i need to do is import the provider library which i will do here in my pubspec.yaml file and yes i will go ahead and run pubget always gotta run pubget once that's done let's create my cafe i'm gonna create a new file and call it say cafe model and i'll have my cafe model class in here that is going to extend our change notifier i'm going to keep around two private variables a nullable daily special and our list of orders that starts off empty and then let's create getters for each of those let's also create a few more private variables one for our database reference and it's two static constants for our paths okay so in our initializer let's start by listening to our daily special and for that method i'll call database child daily special path on value and then we'll listen for events and in that callback function we'll set our local daily special field to daily special from rtdb using our event.snapshot.value and then we can notify listeners and uh actually we're almost done we should keep track of this stream subscription so we can call cancel at the proper moment so we'll do that by saying daily special stream equals this thing so i'll create that field and declare it as late initialized and then i will override our dispose method and make sure we call daily special stream.cancel in there okay with that done let's create our widget to take this information and display it on screen so let me create a new widget called oh i don't know cafe view and we'll make this a stateless widget that in turn will make a scaffold widget and we'll add in our app bar that's lovely okay with that in place let's go back to our main menu here and create one more button to bring up this new screen now this is what it would look like if it were just an ordinary widget but then let's wrap this thing inside another widget specifically a change notifier provider of type cafe model our child will still be this cafe view page and our create method will instantiate our cafe model so now going back to our cafe view let's add in some basic widgets i'll create a column make its first child some header text wrap it in a padding widget and uh now what well let's see instead of creating another text widget to display our daily special i'm going to create a consumer widget this will a this will be a consumer of type cafe model and the builder method will do all the work of creating a widget based on the contents of this model now the three values passed into our builder are context value and child this value is actually the thing that's of type cafe model so i often like renaming it to model and then well let's see if our daily special isn't null we can show a text widget with our fancy description and otherwise let's show a circular progress indicator all right let me do a little hot restart here and try this out and uh whoops looks like i have an error and i'm getting this because i need to explicitly tell dart to convert this event.snapshot.value into a map here in my model so let me do that all right that's better and just like that i am displaying my daily special here in my app and if i went into my realtime database and changed this value well that gets captured here in my onvalue.listen callback which in turn calls notify listeners which in turn tells this widget here to rebuild its text child and everything updates just like magic and so at this point adding our list of customer orders is basically the same process in fact if you're following along how about you give it a try update your cafe model to also grab a list of orders from the database then display it in a consumer widget here in your cafe view so i actually want to give this a try pause the video and then come on back and i will show you how i did it okay ready let's go so what i did was i created a second method called listen to orders and within that method i am listening to my orders path and within there i will convert event.snapshot.value to a map and remember this is my map of maps then i go through the values in this map i will call the map function on this and use the orders from rtdb factory method to convert these json e bits into order objects but dart will complain unless i explicitly create a map of type string dynamic from this variable e which you know i'm just going to rename to order as json so i remember what this thing is then i can convert this thing into a list i'll call notify listeners afterwards and well i'm basically done with the model section except that i do once again need to make sure i can cancel this later so i'm going to set this equal to order stream define it up here as late initialized and then cancel it during dispose and then back in my cafe view widget i'll just create a little space add in a text label and then we'll create another consumer of caffe model in this case my builder will contain an expanded list view and for my children actually you know maybe i will use the spread operator here that's this fancy little three dots thing and then what i'll do is i'll just write a function that maps my orders into cards that contain list tiles with a little display information about my customer's order maybe something like this and with that i now have my customer orders on screen i've still got some nice separation of my application logic and my view logic and everything still updates in real time thanks to my change notifiers now before we wrap up here i want to cover one more important topic which is how to think about your data let's add a button that say updates the price of our daily special to a random number here i'm going to add the button here i'll give it some text and i'll have it call a yet to be written reprice daily special function in my model and now here my cafe model i'll define this method i'll generate a random number for the price and uh now what you might think that i would go ahead and update the price property of that daily special object maybe something like this and then maybe i would go and update the value in the real-time database afterwards as like a kind of backup but as a general rule that's not how you should think about interacting with data you get from the real-time database instead i want you to think of your database as the primary source of truth and these values you have here in your model as just like intermediary values that are updated along the way here let me show you what i mean so instead of changing my daily special price at all in my model object i'm simply going to update the price in the real time database and that'll be all i need to do when i click this button it's going to go out and talk to the database and update the value when it does my database on value listener is going to get updated and that is going to change my daily special object based on what it gets back from the database that then triggers my notify listeners call and my widgets get rebuilt in some ways i almost see this as an extension of what we're already doing when we use providers the same way our widgets rebuild in response to our change notifier changing our change notifier itself kind of rebuilds its internal data in response to our database changing and you know the whole thing happens pretty darn quickly basically by the time that button click animation is finished yes there is a very slight delay but in general the real-time database is pretty fast you should be able to handle it most of the time so i think that should give you a pretty nice overview of the real-time database and how to interact with it using flutter that said let me quickly mention a few more topics before we go each of which honestly could probably be their own video but let me at least make sure you're aware of them so i would be remiss if i didn't mention security rules right now my database is in a state where for the next few weeks anyway anybody can read or write or i guess erase it and that's definitely not what i want when i go into production i would need to write some proper security rules to make sure that people are only able to read or write data to areas of the database that are appropriate now in general this will involve adding firebase authentication firebase authentication is a service that allows my users to sign in and among other things assigns them a unique id that can be verified on the server i can then use that unique id to restrict who can write to the database or where so looking at my data i suspect i might want my daily special to be read in by anybody so you know it might make sense to leave that as publicly readable but in practice i probably only want a small number of people to be able to write to it so i could create a rule in my security rules like this where a user can only write to this branch of the tree if their user id appears in say a manager section of the database so let's imagine i somehow added in a section of my database that looked like this with a little managers tree in this situation user abc would be able to change the value of a daily special because they're listed here in this part of the tree but user xyz couldn't now as for our customer orders i probably don't want a global list like this this works great as an example for teaching purposes but in real life i might change this up a bit to a tree of orders where each customer order was in its own node like this then i could say that customers can only write to nodes where their user id equals the key of this parent node that's what this dollar sign customer id thing is it's taking the key of that branch and saving it in a variable called customer id that i can access later and uh to read in these nodes you would then either have to be a customer or maybe you could be like a barista which we set up in the same way as we did for our managers now of course at this point i've completely broken my app because i haven't implemented any kind of sign in or off yet so you know i'm going to revert these changes for now but this seems like a good direction to go in now that said if you noticed that i had to go back and restructure my data somewhat to fit my security rules that is a good observation and something to note you do want to think about security rules and how they would interact with your database as you're structuring your data not after the app is nearly done and then you're adding security rules in as an afterthought honestly security rules are an entire topic of their own and it would take another separate 40-minute video to really explain them well in fact somebody's already done that jacob's talk from 2016 it's still a good one and while we have made some improvements to security rules since then i would encourage you to check it out to get a really nice understanding of security rules and how to think about them in the real-time database and then of course be sure to read the documentation to learn much more so i've been messing around with the real-time database out in the internet and that's certainly been fun but there might be times you don't want to be talking to your production database directly like if it's a live application and you're worried you're going to mess things up or if you're testing against cloud functions which frankly are kind of a pain in their butt to deploy if you're looking for fast iterative development and you're a flutter developer you probably like that sort of thing so one great option here is to use the firebase emulator suite the firebase emulator suite allows you to run copies of like firebase auth the real-time database cloud functions firestore and more all locally on your desktop machine which means that you can do whatever you want to your local data or security rules and not have to worry about messing anything up on production and you will definitely enjoy testing those cloud functions more to start working with firebase emulators you first need to have the firebase cli tool installed and we have documentation on how to do that you'll want to run firebase init in your local directory which associates your folder with a firebase project and sets up some initial services and then you can run firebase emulators colon start to start up a local version of the realtime database in fact if i click on this link here i get something that looks an awful lot like the firebase console but it's talking to my locally hosted version of the realtime database now once you've done that you'll need to tell your client code to talk to the emulated version of the database and you generally do that when you're creating your database reference that's one reason why i've been keeping my database as a separate variable here in my class so here's what i'm going to do in my class initializer for my caffe model i will call a use emulator method in there i can determine my host that's the url and port my database needs to talk to and that does change based on the platform i'm using on android it's this 10.0.2.2 thing on ios and i think the web as well it's localhost port 9000. oh and don't forget to import dart.io when you do this i also need to add the name space at this point i believe this is usually your project name but if you were to go ahead and open up your emulator ui which in my case is that localhost 4000 page i opened up earlier and make sure you're on the realtime database page you should see it here at the top it's this ns value here so we can go ahead and put that in our code then i'll simply set my database reference equal to the firebase database with the database url of this host and this namespace in my case since i'm setting this as the value of db which is a database reference i'll then grab the reference of this database notice that i'm no longer calling instance here instance would grab the default instance of this database and in my case i'm grabbing one with a custom database url oh and i need to remove final up here since this is now changing and yeah i suspect there are much cleaner ways of switching between which database you want to talk to but you know what this will work for now and well at this point i'm going to hot restart and i can now talk to the locally running instance of the real-time database which is empty so this doesn't look very impressive but if i were to go back and put something into my database using the locally hosted firebase emulator ui like the daily special and in order you can see it's now showing up in my app incidentally i have heard rumors that the ios simulator will sometimes silently fail when talking to the emulator if this happens i've heard the way to fix it is to go into your xcode project and in your info.plist file at a row select app transport security setting and select allows local networking to true that said the last time i tried using the ios simulator to talk to the local emulator everything worked and i didn't need to follow this step so maybe this workaround isn't necessary finally i'll point out that in a case like the real-time database where you've got potentially new data coming in and out all the time it can be confusing trying to keep track of what's new and what's been removed and what's changed so this is one of those times you might want to use an animated list and you could certainly go ahead and build one that's probably the right solution if you want some very specific behavior here but if you want a simple solution it just so happens that as part of the firebase flitter library there's a widget called a firebase animated list it takes in a firebase query and an item builder and it will go ahead and animate in or out list items as needed here let me show you how it works now for reasons you'll see in a moment i'm going to go back to my earlier read examples widget the one where i used a stream builder to build my list of orders and you know i'm going to comment this out and instead let's add in our firebase animated list now it's going to ask for our query which in our case will be getting our customer orders sorted by name and notice i didn't call on value or anything here i just want the database query now for my item builder i'm going to go ahead and start filling out this method notice it includes the context the snapshot which will be the individual item the animation value and the index so uh let me go ahead and convert our snapshot into an order object then i can go ahead and return our list tile widget like before but then i also want to change our tile based on this animation value which i believe is like a double that goes from 0 to 1 to show how far along in our animation we're supposed to be so i'm going to wrap this list tile widget inside of a slide transition widget and our position will be a tween of type offset in between like one comma zero and zero comma zero and we need to call animate on that and uh i'll just pass in the animation value for now which gives us an ugly linear transition actually you know what we have higher standards than that here let's make this a curved animation with a curve of type bounce out and a reverse curve of bounce in and then up here just so you can see what's happening i will set our duration to two seconds as an aside i love the fact that dart has this duration class it makes it so much easier than trying to guess what unit i'm supposed to be using and i think i need to wrap this in an expanded widget so let's do that and then let's revisit this page here's our items but now let me go ahead and remove one and ooh look at that tile animation run away and if i were to add an item back in the orders list wow that's pretty fun too let's do this a couple more times now one thing this widget doesn't seem to do is use any animation when two items switch places in the list but for any list we expect to be mostly adding or removing items this will work nicely and without much effort the big issue you probably noticed is that i'm back to using my database logic right here in my widget this really isn't a widget that fits in naturally with the provider pattern it's probably best suited for like one-off situations where you just want to display some elements from the database but don't like need to make it an integral part of your application's model if you do want to stick with the provider pattern here you'll probably want to use an animated list and manage those transitions yourself we made it well we covered an awful lot here didn't we we learned how the real-time database is structured how to set and update data how to read it in both in real time which you should kind of default to and in a single fetch we talked about using things like stream builders and separating out our display logic from review logic using stream publishers or the provider pattern and we finished up with a brief but important talk about security rules as well as the firebase animated list widget so while there is still more to learn about hopefully this will get you well on your way towards using the real-time database in your application in the meantime be sure to subscribe to the firebase channel to make sure you know when the next exciting tutorial video is coming out and i will see you soon on another episode of firecasts [Music]
Info
Channel: Firebase
Views: 26,170
Rating: undefined out of 5
Keywords: purpose: Educate, pr_pr: Firebase, series: Firecasts, type: DevByte (deck cleanup 0-10min), GDS: Yes, firebase realtime database, firebase realtime database and flutter, firebase realtime database and flutter tutorial, how to use firebase realtime database, firebase realtime database overview, firecasts, firecasts for firebase developers, firebase developer, firebase developers, google developers, firebase tutorial, flutter, flutter developer, flutter developers, firebase, google
Id: sXBJZD0fBa4
Channel Id: undefined
Length: 59min 26sec (3566 seconds)
Published: Mon Jul 19 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.