 All right, that's my cue. Thanks, guys. I really appreciate you waiting out in the hot, hot sun. This is Building the Google I.O. Web App, the 2016 version, how we built a progressive web app and launched it on google.com. So a little bit about myself. My name is Eric Bidleman. I've been on our developer relations team now for over eight years. Long time at Google, a long time on the same team. Feel free to hit me up on Twitter if you want to talk afterwards. I might not have enough time for questions, but happy to continue the conversation there. So during my time at Google of these last eight years, I've really focused on building a lot of stuff. I wanted to be a web developer, and there's no better place to be a web developer than Google. I was fortunate enough to work on a number of different projects over that period of time. I've worked on things like the OAuth Playground, version 1.0 way back in 2010, 2009. HTML5 Rocks is a site that our team put together, and I was a core contributor for that for a while. I've helped build developer sites for Chrome and Polymer and Chrome status, which is the blink team and Chrome team's kind of website that allows developers to learn about new features. Santa Tracker is always a fun one that launches in the holiday time frame so that you can play games and kind of watch Santa go around the globe and deliver presents. Last year, we built the Google I.O. web app 2015. And most recently, I've also helped with the developer site that we have for Code Lab. So if you're doing a Code Lab here today, that's using Polymer, and that's using the site there. So I guess the moral of the story here is that I've gone through some kind of gray period, and then in my later years, I've switched into a blue period. But really, it's kind of cool because all these sites are open source, and all these sites are using open source technologies. None of them use kind of the Google special sauce, Google infrastructure, the magic stuff that Google hides away. We've all kind of used the stuff that you guys have available to you as web developers as well, and we've really tried to do that throughout the years. So of course today, I want to talk about a new site. The Google I.O. web app, the 2016 version. Hopefully, you've been to this site before coming to the show. And you can see it kind of changes over the course of the three or four months that we work on the project. Depending on what phase we're in, registration happens, the schedule comes out. Today, the experience of the videos and live stream. We call this Project I.O.A. So that stands for Google I.O. web app. It's a state in the Midwest, which is where I'm from. But it's also our affectionate name for the project. And we really do believe it's a web app. It's not just a website, and that's why we call it I.O.A. So if you hear me say that, that's what I'm referring to. And if you haven't seen it, it's a true progressive web app. Again, it's on this domain, a Google domain launched as a real product, the official site for the event. It's got desktop experience, it's got a mobile experience, it's written in Polymer. You can see it uses material design, so we have things like push modifications, service worker. We've really spent a lot of time kind of making the site work offline, making it really offline. Full screen videos, that's kind of the bread and butter of I.O. when you're at the show, right? People want to see the content, especially if you're not at the show. You want to see the stuff on videos. And then if you kind of go dive into the rest of the page, it's a single page application. So you see transitions between pages. You see kind of cool material design effects as they happen. Of course, the main thing about I.O. is the schedule. So that's kind of the centerpiece for a lot of the time that we spend. So it's really cool. It's a full-fledged experience. There's a lot going on. And that experience, again, changes over time. It's not just a static site. It actually really evolves quite a bit. So if you came today, you actually saw the countdown. Counting down to zero, and it flipped in this little fun mode where you see Android's head and a few other cool things. And that plays for 10 seconds. And then you get thrown into, for the next three days, you get thrown into a live stream experience. So you have a full-screen YouTube video. And then users can bookmark and save sessions and kind of customize a schedule and get notifications about when that stuff comes up. So very immersive experience, very different experience, depending on where we are throughout the course of January to May. So we built a modern app, and we really had to utilize a lot of new modern technologies. So we pretty much used everything under the sun when it comes to the modern web platform, not just client side, but also full-stack server side. So again, we spent a lot of time making service worker happen, making offline experience great. So that was a big focus of what we wanted to do with this progressive web app. We implemented push notifications. We worked on accessibility. It's a single-page application using web components and Polymer, and I'll talk about why we chose that route. We're using new APIs like Firebase, Google sign-in. Our back-end is written in Go and App Engine, just again, using the developer products that you guys have available to you as well. Nothing special here. We're just using the same stuff. The team that builds the app this year and last year was actually a very small team. It's not a bunch of software engineers at Google. It's actually a team like myself, just 20% engineers that work on our developer relations team. And we focus on different areas. So if there was a bug or something, there's an issue in the site, you know who to blame here now. So if you're going to get a push notification for this session, you can talk to Nicholas Garnier. But I just want to highlight, again, we're not using anything Google internal. It's using developer products that everyone has available to them. And the team is not kind of a dedicated team. It's just essentially a list of volunteers. So why would a list of volunteers, why would 20% engineers at Google want to build something of this scale and have all this pressure put on them to be the official site? Well, we have some goals. Every project should have some goals. The first goal was it's fun to write code, right? We're engineers at Google, too. We want to write code. I will show code in this presentation because I think we solved some interesting problems along the way. And I want to share them with you and how we tackled some certain things. But it's also interesting for us as people that talk to developers on a regular basis to actually build real things. Not just demos, not just sample code, but actually build real products with real users, with real bugs, with people complaining on Twitter if you do something wrong. This is fun stuff. And it's painful. We go through the same experiences you guys do. We run into bugs. We file bugs against our product teams, as well. And this is kind of one of the goals we have implicitly is that by building a real product that has real challenges, we actually go through some of these pain points ourselves. So if we're going to talk to developers, we should be able to speak the lingo. So we filed a lot of bugs against different product teams at Google. We even reported browser bugs to different vendors for certain things like HTTP to push issues. And so it's really cool because it ends up making the whole developer platform at Google better, just because we're kind of using some of these new APIs before anybody else starts to use them. We call it dogfooding at Google. So it's an extensive project. And again, it's not just a site. It's not just what you see kind of the visual part of it. But there is a whole architecture here that I kind of want to explain. You don't need to know all the pieces here. The important bits are that we have a front end. It's written in web components. I'll talk about that. We talked to a bunch of APIs, Google, YouTube, Analytics, Maps. In between our front end and kind of the back end and the outside world, it's a service worker. You're going to hear a lot about service workers and progressive web apps. And so we wanted to actually exercise and build a real progressive web app. And so we obviously implemented service worker to do some of the cool stuff you can do, like push notifications. Lines in blue are authenticated requests. So we have Google sign-in. You can log in and personalize a schedule for IO. For our back end for that, we're using Firebase, which is a really, really amazing, real-time database in the cloud. And the cool thing we did this year was we actually shared this back end with the Android native app and the iOS client that we have this year. So everybody is kind of syncing on the same data. If you bookmark a session in one, it shows up in the other app. And if you've ever used Firebase, it's actually amazing. This is the first time I had real kind of experience using Firebase for a significant project. On the right here, you see the native Android app, and you see this session. And you see Iowa, our website on the left. And you can see what happens is that when I bookmark the session in one app, the native app, it shows up instantly in the website as well. And this goes for the other way around. Firebase really, for us, became kind of a communication channel. It's not just a database, but it was a way to listen to data. And as that data changed, then update our UI. So it was great for them. It was great for us. And the iOS client does this as well. Another goal we had was to build a progressive web app. And all the usual suspects here apply. I'm not going to kind of dive into the basics of all you can do in progressive web apps. But we have SSL. We wanted people to launch us from their home screen, have that experience just like a real app or a real boy. Splash screen, right? So people can launch us and get that immersive experience. That's what it looks like in our app. ServiceWorker gave us notifications. And for an event like Google I.O., it actually makes a lot of sense to send notifications to a user's device. That was actually making the experience that much better for the event for users looking at and attending I.O. And of course, ServiceWorker gets you things like push notifications. It gets you things like offline caching. But we actually went the extra mile and did a fair amount to make Firebase-based work offline and also make our dynamic content work offline as well. And I'll talk a little bit about how we did that. So ServiceWorker for us unlocked a number of actually interesting use cases that we wouldn't have been able to do without it. The first is the push notifications. But again, for us, it was about re-engaging users for I.O. We can send them reminders to rate sessions or when sessions are starting, when sessions have been updated. Again, this makes actually the event itself a lot more valuable to users. Fully offline experience for us, we actually did some really cool things with caching offline data to analytics. So we are a big fan of analytics. We want to know what our users are doing on the website. So the fact that we can intercept network requests in ServiceWorker while users are offline is pretty cool. You can stash those in index database. And then when the user comes back online, we just replay those back to analytics. And actually, analytics is happy to take this data. It can take delayed data and give you the same kind of insight into what users are doing while they're offline as when they're online as well. And we use ServiceWorker as a performance tool. So a lot of people know about ServiceWorker as kind of for offline and push, but we actually use it as a performance tool. And we track this over time. What you see here is visualizing our first paint time in Google Data Studio 360. So check that out. I think it's a relatively new product. But essentially, we're reporting in analytics user timing data or the first paint time that the browser sends. And you get this with this API in Chrome and Microsoft Edge, and IE has this as well. And so we track this over time. You can see our first paint is pretty good. Around April, so this is when a lot of people are hitting our app for the first time. They're checking out the schedule for the first time. They missed the registration. They missed the announcements. So that's kind of the spike there. So first time visitors. And you can see what happened over time is our first paint went down because they're coming back to us. They're coming back to us with the ServiceWorker. And ServiceWorker has had all the assets cached in their browser. So over time, this became a performance tool and it actually made our app faster thanks to ServiceWorker. So speaking of first paint, show some raw numbers for our splash screen. We actually do pretty good on mobile. If you don't build a progressive web app, that's not fast. People are not going to use it. So we really honed in on the performance. So on web page tests on a 3G connection with a Nexus 5 device, we got about a 3.1 first paint, pretty good, on a hindered connection. And of course, that repeat view is even better thanks to ServiceWorker. So the fact that people are coming back to us, we're painting pixels faster because all of our assets are in the cache. There's no network at play. Desktop's even better because there's not a hindered connection. It's a more capable device. So about 357 milliseconds and a repeat view, even better in that case as well. So those are some of the goals and why we do the IO web app every year in-house. I want to switch now to talk about some interesting things in three big buckets. Challenges we face, hacks we put in place, cool things like material design. How do we implement some of these flashy things in material? Components, we wrote a single page web application using web components. I want to talk about why we did that and how we did that. And offline notifications. I think there's some interesting UX patterns here and things like making Firebase work offline that are not immediately obvious at least they weren't to us when we first started to develop. So let's talk about material design. More of the story here is we built a web app that uses material. So for that, we use Polymer. Polymer has a rich set of already fabricated material design components, things like ripple effects. You see sliders, dropdowns, widgets, dialogues. The point is a lot was already done for us. When our design team came to us and said, build this, hey, we already have some of this built for us. We don't need to reinvent the wheel. And that's kind of the power and awesomeness of web components. So we use Polymer's set of materials design components. We also wrote our own custom elements, and I'll talk about that. Some of the new elements the Polymer team is working on are also things for scroll effects. So full app layout elements and things that manage on a mobile UI, they manage things like scroll states. So you can see in this demo here as the header kind of goes out of place. You transition between a background image and a color. When you fling back down, the scroll bar or the top nav becomes sticky again. And so you can do all this yourself. We could have done this. But again, we didn't want to. It was already done for us. We're lazy. So we implemented this element, this app header element. Just drop this in your page. Tell it how it should behave with a couple of HTML attributes and what effects it should do. And then you get kind of sticky position scroll header behavior just like this. So it was really easy for us just to use. And the other thing we did with material is we have these kind of full page transitions. It wasn't enough just to throw somebody on a new page. We wanted to make that priority, right? So we did that using a couple of different things. And I'll talk about how we did that. We had a simple routing system. Nothing fancy here. Basically just using the history API, pop state events, history API. And when someone hits a back button or goes forward, we run this magical page transition method. So what page transition does in our router class is essentially it goes through a list of promises. Each kind of happens at a very particular time to manage the state. So the example here, you see what happens when you transition between a page, the schedule page to the tending page. And you can see it in real time in slow-mo. What happens is the header kind of fades out and the content drops down and fades out and then the reverse animation plays. So in order to run this, we basically first fire a DOM event. This is just an event we made up, page transition start. And what this does is it tells the current page, hey, this animation is starting. So don't do anything stupid like run a bunch of JavaScript at this point. We want the performance to be really good. The second thing we do is we run this kind of promise sequence. We have this little animation helper library we call. And that's our Iowa namespace. Very proud of that. And we run exit animation. And that's the thing that's going to fade out the header and drop the content down. So that's what that promise is responsible for. In the next promise, oops, sorry. In the next promise, we update state. We update state. I'll update the state in my slides. So in the next promise, we update state. We just basically select the new page and we set our router to kind of the current URL. That's all that's doing. Next promise calls the run enter animation. And this is the thing that fades in top nav and slides in that content. So the reverse animation essentially of run exit animation. And the last thing we do in this promise sequence is then fire a final DOM event, page transition done. And that tells the new page, hey, all this cool stuff that you've just done is done. And you can then go ahead and do extra setup logic or whatever you need to do to initialize yourself. What I like about this is that, essentially, we have all this asynchronous stuff going on with CSS animations, kind of lazy loading, dynamic selection of pages. We are able to rationalize it very easily with promises. And inside of one of these functions, it's using the web animations API. This is a new standard API in Chrome. And Firefox has this in their nightly as well. Run animation looks something like this. So when you transition between pages, we essentially grab a couple of nodes out of the DOM, that title bar and that main content area. And declaratively in code, using the web animations API, create kind of the animation we want to run. So transform from 0 pixels to negative 100, slide down, and fade out from 1 to 0. Give it an easing function and a duration. And then you create a group effect. And a group effect is essentially a parallel animation. Anything inside of this array is going to be run at the same time. So both these key frame effects, the thing that fades out, the title and the thing that drops down the content, are going to be run at the same time. And what's cool, kind of interesting thing about this is this actually happened to us. Our designers changed how they wanted these transitions to happen, what things took place when the pages were transitioning. And so just by removing a key frame effect from the array or adding it, we were able to modify these transitions very easy. So web animations API, with promises, a really, really, really great way. I highly recommend it to build material design websites. So that's a little bit about material design. The next thing I want to talk about is how we use web components to build a single page application. Again, we're using Polymer here for its set of material design components. We didn't want to reinvent the wheel there. We want to reuse ability. We're using a lot of components everywhere on the site, so there's no sense to make our own. But we also built our own. And so I built a little bookmarklet to kind of visualize how Iowa uses components. Anything in red here is a custom element. It's a custom web component. So you can see things like the Settings panel has a couple of custom elements in it. The Countdown on the home page is a custom element that knows how to count down to today. The app drawer is a web component and the items inside of it. Even if you scroll down on the home page, this little card at the bottom here, if we get to it, that cycles Twitter information. It's a web component. It's responsible for pulling in Twitter data and then rendering and cycling through that. And also, if you go to things like the Schedule page, this is kind of where we have more stuff going on, right? So schedule items, each of those items, they manage their own state, their selection state, their pop-up UI. All that stuff is a web component because we're using it in a couple of different places on the site. So it made sense to make those reusable components. And you can imagine doing this across any website, thinking in components. The Colab site is another example. The top header here, the title, and the description, it doesn't really make sense to make that a web component because it's a one-off. We're only doing that once on the site. But the cards actually do make sense to make a component. We have those all over the place. They're used a number of times. It makes sense to make those reusable. Chrome status is another example of a site that uses web components. So in this case, the left-hand nav here is responsible for fetching the list of Chrome versions and rendering that list. That's all it does. It's very good at what it does. But it was kind of cool to make that kind of a compartmentalized component just to do that very particular task. The Polymer catalog is another example of taking this to an extreme. The Polymer team implemented a bunch of components, and they really wanted to exercise them to build a UI in all web components. And they did that. That's why everything here is red. Everything on that site, more or less, is a web component. And you can do the exact opposite. And this is what GitHub has done with their little timestamps on the site. Anytime you see a relative timestamp in GitHub, that's a custom web component. And so you see these all over the place, commit messages, bugs, all over the place. So it makes sense for them to make that reusable. So you can use components as much or as little as you need. But the point is, if you have reusable functionality, it makes sense to make that a component. And we did this in our application as well. So every page in Iowa in our web app is a web component. IO-HomePage, IO-ExtendedPage, even the FAQ is a web component. And one reason we decided to do that was because all the JavaScript and CSS and markup, particular to that page, is all kind of embedded inside of this component. We get all that goodness kind of traveling around with us for free. The other thing we could take advantage of is custom elements API. So custom elements, the standard API in the browser, actually gives you lifecycle callbacks for very particular things that happen in the lifecycle of a custom element. So for example, when an element's created for the first time, you get a callback for that. When it's attached to the DOM, or when it's removed from the DOM, you get a callback for that. And so for this, it was actually great because we could manage the state of our pages very easily just by attaching ourselves and utilizing the API that was already in the web platform. And we actually extended the native API as well. We defined a couple of our own callbacks for when the transitions were done and when sub-page transitions were done. And so you remember the example before when you're transitioning between pages, we have that page transition done DOM event that gets fired. And essentially what happens is the page just listens for that event, and then it calls its own on page transition callback. And so that's the way we're able to manage some of the jank in our animations, just by attaching ourselves to the native API. So all pages in our app are web components. Again, it's self-contained, reusable pages. As people navigate between these pages, it makes sense to remove them and add them to the DOM. What we do is we lazy activate pages. You don't want the schedule page and the home page to be loading at the same time, right? That would be silly. So we wrap them in a template tag. And a template tag is another part of the web component standards. Essentially anything inside of a template is going to be totally inert until the template gets instantiated and the content is stamped in the DOM. So Polymer has this feature, this DOM in feature that says if something becomes truthy, then do that for you. It's kind of a helpful extension to the template tag. And we do this through all of our pages. And we wrap these guys in another custom element called lazy pages. Lazy pages is dead simple. All it does is it knows about its children elements, and it knows, based on the URL, to stamp, based on the name there of each DOMF, what page should be activated. And so this is the way we're doing lazy activation and managing the single page app feel using web components. So if everything is a component, how then do you share state across your app? How do the pages communicate with each other? The answer for us was to do something similar to dependency injection, if you're familiar with that term. We essentially give every custom element that wants a shared state an app property and define this app property on our window. It's just a global property that we can pass around and kind of inject in each of the web components. So the example here is sharing this app property between home page and schedule page. We use this in a couple different places. The example here is we have a Google sign-in element. So when somebody logs into our app, this is the thing that's doing it. It's responsible for doing the OAuth flow. And what Google sign-in will do, it'll change app.currentUser when the user gets authenticated. And then since we're data binding in Polymer, we're just essentially attaching and wiring these things together without any JavaScript. And so then schedule page and home page can get access to current user when it changes. What I really like about this, though, is that as a first-time viewer of this code, you can immediately kind of understand what's going on. So you know there's pages, right? You kind of know what they're called, schedule page, extended page FAQ. You know that there's this lazy pages thing. Maybe these pages are dynamically instantiated or created in the fly. There's this app thing that maybe gets shared across these pages. You can see that that app is responsive. There's these media query elements chilling at the bottom there. You know there's sign-in in this application. So there's a lot you can kind of get from just by grokking the markup. And that's really kind of what I like about custom elements and web components. You don't have to understand the details of these, you can understand what the app is kind of doing. SharedState is all over our app. So we do a number of different things. We attach this to the app global. We attach things like, is your page transition done? Should your header reveal itself when you're scrolling? What's the current user? If you're on what type of device? So we can show in high different types of markup based on that device. And so this is how we do SharedState using components, kind of dependency injection. In Polymer, we use data binding, but you can just set JavaScript properties. Element.app equals this global. So it's just using JavaScript under the hood. So that's a little bit about material, a little bit of why we use custom elements for our single-page application. The last thing I wanna talk about is offline and notifications. So my first rule for offline is that you have to let users know about offline. People don't expect a web application to work offline. I know I don't, I still don't. Even in 2016 when we have things like ServiceWorker. So we really guide users through this entire experience about being offline. When you first come to our site, the left image here shows you that we show a little message, a little toast that says, hey, caching is complete, future visits will work offline. So we immediately let the user know, hey, we got you covered, this thing's gonna work offline. We spent the time to make this thing work great offline. The second thing we do is that if people come back to our site and there's a new version of our app, we show the message that says, hey, there's a new version. Hit this awesome bright yellow refresh button. And so we really want people to get the latest version of our app because ServiceWorker is really good at what it does. It caches very heavily. That's great for offline, it's great for performance. But if they revisit our app and they get the old version, that's no good either. So we wanna let them know that. We don't wanna like hard refresh the page because it's kind of a bad user experience too. And you see products like Google Inbox doing this and I think it's actually really valuable, really effective. The next thing we do is if you come to us and you go to airplane mode, you have a flaky network connection, we tell you that, we say, hey, we think you're offline. But don't worry, again, we got you back. Anything you're gonna do, just proceed as normal. You can bookmark sessions, add or remove them. Doesn't matter that it's not really saving to Firebase yet. But when you come back online, we tell you, hey, everything checked out, we added that stuff to your schedule while you did it offline. So we really guide users through the entire offline experience. So there's kind of this like big elephant in the room that talked about Firebase a lot. Firebase is really amazing. Real-time database in the cloud. All the different apps, the schedule apps for IO this year use it as their back end. But what happens when you get pretty little dyno, right? A WebSocket API, how do you make that work offline? Well, that's the challenge. So it was a challenge, actually. But what we ended up deciding on was basically just wrap the thing in index database. So anytime you try to set data in Firebase, stash it in IDB first, try to write it to Firebase, and then remove that stash data from Firebase. So in our Firebase ES6 class, we have a setFirebaseData method that we call for all Firebase operations. Takes the path of the bucket, the thing we want to change in the database, and the value we want to set it to. And then we run through a list of promises. So we first push on to index database with what the user was trying to do. We then set FirebaseData, if we can, update the live database in Firebase, and then we remove that information in the next promise. And if there's any errors, we just say, hey, you couldn't write, just remove that stash data. We don't want to try to replay that bad state. So this allows us to make the app work entirely offline. You can use it just as you would online. So the example here is toggling a session. When a user wants to save a session to their schedule or remove it from their schedule, we have toggle session. It takes a session ID, if it should be saved or removed, and the current user. And then we just go through this flow. So if you're authenticated, that means you're online. So we'll just basically our Firebase reference pull your user ID from that live connection. And then we call that magic set FirebaseData method that under the hood does index database and sets the data and removes it. Now, if you're offline, you'll drop down to the else if you won't be authenticated. So we'll grab your cached user ID that we have for you. And then we just queue up an operation. And what queue operation does is also just writes data to Firebase. What you were trying to do, and then the value you were trying to set it to. So when the user comes back online and maybe they have a connection again, we essentially just have this init app function that pulls in the schedule and then tries to load their personalized schedule on top of that, the things they've bookmarked in their schedule. And so we try to replay that from the cache. So anything the user's done while we're offline, we basically just try to replay it for index database and update Firebase if we can. This is what it looks like, pretty simple actually. So load user schedule, if you're replaying from the cache, we just read from index database. iOS simple DB is just a little helper library that we have that promise of thighs, if that's a word, index database. And so we just read that information from index database and then just update our UI. And that's the thing that actually does the cyan bar next to the events that you've actually bookmarked. Now, if you're online, that's the easy case. You just basically drop down and we clear the cache and we immediately just set up live listeners to Firebase. And so that will subscribe us to the data changes and we call the same update schedule page UI. Starting up online, we go through this whole authentication flow. I just wanted to show this code real quick because the interesting thing we're doing here is actually since we're on a Google domain, we have a lot of users, we're sharing Firebase across different applications. We wanted to scale it really well. So we're using this same hashing algorithm to actually shard users in Firebase to different buckets. We have 10 different Firebase buckets that users get bucketed into. And then we set up the reference, we authenticate you with Google's OAuth endpoint and we replay that information when you start up online. So that's offline, that's something service worker gets us. We also have proper notifications in the app. And the rule here, my rule, my personal rule, is that you have to let users know about this. The reason is because again, people don't know, they don't expect to get a notification to their device from a website. That's still bizarro. So we guide you through this experience as well. The first thing we do is we don't prompt right away. We don't say, hey, can I just have notification permission? The user actually has to take an action that requires notifications. So they'll try to bookmark a session, we'll take them through our sign-in flow for first-time users, they'll go through the OAuth flow. When they come back, at this point, they have to actually allow that permission, the browser API to go through. So we bring that up and then the last thing that happens is we save that information to Firebase and that's what calls this little toast that says it's been added to your schedule. Second time is easy. The second time they're already logged in, they've already enabled the browser permission so we can just show them directly that message that says this thing was added to your schedule. Something we've decided to do in our app was actually have a setting where people can then change the notification permission. We don't want, they decided at a later point they don't want notifications from our app. We allow them to change that setting. The other thing we deal with is what if people don't want to be notified? If they hit that block button, they don't enable the browser permission, we can't send them notifications, but that's their choice. So that case, they'll try to bookmark a session, they'll hit the block instead of allow, and we say, hey, can you please update your notification permissions? Now, we decided to do this because we think notifications for our app and for Google IO are very valuable. So we basically have this link where they can go off and learn more how to re-enable that permission in the browser if they choose to do so. It's kind of a hitting setting, especially in mobile. So we let them know how to do that and how to update that in Chrome. And we actually keep showing this. It might be a little bit pervasive, but again, we think the notifications are interesting. So we said, hey, that thing's been added to your schedule, but if you want to learn more how to re-enable notifications, here's how to do that. The other thing you have to deal with is how do you modify your UI based on the fact that the browser supports notifications or not? If you remember before, we have this settings panel that has kind of the description of what notifications will give you on the site in this checkbox. And we also have special UI elements like set a reminder for when IO starts. And in other devices or in other browsers that don't have the feature, we have to modify the UI based on that feature detection. So this is pretty easy. We basically just have this class notify feature. Anything that's a notification feature, we put that class on. We feature detect, the feature detects pretty gnarly, by the way, if you can't tell. You have to check for a number of different things to know if notifications are available. If you don't have that feature, we just apply this global class to body, real quick, dirty hack. And then we basically just hide any UI element if you don't have notifications. Very easy way to just modify your UI based on the fact that you have notifications. So those are the three big buckets I want to talk about and the things and the challenges we tackle. What do we tackle? Material design, components, and offline notifications. I also want to highlight just a couple of cool things that we solved along the way. We ran into maybe their tips and tricks for you guys to kind of take to your next projects. The first was to actually measure the usefulness of notifications. So we're doing that in our service worker code by handling the notification click event and then essentially just constructing a URL back to the user schedule. So when they click the item, it takes them back to their personalized schedule. And we append this UTM source notification attribute. This is a URL primer that Google Analytics understands. And it essentially allows us to know and measure the usefulness of each of the types of notifications we send. So maybe next year we're not gonna do notifications because they're not useful. Again, part of this project was validating progressive web apps and kind of the usefulness of all these awesome features. The other thing we learned is that service worker is a pretty sweet technology, right? But it's actually a real pain if you're trying to develop with it. You don't know if it's your fault, if it's the service worker's fault, if it's the planet's having aligned. There's just a lot of things you start to question. And so debugging service worker is actually pretty painful sometimes, but we learned a couple of things along the way that hopefully it'll help you. So the first thing is, if you're developing, do not turn on caching. Do not use the cache API. For us, we actually have a couple of gulp tasks that run that generate different versions of service worker. And for our development environment, our production environment, and our staging environment. And for Dev, we basically don't use caching. So we know that service worker's not the issue there. So we're guaranteed to have the latest file changes and all that good stuff. The second thing is, a lot of people don't know if you just do command shift R, at least on a Mac, it'll basically reload the page in Chrome without service worker at all. And so this is really handy to get a quick gut check if service worker, if you think service worker's messing with your mind, which it often does. The other thing that we took advantage of was this about page in Chrome. It's really handy just for drilling into a service worker, you can go in there, debug a page, debug a service worker code, start and stop a service worker, kill service workers. Good way to start over from the fresh user experience. So I highly recommend that. And the DevTools team has actually done a tremendous amount of work in the last couple of weeks, maybe getting ready for Google I.O. And adding a bunch of cool stuff for service workers inside of the DevTools. So you can do things like actually simulate a push notification now to your app just by pushing a button. That's really easy and awesome for testing. So I think we built a mobile app and we really, again, a Firebase real-time WebSocket API. What does that mean for mobile battery performance, right? If somebody's viewing our app. So we didn't know, we took a guess. We said, hey, this is probably pretty cool. We can listen for the page visibility API and just shut off the WebSocket altogether. When people bring our app back up, we turn the connection back on. I asked around Google, I asked around Twitter, nobody has data on mobile battery performance and how Web Platform APIs affect it. So if you guys, if anybody out there has that data or is willing to share it and discuss with me, I'm really interested in it. This was our best guess. And again, we just kind of did this because it felt good. We're a big fan of Google Analytics in the site. And for us, it was very valuable to actually, in real time, get client-side errors and know exactly what issues people were hitting in browsers, JavaScript errors. So we report everything to analytics, any JavaScript errors that happen. We actually use this during production pushes to measure what people were doing and how crappy developers we are, basically, and didn't think about cross-browser issues and stuff. But it's kind of cool to see these different errors rise to the top. And we were actually fixing them while we were in pushes. That's really easy to do. So we just attach ourselves to the global on-air event that is fired. And then on modern browsers, you actually get a full stack trace. So we have a file number, a line number, and a stack inside the JavaScript that we push. And we just send that to analytics as an error event. So it gets categorized as a special event. And then we know exactly what error it was. And hopefully, you can track it down pretty fast. You can do the same for promises. So in our app, we're using promises for just about everything. Really love promises. There's two events for that, unhandled rejection and handled, rejection handled. And so we're doing that as well, reporting any promises that were rejected that we didn't think of, and reporting those analytics. The interesting thing here is, I just scrolled away from it, but the interesting thing here is that just because a promise isn't handled right away doesn't mean it's an error, right? So our quick hack here was to wait 10 seconds. If you're not handled in 10 seconds, then you're probably in error. So we report that to analytics. We just push all these unhandled promises onto an array and then report those to analytics. There it is. So it just says another error event, unhandled promise, and then the reason it was unhandled. So that's it. Material design, components, offline notifications, how we built the version of the web app this year, used Firebase, used Material, shared a back end with the different versions of the app. A lot of stuff to cover in 45 minutes, but I do want to mention that the code has been open sourced today, so if you want to check it out, you want to know how we did certain things, file issues, ask questions, send PRs. You'll help everyone that views the site. It's on GitHub under Google Chrome IO Web 2016, and the app is, of course, at that URL. If you want to hit me up and ask questions on Twitter, feel free to do so. I really do appreciate you guys coming out today. I know it's hot out there. And thank you for everyone watching the video. Thank you.