Zero to App: Live Coding a Cross-Platform App on Firebase (Google I/O'19)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC PLAYING] MIKE MCDONALD: My name is Mike McDonald. I'm a product manager on the Firebase team. I'm here to talk today about everyone's favorite topic. How many people are here for business? Let's start with that. Your company is paying for you? Let's imagine-- you've been out all day in the sun. It's hot. If you're like me, maybe got a little sunburned. You are now going to go to the local drugstore and pick up some aloe vera. You pay for that, and you're going to expense it, because the company's paying for it. You got sunburned. And you have to deal with everyone's favorite problem, which is expense tracking. Right? You all love this process. You get back from a really great trip out to I/O, and then you just have this cluster of emails, and cluster of receipts that you have to deal with. You have to upload all those receipts, you have to scan them. You have to determine how much it was. You maybe want to know, oh god, am I spending more than everyone else on my team? Wouldn't it be awesome if I went on a trip, and I had a receipt-- you can check out my burgers and beer from a trip up to Seattle. I want to just take a photo of that, have some magic occur, and then know, hey, that cost me $51.74 dollars. And have that automatically tracked. That's kind of my dream. It would be even better if I could allow everyone on my team to do that. Maybe I have an iPhone, and my co-worker has an Android. Or maybe I want to access all of those things on the web, after the fact. I don't really want an app. I need this app to be available across Android, iOS, and web. If I just sat down and started building an app the way I normally built an app, not only would I build all of those front ends, but I would build a bunch of backend infrastructure. I would have Compute Engine VMs, or maybe I'm using App Engine, or serverless things like Cloud Run or Cloud Functions. But I'm writing a bunch of back end code, to do authentication and authorization. Maybe I have an ORM in there to talk to the database that I'm also managing. It's kind of a huge pain. It's a ton of infrastructure, overhead, and all I want to do is upload receipts and scan them. It should be easy. I'm going to use Firebase to help me build this application. Firebase is Google's mobile platform. It helps you develop your applications, understand what's going on in your application to ensure that it's high quality, and then helps you grow your application. Firebase apps are different, because they don't require that you build all of that infrastructure. It's a bunch of managed services that let you do things like upload files directly from your phone to the cloud, or synchronize data across devices. We're going to start off. I'm going to invite a couple of my friends out. Frank, Jen, and Kat-- I need some help building this app. [APPLAUSE] FRANK VAN PUFFELEN: Thanks, Mike. We're going to build a few apps here. I'm going to give the team a few moments to set up. We're going to talk through what we're actually going to be doing. Mike already said, we're building an expense tracking app. Who's excited about that. [AUDIENCE WOOS] FRANK VAN PUFFELEN: Not too bad. I'm not sure about you, but when I am out having dinner-- when I'm traveling-- when I get the receipt, I want to take a picture straight away. I grab my phone, and I take a picture. I never actually do my expenses for my phone. I file my expense reports from my laptop, in a browser. I have the same app on my phone-- typically an Android version for me-- and in the browser, a web version. I need to see the same receipt images there. We need to store them somewhere where we can access the same files from any version of the app. We're going to use Cloud storage for that. Cloud Storage is Google's exabytes scale storage solution. It's used to back some of the biggest apps that you may have on your phone. For example, Snapchat relies on cloud storage for its files. That is great, because even though we're just starting with the four of us today, this means that we know that the app will keep working even when there's millions of users tracking their expenses. We're going to access cloud storage straight from our devices. We're going to read and write files to the cloud straight from within our application code. We're not going to spin up any servers. We're going to just use the Firebase SDK for Cloud Storage in each of our apps. That means that we'll write a file straight from the iOS app, and then later we can access that same file from within our web app because we're using the Firebase SDK to access cloud storage. We can share the file with users worldwide if we want, but since in this case we're tracking expenses and we're actually storing receipts, this is probably somewhat sensitive information. We can also lock it down to just the users that we want to give access to these files. I've been telling you that it's very easy to actually write this code. How about we actually write some code? Can we switch to the split screen, please? As Mike says, we're going to be building three apps here today. We're going to be building the expense tracking app for iOs, for Android, and for the web at the same time. We're going to do that by writing them in parallel. We've split the screen into four pieces. At the top left, you see that Mike is working on the iOS version of the app, and he's doing that writing Swift. On the top right, you see that Jen is working on the Android version of the app, and she's writing Kotlin. On the bottom left, you see that Kat is working on the web app, and she's doing that in JavaScript. Now each of them has already done some prep work on the app, because we don't want to bore you with UI details and things like that. If we run the [INAUDIBLE] now, we can see the prep work that we've done. We'll share the code for the entire app after the talk, so that you can see what we've done. We're not trying to hide anything, we're just trying to save some time for things that are not relevant to what we're talking about today. We have a very basic app, with a very simple layout. We can select an image, or take a picture. If we do that, nothing happens, because we haven't wired up Firebase yet. That's what we're going to do next. If you look at the bottom right, you see that we have the Firebase console. This is where you manage all of your Firebase projects. As you can see, I have a lot of them. We already created a project that will serve as the back end for these apps. All our apps are going to be talking to the Firebase backend services in this project. You can see that on the left, we have lots of Firebase features that we can use from our apps, like storage that we're going to start with, a database, authentication, functions. I think that's the ones that we're using today. We've already added information about each of the apps to this project. We added information about the iOs, Android, and web application to the Firebase project so that it knows what apps it's going to connect with. Based on that, Firebase generates configuration files that we downloaded and added to the IDE. We took Google services info.plist and we added it to xcode. We took a Google services [INAUDIBLE],, and added it to Android Studio. And we took a configuration snippet and added it to the web app. With that, we're ready to start coding. From here on, I recommend that you follow along with the technology that you're most interested in. Top left, for iOS. Top right, for android. Bottom left, for web. In each of these ideas IDEs, we already added the configuration data and the SDKs that we're going to be using. We've done a plot install. We've done a gradle dependency, and we've included some script snippets. Now, we're really ready to start writing some codes. Remember where we started off. We took a picture-- or selected a picture from the gallery-- and we need to upload that to cloud storage. When the user selects an image, this on image selected method gets called. This is where we need two things. We need a reference to the local data of the image that we just took, or selected. And we need to know where in Cloud Storage we're going to write this data. We're going to build a path into Cloud Storage-- called a storage reference-- out of three pieces. We start with a fixed name called receipts, which is just a folder where we store all the receipts for all users. If we ever need a different type of file, we would store it in a different top level folder. Then under that, we create a folder with a user ID for each user that uploaded the receipt. So Mike's receipts are going to be in his folder, and Jen's are going to be in hers. Then, we end with a unique ID for the file name. It's really just a unique ID we generate to make sure we never have file name clashes. Next up, we're going to tell Firebase to start writing the data to Cloud Storage. We do that by calling on the storage reference to put the data to Cloud Storage. This is all we need to do to get Firebase to start uploading the files in the background. And note, all the things we did not have to do here. We did not have to check for network connectivity. We didn't have to spin up any background tasks, or threats. We just told Firebase to start uploading the data, and it went to work. Now when the upload completes, that can be one of two cases. Either the upload succeeded, or it failed. If the upload succeeds, we're going to display a message that it succeeded, for the moment. But when the upload fails, we take the error message that we got from Firebase, and we display it on the screen because this might be really useful if we need to troubleshoot something. This is all that it takes. Let's build and run the application, and see what we actually can do now. Now, if we select a picture, you can see that the upload starts and we get a very nasty error message in some of these devices. If you look carefully, it says that permission was denied. We don't have permission to write any files, which sort of makes sense. But we didn't talk about that yet. So let's switch back to slides, and see why this happens and how we're going to fix it. Remember when I said that if Mike uploads receipts, that probably only Mike should be able to see them. And when I upload receipts, that only I should be able to see them. That's actually what's happening here. We store files in user specific folders that only the user can access, and we do that based on the user ID. These are Firebase surface hide security rules. We've written these, and these are automatically enforced on the server. Because remember, we are accessing cloud storage directly from within our app. So we can't do security through surface hide code. We have to do it through these rules. And these rules tell us one very important thing. In order to be allowed to read and write the receipts/userId folder, you must be signed in as an authenticated user with the same user ID. That explains why nothing works yet, because we didn't sign in yet. Let's fix that by adding Firebase authentication. Firebase authentication is the second product for use, and it's our secure serverless sign-in solution. By just using the Firebase SDK in your application, you can allow your users to sign in to any of our supported providers. This includes email passwords, phone number, maybe even a password word list link in email, and many social providers like Facebook, Google, Microsoft, Yahoo, Twitter, GitHub-- I think we have a few more. By just writing client side code, you can sign your users in with any of these providers. When the user signs in, we generate a unique user ID for that user. We do that the first time they sign in, when we create their accounts. This ID will stay the same for the user, for as long as they're using your application. It's the same, no matter what platform they sign in from. So if today, Jen signs in on her Android app, and tomorrow she signs in on the web version of the same application, she gets the same user ID. That is great, because that means we can access the same files that she wrote from her phone. You do all of this without writing any surface hide code. So no triple-legged OAuth validations. You just write a few lines of client's hide code, and Firebase does the rest. There's more to authentication than just code. You actually also need UI. You think of some of your favorite apps-- you know that there's a lot involved when you first signed into them. You need to sign up, or sign in. You need to enter your email address and your passwords, and wait. As soon as you enter an email address, you probably want to send a verification email because if the user ever forgets their passwords, you need to send them a reset email. All of these things require that you have a user interface for these actions, for these flows. Let's be honest, all of these flows have nothing to do with what your app is all about. None of it is about expense tracking. We've built a library called Firebase UI. It's built by the identity experts at Google, and includes years of best practices in building good sign-in flows. This library is available for iOS, Android, and the web, and we're going to be using that today to build our sign-in flow. Let's switch back to the codes to actually do that. Can we switch back to the split screen, please? Just like before, we already included the Firebase authentication SDK. We have a method here that gets called when the user clicks the sign-in button. What we're going to do is we're going to add code to call out to Firebase UI to start a sign-in flow. This is a single call to Firebase UI, but we're passing a few parameters that tell Firebase how to actually display this. The most important parameters are the providers that we want to enable. So in this application, we're going to allow the user to sign in with email and passwords, and with a Google account. Now as I said before, we have many more providers, and if you want to enable those, you would just add them to this list. You would also enable them in the Firebase console. This is all that we need to do to kick off a sign-in flow. See again how little code we have to write. When the user completes the sign-in flow, it's again going to be one of two states. Either the sign-in succeeded, and in that case, we're going to display the username. Or the sign-in failed, and just like before if the sign-in failed we'll take the error message that we get from Firebase, and we'll display it to the user. Let's build and run these apps again, because I think this should be all that we need to do. Now, if we click the sign-in button we get a pop-up that's asking us to sign in. We can select our account, or enter or email password credentials. If this is the first time that we sign into this app, we actually-- for OAuth, we get a prompt whether we want to give the app access to our basic information. When we sign in, we see the user's display name. With that, we should actually be ready to now upload an image. Let's see if this time, it succeeds. That looks much better. It's a bit hard. We don't do anything with the image yet, so right now you have to take my word for it. Let's look at the Cloud Storage console in the bottom right, to see if any of our files actually uploaded. Kat, can you go to the Cloud Storage console? You will see that we have a folder called receipts, just like in our code. In there we have three sub folders. Only two of them completed so far. In each of these, we have a file of the receipts that they just uploaded. If you look at the metadata, you can see that the time-stamp is really what just happened. So our files are making it to Cloud Storage. That's a pretty good basic start for what we need for sign-in. We've just signed into the app, we've taken a picture of a receipt, and we've uploaded it to Cloud Storage. I think the next steps that Mike was talking about, is that we need to get some information from these receipts and then start storing that in a database. Jen, how do you feel about actually doing that? JEN PERSON: Sure, I'll give it a go. Let's go back to the slides. We've stored the receipt images for our expenses in the Cloud now. What's next for our app? Right now, we just have a bunch of receipts that are sitting in Cloud Storage. We don't really show anything in our app. We could ask the user to enter the details about the receipt themselves, manually, but this is Google I/O, and machine learning is all the rage. So we really want a software solution to this. We are going to use Google Cloud Vision to extract all the text from the receipt, find the total amount of money in there, and write that to the database. We can't use Cloud Vision right on our device, because that would give all of our users access to our API keys, to run their own Cloud Vision jobs, which we certainly do not want. We could also use ML kit on iOS and Android. But for this specific case, we really want to ensure that we know that the amount on the receipt is correct, so we don't want that access to happen on the client. What we really need is a trusted place where we can run code, that automatically gets called when we upload an image to Cloud Storage. Then, calls Cloud Vision to get the amount for the receipt, and securely writes it to a database so that the app can read it. To solve this, we're going to use Google Cloud Functions. Why use Cloud Functions, or let's say, any server solution, or serverless solution. There are several reasons where this would be an advantage. For one thing, security. In our case-- and many other cases-- there are things that you don't want the client to be able to have access to. You want to be able to process data in a place away from the client that can be secured. Also, it's really nice when you only have to write your business logic once and have it work across platforms. A server or serverless environment enables us to do that. And finally-- and perhaps most important to your users, from their perspective-- is if you're doing something that is battery intensive, or data intensive, you really want to do that away from the client. That's going to be a better experience for them. They're not going to be too happy with an app that is draining their battery. In this case, we could solve this by spinning up our own server, VM, container, but in this case we are working like a serverless microservice. We need to run just a tiny bit of code in a trusted environment that automatically spins up and down as needed. That's where Cloud Functions comes in. Cloud Functions are event driven, meaning that they respond to events that are happening in your app. This could be a user logging in to Firebase Auth for the first time, writing a document to a database-- triggering an analytics conversion event-- or even triggering directly using an HTTP trigger. Whatever one of these events occurs, your Cloud Function is activated and you can act on this information as necessary. This could be all sorts of different things. Maybe using Firebase Cloud messaging to send a notification to a user. It could be uploading or changing some data in Cloud Firestore, or performing some modification to Google Cloud Storage, or even Amazon S3 because it's going to work with third party services, as well. Cloud Functions are written in TypeScript and JavaScript, on top of no JS. And thanks to NPM-- the world's largest repository of Node.js models-- you have over 350,000 packages available at your disposal. So there are lots of options for how to use Cloud Functions. Cloud Functions automatically scale up and down to meet user demand, which means that even if your app is an over night success, we're going to have you covered. At the same time, you only pay for what you use. So if you scale down to zero, you're not going to pay anything Here you see Cloud Function scaling up and down. Clound Functions work inside a privileged Google environment, which means that you can make use easily of Firebase and other Google Cloud Services, often without having to perform any additional configuration or setups stats. You don't have to worry about getting the credentials. They are automatically provided by Google. Let's see what this looks like in code. Let's go back to the code. Where you're going to want to draw your attention is to the bottom right, because that is where our Cloud Functions code is. Here we have a little bit of setup that we have initialized. Let's get right into the functions. Our first step is going to be to create a Cloud Storage trigger, which has a format like this. You'll see functions. whatever the Firebase product is, and this one is on finalize which means this code-- whatever we put inside of here-- is going to run anytime there's is a change made to our cloud storage bucket. The next thing we're going to want to do is determine the user ID and file name. We're going to need this information when we we use Google Cloud Vision to extract the information from the receipt. Let's get back to that part, where we extract the information from the receipt. That's just going to give us any text that is in the receipt. But what we really want is the total. We have designed a custom function that searches the receipt, gets all that information, and looks specifically for the total amount. I'm not going to go into what that does right now, but you will be able to check it out on GitHub if you're interested in how that works. There's still one more step, because right now we have the information. We have the total. But we haven't put it anywhere yet. So we need a database in order to store it Let's go back to the slides to talk a little bit more about that. Here we are-- we uploaded the receipt, we extracted the amount from it. What comes next? It's a good time to talk about a database. Most apps these days need to store data, and our app is no different. Right now, we're just looking to store the total amount of how much a single receipt costs, but it's possible that we'll need more information in the future. Maybe it could be a cost code, or maybe where it was spent, et cetera. There's a lot more information that we may need in the future. For our database, we're going to be using Cloud Firestore to store that information. Cloud Firestore is a hosted NoSQL database that we can access directly from our app. It comes with a rich client library that makes interacting with Cloud-based database easy, and it provides effortless syncing so you can check out changes as they are happening. Cloud Firestore also has a robust offline mode, so your users can keep interacting with your app, even when data is not available. If you don't need real time syncing, you can still do one-time fetches. Cloud Firestore is Cloud hosted, so we can share data between our users, which is exactly what we need. And we can access Firestore directly from our application, meaning we don't have to write any server side code to make the read that our app is going to do. We just incorporate the Firestore SDK. Cloud Firestore is a NoSQL database. So if you're familiar with SQL, and you're used to rows and columns, we're not going to have that structure here. Instead, our structure is going to look a lot more like this. What we have is documents, which have all sorts of fields in them where the key is a string and the value could be all sorts of different object types. These are grouped together in what we're calling, collections. Each of these represents a collection of documents. Specifically, let's zero in on what the structure is for our own application. Here we have a collection of users and you'll see their user ID. We have some fields, user cost and team cost-- sort of foreshadowing what we're going to be talking about in the future. Under each user, They have their own collection of expenses which lists out all of the expenses that they have uploaded. Here's a look at some of the rules that we would use to secure the database. This allows us to access the expense documents. A user can only read their own expenses in this case. We don't allow users to write directly because we're doing that from our Cloud Function. Speaking of which, let's go back to the code so we can add that Cloud Firestore code. Again, you're going to be looking at the bottom right screen to check out the Cloud Functions. What we're going to do is write the total amount to Cloud Firestore, which looks like this. It uses the admin SDK. Whenever you have to do any sort of server side or serverless side work, you're going to think of the admin SDK in order to access Firebase products. We still have one more step to do. We are writing to Cloud Firestore, but our applications are not reading from it. Let's take a look at our client apps for a moment, and see how they're going to set this up. Our first step is going to be to attach a listener. That's going to be updated any time a new expense is added. This is going to read the most recent expense and then, we're going to want to display it in the UI so that you can see it. Let's see if this works. We're going to run our apps again, add a new receipt, and see if we can see the most recent upload. We still have a couple numbers there that aren't listing any information. We really want to know about some total amounts, so I'm going to pass things over to Kat, who's going to be able to tell you more about that. KAT FANG: Thanks, Jen. At this point, we have our receipt stored in Cloud Storage, and we've pulled out the totals and saved them as Firestore documents. This means I have all of the data that I need to get useful aggregate information, and answer questions like, how much am I spending? Since I have access to all of my documents, I could do this on the client side by pulling all of my expense documents for Firestore onto my device and summing up the total there. Now, that works well if I have a couple of expense documents, but as I travel more the number of receipts I have will keep growing and I could end up with hundreds or thousands of entries. And it can get a little expensive to pull down this many documents. It's not efficient to pull down this much data when all I want is a single value to answer, how much am I as an individual spending? Instead, we'll optimize for read time. How can we do this? We can use another Cloud Function to help determine the total spend for each user. Just like we triggered a Cloud Function every time we uploaded a receipt, we can write another function that triggers every time an expense document is written to Firestore. When we get a new expense for a user, we can update their user document to add the amount from that receipt to their running total in the user cost field. Let's take a look at what this looks like in code. Again, we'll be in the lower right hand screen. First, we'll need to create a new function. We'll call this calculate user cost. This should trigger whenever a document gets written to Cloud Firestore by an earlier function. We specify this by putting in our function a document path pattern. This function will only trigger four documents that have an expense ID and are associated with a particular user by their UID. When this triggers, we will get a snapshot of the data, along with some other event context. Now we can read the amount from the document, and grab that UID from the document path. Using this information, and the admin SDK, we can get a reference to the user document that we actually want to update. We use a field value dot increment, and add the new expense account to the running aggregate for the user. We also keep track of the last updated time which we can get Firestore to fill in by using the server time stamp function. You'll see here we're using merge true and set. This allows us to either create or update the document, depending on whether or not it already exists. And like before, we return a promise so that the function continues to run until the work to update the user document is done. At this point, we deploy our function, and that will write the data to Firestore. Now we just need to update the clients to read this data. Let's turn our attention back to the other three screens for each of our three client platforms. Next to our existing listener-- getting the latest expense we uploaded-- we'll add another listener. However this time, we know exactly which user document we want to listen to. Instead of using a query listener, we'll use a snapshot listener that points to that particular document. Then, we will pull out the user cost field that holds our individual total and update our UI. Now if we go ahead and upload a new receipt, we'll be able to see it upload, which will then trigger the function, which parses out the total. And that will in turn trigger the function, which adds it to our own user total. Awesome. Now I know how much I'm spending as an individual. But I'm also interested in the aggregate information of how much we are spending as a team. We'll be adding this to the user document, as well, so we only have one document that we need to read to get all of the aggregate information. You'll note in our UI code, I've already pulled this information out and we're updating the UI with our team cost as well. The last thing we'll need is the actual team cost data itself. Let's go back to the slides to see how we can do this. At this point, we have three apps running on three platforms, each scanning receipts and summing up how much we are individually spending. Since we're often traveling together, I want to know how much we're spending as a total, just to make sure we're not going over budget. However, we can't read each other's expenses or user documents, so how are we going to be able to get this information? This is another place where Cloud Functions comes in handy. As Jen mentioned, functions is trusted code, which means that it is allowed to access everyone's documents even if I, as a user, cannot. This time we'll write a function that gets triggered whenever anyone's user cost document gets updated. If it does, we'll calculate the delta and add that amount to the team cost build, and fan that data out to all the users so everyone has access to that information. Our functions pipeline looks something like this. When Jen uploads a receipt, that will trigger a function to use Cloud Vision to parse out the total, and store that as a Firestore document. That in turn will trigger another function, which will update her aggregate user cost in her own user dock. And that in turn will trigger another function, which will update the team cost for myself, and Frank, and Mike. But, wait. If I update the team cost, isn't that in the user document as well? Wouldn't that, in turn, trigger the function which would update everyone's team cost again, which would end up in an infinite loop? Yes, it could. So we'll need to be able to detect this case, whether it was a user cost update-- like in Jen's case-- or if we have updated the team cost, in which case we want to stop the potential infinite loop. Let's go ahead and take a look at what this looks like in code. We're in the bottom right hand corner. We'll need to define a new function. We'll call this one, calculate team cost. We want this to get triggered anytime a user cost gets updated, which means we'll want to write a trigger for on write as opposed to on create, as we did in our previous function. Where on create functions give us a snapshot of the data, and on write function gives us a change object, which will tell us what the data was before and after the write operation. Having the changed data allows us to determine if this is the case where Jen's uploaded a new expense, and we have new information to fan out to all of our users. Or if it's the case where Mike's team cost's got updated, which means no new data was generated, and there is nothing to update. We can do this by getting the old and new user cost. Old total here uses the change.before fields to find out what the user cost was before the write. New total here uses change.afterfields to find out what the user cost is now. If these two totals are exactly the same, that means the user cost field was not updated, which is the case of what happens when Mike's document gets updated when Jenn uploads a receipt. In that case, we don't need to update anyone's information, and our work here is done. We can return true to get out of the function. Otherwise, there is an update to the user cost and we want to add this to everyone's running team total. To do this, we'll need to loop over every user document. We'll use the admin SDK once more, using the get function to fetch all documents in the user collection. This gives us a query snapshot, which we can loop over and update each document in turn. Like with user cost, we'll once again use the increment value to ensure that this amount gets added to the team cost transactionally. Again, we want to make sure that the function doesn't quit prematurely because all of these updates are happening asynchronously. We'll need to keep track of the promises returned for each one of these updates, and we'll return promise.all, which will ensure the function waits for all updates before exiting. Now, we've already updated the UI to pick up the team cost as soon as the data field is there. Let's try uploading a receipt one more time and see if we can get the team cost update across all three platforms. You might notice that the team total is a bit smaller than expected. This is because the function only picks up on new data, which means that earlier receipts are not calculated as part of this. We could have another means of back filling this data. However, that's all that we have time for now, so if you have questions about this, find us in the Firebase area. Mike, what do you think of our expense tracking app? MIKE MCDONALD: I just uploaded all of my receipts, so I think it's awesome. Can you go back to the slides, please? I came to you all about 38 minutes and 15 seconds ago with the problem of, I want to keep track of my expenses. Frank walked us through how I can sign in, get an OAuth token, securely upload my photos into the Cloud, and then handed off to Jen, where she walked us through how we can actually respond to that. We can call out to the Cloud Vision API, run our magical algorithm to find out how much money a receipt cost, and then wrote that to the database. Kat has helped us through how we can aggregate that information in various interesting ways to get our team cost, as well as our individual user cost that's then written back through Firestore, and synchronized out to all of our clients. It was a really, really cool project, but we're not done yet. We want to share this all with you. If you want to go to xpnz.io and start uploading your receipts, you can just share that cost, and then we can aggregate across all of I/O to see how much we have all spent. It's a nice, fun way to not feel so bad about what you're expensing, in relation to what all of us are expensing. The code will also be up on GitHub at a later date. Don't worry if you didn't quite catch everything that we're doing. You'll be able to build your own expense trackers in the near future. Thank you all very much. I hope you all enjoyed the first day of I/O. Go out there, put on sunscreen. Don't get sunburn like me, and have to get some aloe. There are a couple of other talks. The What's New in Firebase talk, Wednesday, at 10:30. A deeper dive on Firestore data modeling, and then also architecting mobile web apps if you're really interested in the web. Thank you all very much. Enjoy the rest of your day. [MUSIC PLAYING]
Info
Channel: Firebase
Views: 39,924
Rating: 4.9324117 out of 5
Keywords: type: Conference Talk (Full production);, pr_pr: Google I/O, purpose: Educate
Id: gvbq12bGqHI
Channel Id: undefined
Length: 40min 16sec (2416 seconds)
Published: Tue May 07 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.