/Google I/O 2011: Android + App Engine: A Developer’s Dream Combination

Google I/O 2011: Android + App Engine: A Developer’s Dream Combination

Video: Google I/O 2011: Android + App Engine: A Developer’s Dream Combination

Subtitles

Abrams: Well, good afternoon, and thank you for coming. Uh, I really appreciate you taking the time. Uh, so I'm curious–how many– we have Android and App Engine, so the Android guys were supposed to sit over here… No, no. Seriously, how many of you are And– primarily Android developers? Okay, and how many are primarily App Engine? Okay, so Android is winning a little bit. That's what we expected.

Okay, great. Um, so I'm–I'm Brad Abrams. I'm the product manager for, uh, the Cloud-based developer tools that we have. man: Turn the microphone on. Abrams: Mic, mic… Ducrohet: Mic? Yes. Uh, I'm Xavier Ducrohet. I'm the lead for the developer tools and SDK for Android at Google. So why are we here? Well, a lot of you are mobile developers, so that's good. Uh, I've been at Android for, like, four years, so as you can expect, I've had a lot of devices. I started, you know, with– way back before the G1, I had a prototype– actually, I had several of them. Then I moved to several G1s that had myTouch, Droid, Nexus One, Nexus S, and, well, every time I get a new phone, it's always the same story– all my data that's in the cloud, great, it's directly on my new phone. Otherwise, well, I have to reinstall apps, reinstall data, and it's really a bad story for the users.

And now that there are tablets– and I heard that some of you may have some tablets, too– Um, you're going to want to have your application working on a tablet, and you do something on your tablet because you're at home and you have it with you, and then you go somewhere where you only have your phone, and you want to have all your data seamlessly. Abrams: Uh, and I'm, uh– the reason why I'm here is I've been a web developer for a lot of years, and I just love the web, and one of the things about the web I really like is the stateless nature of the web. And once you've built a web app in PHP or whatnot, I can go use that app from anywhere, and my–my state carries, and, uh, some constraints of the web made that possible. And when I came to Google about a year ago, I just fell in love with Android. It's just a fantastic platform, and I love–I love my phone, but I was a little bit disappointed with some of the apps, you know, like, I bust through a certain level of Angry Birds, and, you know, on one device, and then I– I can't– I-I can't access that level on another device.

And so I was–I was, uh, not happy with that experience, um, and so it was–it was around the holidays, right? Ducrohet: Yep. Abrams: Um, that we– Xavier and I and several others, um, started talking about, What could we do to make this a better experience? So… Ducrohet: All right, so, um, now if you're coming from the mobile developer side, uh, when you think about putting a cloud component into your application, you're probably going to have a lot of questions. Uh, the first one is going to be cloud equal datacenters, you know, dealing with machines, you know, CPU cost, bandwidth cost, and all of that, and that's probably a bit scary at first. Then there's simply coding for a cloud. Um, if you're coding for a small device, you're probably not used to coding for hundreds of CPUs at the same time.

Uh, and then there's the question of security and authentication, both, you know, making sure that your app actually connects to the, you know, data of its user, and, you know, not another user, and also from a user perspective, on the phone, you want the experience to be really good. Typing a password on a phone– not so good. Um, it's possible, but it's not a great user experience. And finally, one of the other questions is purely practical– you're coming from Android, uh, you know Java, you know Eclipse, are you going to have to learn about new tools? Abrams: You know, Xavier, those are fantastic questions. You know, I anticipated you might have those questions. Ducrohet: Huh, surprising. Abrams: So, uh, obviously there are a lot of solutions out there for building cloud-based apps, and from an Android ecosystem perspective, use whatever, um, solution you think makes sense.

Uh, we're gonna talk a little bit about App Engine today, uh, and App Engine, just to address a lot of those points, from a cost perspective, App Engine is absolutely free to get started, so just like Android is free, App Engine is free, and you can go build the app that we're gonna build onstage for free today with App Engine. And then your costs scale with your–with your usage, so as you have an app that's amazingly popular, uh, you know App Engine can keep up with that, uh, and the costs will be linear with that. Um, and so this– we built App Engine in–on top of Google scale, so we built it to run Google services, uh, so the datastore that I'll be using in the demo is built on the same technology that the index use– that Google's index uses, that GMAIL uses it.

So it is a highly scalable, highly available system. Um, and then one point that I think is very important is that from a security perspective, we've built App Engine, as well as Android, to have a really good first-class notion of the Google authentication and Google identification, and that helps tremendously when you build out the applications. And in terms of ease of use, well, we have the Google plugin for Eclipse, um… How many people have already used the Google plugin for Eclipse before? Okay, yeah, so a good number of you, so I don't have to tell you, it's–a few of you that haven't, um, it's a fantastic, uh, Java-based development experience for building App Engine applications, so whether they're backed by GWT or not, you can use them for Java-based App Engine applications. There's a local development time server in it that lets you debug, uh, and build applications locally. Uh, there's functionality to push to production, uh, to App Engine, and you get– somebody was asking earlier about Google APIs– in the latest version of the Google plugin for Eclipse. There's very easy access to the whole range of Google APIs, so we make it easy to discover and use those APIs. Um, and then we just added a visual drag and drop designer for building the client side– the GWT client side of those apps, uh, which we'll show you a little bit about.

And then it's– it's really widely used for App Engine development. Ducrohet: All right, that sounds excellent. So let's build an app, uh… Abrams: Yeah, what would you like that app to look like? What do you think the architecture should be? Ducrohet: So you're going to want a cloud back end, right? That's why you're here. So you want to have an App Engine component with the storage, uh, so let's– well, that's a pretty simple– you know, basic, simple– let's make a task application. Abrams: Y-you know who needs a task-tracking app? Ducrohet: Who? Abrams: You know, uh, Larry Page just got a new job. He's our–he's our new C.E.O., and as C.E.O., you know, he's got a lot to keep up with nowadays. Ducrohet: That's true. Abrams: So I think maybe as our sort of gift to Larry, uh, we should–we should build him a task-tracking app. What do you think? Ducrohet: All right, Let's do that.

Yes, that's a good idea. Abrams: All right, all right. Ducrohet: All right, so all tasks–so we're going to have that App Engine server, where we– we will store those tasks, and then we'll have two clients. One of them will be, uh, an Android client, of course, and then the other one will be a web client built with, uh, GWT. Um, so… You know, the other thing that we want to do is that we want this Android application to be a really good citizen. Now we have a technology in Android called C2DM. It's Cloud to Device Messaging, and it's a way for the, uh, server to notify your application in a very efficient way. You don't want your Android application to regularly go and talk to the server, saying, "Hey, did something happen? What about now, did something happen again?" It's like, you just want to wait for the server to tell you something happened, you know, talk to me again, and then, you know, you do actually exchange some data.

So this is the C2DM service. Um, it basically opens a connection, and you have one single connection that is shared by every C2DM-enabled application, so it's much better, uh, not having every application every now and then waking up the device, and then the device goes back to sleep, and then ten minutes later, another application wakes it up, so C2DM is much better. So what we see here in that diagram is that we have a lot of components that are already handled by the current tools. Uh, App Engine server and the web client– it's handled by GP that Brad just talked about. Uh, on the top left, we have the Android client, which is handled by the tools that my team is responsible for, the Android developer tool– the ADT plugin. And in the middle, there's those arrows there that are going to be some sort of network communication, and I don't know anything about those.

Abrams: Yeah, those arrows– those arrows on the diagram seem to be the hard part, but as Xav said, the other pieces are sort of taken care of by other tools, but it's really integrating these pieces that's hard. So, um, I'm happy to announce that we have a Beta release– we just posted it a few hours ago–where's Chris? Yes–um, a Beta of GP 2.4, and it's got built-in support for Android in particular. Uh, the RPC, uh, the Android to App Engine line, uh, we've got built-in support for that, and we've got seamless integration of Cloud to Device Messaging, and those bits are available for you today. Uh, you can go to that URL and download them right now, uh, during the talk, assuming the WiFi holds out. [laughter] Abrams: So, um, So I–but, you know, there's really, I think probably the best way is to kind of show a demo, huh, you think? Ducrohet: Yeah, let's just build an app.

Abrams: Yeah, so, uh, do you want to switch over? Ducrohet: Yes. Abrams: Okay, so what I– we're gonna spend the bulk of the rest of the time here in actual code, in Eclipse, and this is the Eclipse Helios release–the latest release. Uh, I have the latest Android development tools installed, and I have, uh, this Beta version of GPE, uh, which I'll mention is quite hot off the presses, um, so we thought about showing– well, we'll just kind of do slides or something– but I thought, no, no, no– the I/O audience– they want to see the raw bits, right? They want to see, no matter how new they are, and they will forgive me if… [laughter] Abrams: Okay, anyway, let's press on. Um, so we'll come in, and we'll say, "File, New," um, and we'll create–there's a new menu option under Android called the, um, "App Engine Connected Android Application.

" Um, and we'll call our app "Cloud Tasks," to build that task-tracking app for Larry Page. And what it's gonna do is it– you'll see, it creates– Whoa, that didn't work out. It creates two projects– uh, an Android application, and an App Engine application, um, and it's just building those right now. Um, it will work out. And–and they are connected. They're tied in a couple of– in a couple of places. So first, we'll just come in, um, and say, "Debug As," and then we're gonna select this new item, called "Local App Engine Connected Application." And I'm just gonna spend a few minutes showing you what you get out of the box. So just while that's coming up, uh, we'll come in here and look at the source, and you see in the server, there's a couple of pieces here. A couple of these files are based on handling the Cloud to Device Messaging, um, functionality. How many people have–have checked out Cloud to Device Messaging? And how many people have successfully gotten it to work in your app? Okay, okay, that's a pretty good percentage. Um, it–it's a very powerful technology, but it is tricky to get it to work, and everything you need to do on your server. Google just handles proxying, um, uh, messages, so you have to handle a lot on your server.

And then there's this– draw your attention to this HelloWorldService, and that's an RPC service that we have, and it–it just sends the–the user's, uh, name. Okay, so we should have it up now. The first thing we're gonna do is go ahead and log in, and many Android devices, um, have Google, uh, accounts already selected, and we're gonna go ahead and use, uh– We've got Larry Page's account, so we're gonna use that. So I'm gonna go ahead and log in, and while that's happening, I'll flip over to the, uh, web app. Um, so this is– there's also a GWT– that's a Google Web Toolkit-based AJAX client, uh, for the– for the same service. Ducrohet: I want to go back to the Android for a minute. As you saw, there's a concept of account inside Android, and so there's an API to get the accounts, and in that case, that application just queries for the Google type of account, and we just display that to the user.

You don't have to enter–to make your user enter a password, because, you know, when you login the first time on your phone, you entered that password already, so you don't have to enter it again, and that's the type of experience that I was, you know, mentioning earlier. Um– Abrams: That was fantastic. Thank you for jumping in there. Okay, so we'll log in to the, um, client, uh, and I–you know, we need to log in as Larry Page. Let's see, it's LarryPage20… Ducrohet: 11, yes. Abrams: 2011. I get that wrong. Yeah, so 2011– that was the year Larry kind of ascended to the– to the throne there. Ducrohet: The throne, yes. Abrams: Um, so we'll go ahead and log in, and notice that it just had me enter the user I.D., because we're running in App Engine test mode, right? so we're running it locally. When we deploy that, it's gonna really ask us to log in, and then we really will have to get Larry's, uh, password. So, um, let's see. Uh, we get to log in here. I had a little, um, snafu, so let's log in again here.

And what you–what you should– what's happening under the covers when we log in is it's actually doing a register with C2DM, so it's sending a message from the emulator here, um, through the–through the internet to Google services, and then back to, um, back to the local App Engine instance we're running on this machine. So you're seeing this machine– the emulator talk to the local App Engine on this instance. And if I'm successful registering, you'll see a notification right here that says the registration was successful. So, um, that's kind of the first thing. Um, now–now that we're logged in and successful– I'll go ahead and clear that so we don't get confused later. Now I can click "Say Hello," and what that's gonna do is an RPC call from the client, uh, up to the server. And what's really interesting about this, is notice what it says. It says, "Hello, Larry Page," because it knows who I am. It knows my Google authentication. Because Android uses Google authentication, and App Engine uses Google authentication, so they have a shared notion.

And it's not like we're sending name and password over the– over the wire, we're sending some really large prime number, or something, right? Ducrohet: Yes. Abrams: Okay, and, uh, and– Ducrohet: Sort of. Abrams: Uh, where is it? In the web client, uh, we have basically the same experience on the web client, so you can build one RPC layer that works for your Android client and your web client. Um, and then we– the other cool thing we can do, is we just have this test screen– 2011, right? at GMAIL. Whoops. And we can say hi, and basically, as soon as I can send that, it's successfully saying– you see right up here, and then there should be the message–"Hi." So that–what you get– you get all of that just by saying, "File, New, Experience." So you already have C2DM wired up, and you already have an example of an RPC service, uh, up and going, so you can go and clone that, and see–really debug this thing and understand how it works, just by saying, "File, New.

READ  The Room Three Review | PC

" But, you know, I don't think Larry's gonna be happy with just this. Ducrohet: [chuckles] Probably not, yes. Abrams: We–we probably need to keep going. Ducrohet: Yes, yes. Abrams: So let's keep going a little bit, um, and go ahead and flush out. I'll start an ad– I think when you build a task-tracking app, the first thing you need is, well, a task, and this is just a plain Java object, a couple of annotations on it with due date, but there's no, uh, persistent, specific code in here. This is just a plain old– a plain old object, and we're gonna use this, uh, as our data transfer mechanism, so it's just a plain class. And then we're gonna show, um…. Whoa, what am I doing? We're gonna do–use our new RPC tooling wizard, and that's under the Google, uh, node. And now what we've done is we've examined the server project, and we've looked for– looked at all the classes that are in there and found the ones that are entities, that are either marked with– Uh, what's it called? PersistenceCapable, or, um, whatever– Serializable.

Anyway, uh, with a couple of annotations, um, that work for– with our RPC layer, and we found those, and then when I click "Okay" here, it's gonna create, um, basically, query, plus "create," "update," and "delete." Uh, we looked at a bunch of applications with Android apps and web apps, and we found this to be a very common pattern to do, Uh, so let's just look at it a little bit. So basically, we have "create," "read," "update," "delete," and "query," um, and then in addition to that, we have, into shared code, um, we have generated the appropriate client proxies, the TaskProxy and the request service, that are the client version to be able to call this.

So all the calling code and the server code was–was generated for you. So, uh, and we'll drill into exactly what those look like in a little bit. But, um, so now you can do your data storage however you want to. You want to use Amazon S3, you want to use your own local database, you have some local–some legacy system that you're using, you need to store data– perfectly fine. Do whatever you want. Um, I'm gonna show you using App Engines datastore. It's a highly scalable, NoSQL datastore, um, and I'll just– just to save the typing, I'll just drag this file in, um, and I'll show you bits and pieces of that as we go. So I'm just gonna create DataStore–db, new DataStore, and let's see if it–oh, yeah, and it needs to be static.

Whoa. Okay, so now we have that. Now let's look at how we implement each one of these. So query is just gonna be, um, db.findall, and it– it's instructive. Let's look at what that looks like. So this is the implementation of findall in that– um, in that datastore class, and all we're doing here is this query. We're using persistence manager and doing a query, and if you've done SQL development, it looks very similar to you probably. Um, what we're doing is we're selecting from, and the table is just the name of the task, uh, type that we put in, because we store some other stuff in this datastore. Um, if you splunk in the C2DM code, you'll see we're storing the C2DM registrations in the same datastore, so we need that, uh, unique I.D. there. Um, and then we're gonna filter out– we're only gonna return those tasks that match the current user's I.D. So, again, App Engine knows who is making this request, so it's gonna return only those tasks that match the I.

D. of the person who's making the request. So for example, uh, Larry Page may not want you to see all of his tasks, so that's kind of our– our authentication mechanism. And you notice it's very basic, and it–and it works, um, for kind of large data sets, too. Ducrohet: So that's what I would write if I had, like, hundred millions of users. Abrams: Yes, yeah, so we– I mean, obviously, we're only gonna have a couple of users here today with this, but this exact pattern, we have, uh, App Engine customers use– storing hundreds of millions of entities in the datastore using code that looks very much like this, um, same basic model. So App Engine is really tuned for this sort of experience. Um, and then delete, um, this is pretty basic. We're just– We're just gonna go ahead and delete it. I'm gonna do just a little bit of business logic here, get the user I.D.

, uh, out of the task that's passed back from the client, um, and notice here that I don't have to deal with any serialization logic. Like, I need to know what task to delete, but it's fine– like, the framework handles all that for me. Um, and this is where you could do something like tombstoning, or copy this data, or do some permissions thing. There's a place for that business logic now. Um, and then update is also equally easy. Uh, we're just gonna call db.update of that item, and that again, it just stores the item in the datastore. And the datastore handles matching of the I.D.'s to make sure they're right, and then doing the update. But, um, because we're using the e-mail address as a security precaution, we want to make sure that you can't put, uh, something on Larry Page's task list, that you can only put things on your own task list. So no matter how the task comes in, we want to set, um, the e-mail address– oh, it was somewhere– yeah, we want to set the e-mail address to the user's e-mail address. So I'll just use this utility function to get the user's e-mail address.

Um, and that's a really powerful notion. Notice that the–obviously, the clients that Xavier and I are gonna write are gonna be well-behaved, but this is just JSON over HTTP, so really, any client– anybody could come and try to hit the service, so we need to do some security here. But notice how easy it was. It was amazingly easy, because App Engine and Android share that common authentication mechanism. And just– just to drive it home, see how simple that "Get e-mail address" method is? Because App Engine already knows the user who's logged in, so we just have to pull the e-mail address out of there. Okay, so we'll, um, go through and do "read" here. Uh, and so the read is just to show you that you can, um, take parameters to these methods. And then "create" is– is mildly interesting. What we're gonna do is actually do an update of a new–a new task.

So what we're gonna do is create a brand-new task, send–round-trip that to the datastore, and that'll give it a unique I.D., uh, in the datastore sense, and then when we have the unique I.D., when we later come and update it, we'll know which one– which task we're updating. So, you know, Xavier, we've got an RPC layer here. We've got a data layer. I think we're ready for you to build the Android app. Ducrohet: Yes, well, it looked a little bit too easy, and, you know, when I build my app, I want to make sure that there's at least some task in there, so that I can make sure that it works. Abrams: Oh, I see. I see. Ducrohet: So, uh, yeah. Abrams: You don't trust me. Ducrohet: Well… no. Abrams: You think that I made a bug here, and you don't want– Ducrohet: No, no, it– it just looks a bit too easy.

Abrams: That's okay, that's okay. I mean, there's a lot of people here. I understand. Ducrohet: Yes, yes. Exactly. Abrams: So let me demonstrate that it works. I have this client directory, too, and it is where that default GWT-based client, uh, was created in. And again, you don't have to use GWT. Um, uh, our team builds GWT. We happen to think it's fantastic. But you–you can use whatever you want and the system still– still works. So, um, I made some small modifications to the client, uh, the same client that we saw. Uh, and basically what I did is implement buttons for "create," "update," and "delete." And I just want you to look at the code really briefly for, uh, each one of these. So for "create," what we're gonna do is call that TaskRequest factory. That was created by the serve– by the RPC tooling wizard. When I said I wanted task, it created that in shared code. Uh, and so we're able to access this from–from GWT.

Um, and then we just do an Asynchronous Fire on, um, on this. And notice that we're calling the "createTask" method. If we look back over here, um, we should have, somewhere, a create–a "createTask"– there it is, "createTask," right there. So there's a–there's a one-to-one, um, uh, correlation there. And same thing with "update." It looks exactly the same. We're just gonna, uh, pick a– pick some task name and put it in, and then we call the update task, passing that task. And you see we're doing it asynchronously, so we don't block the browser, um, thread. And, of course, if you're not familiar with GWT, um, this is very cool in that we get to write in Java code the same language as our server, same language as our Android app, uh, but it–it runs– it compiles it to very optimized JavaScript code, which works in any web browser. Um, so if I re–Oh, that's– that would be the wrong– that would be the finale that we don't want to show people yet.

Okay, um, let's reload this one. Um, so, uh, there we go. So there's our very nice buttons that we added. So I can come in and create a new task, um, and you see, it did that round trip to the server and gave me an I.D., and then I can update that task. Uh, and you can see, uh, Larry's first task– Use "really big" blimps for streetview. I think that's– that's a good idea. Um, so we can create another one, and update that one, okay. Uh, let's do a couple more, just to give you some test data. [laughter] Abrams: We spent most of our time coming up with these items. Ducrohet: [laughs] Yes. Abrams: So I'm glad you like… Okay, and there– there's all of them. So query–so "create," "update," "query work," um, and then we'll go through and delete that last one, just to show that "delete" works.

Okay, so now what we've shown– Ducrohet: Yes. Abrams: I hope maybe you're a little more confident. Ducrohet: Yes. Abrams: Now what we've shown is a data layer on the server, uh, an RPC layer there, and then we showed a very simple GWT client, so you can probably– you can probably maybe copy some of that code for your Android client, if you want. Ducrohet: Uh, I could almost, yes. All right, so let's do an Android application for that. Uh, so Brad showed you that there was two project that were created–one for App Engine and one for Android. So let's take a quick look to see what's there. Um, there's a lot of classes, actually, in there. Uh, the main one, really, that's going to be… [speaking indistinctly] the cloud task activity. Uh, that's the one that currently show the "Say Hello" button, or whatever. The account activity is the activity that you get when you go to the account menu to actually register your account, so it's there for you already.

But, you know, if you don't like it, if you want to style it, you can just change that easily. There's a C2DM receiver. We'll talk about that later. Um, and a bunch of–some utility functions and things like that. So to make my application, I'm going to need some new files, so I'm going to drop a few files. I'll show you what I drop, so that, you know, there's no, you know, hidden things happening in there. Uh, so mostly I'm going to drop, first some resources, there's, you know, some icons, some styles, some new themes and things like that, and some layouts here. Uh, we're going to have two new activities. So the current one, the main activity, is going to be replaced by one that display the list of tasks, and, uh, so that will be my task list layout here. with the list item to populate the list view. And then I have two other activities– one to add a task, and one to view the content of a task. So I'm just going to drop that into my project, uh, and then I'm going to drop some new code. So, um, I said, you know, new activities– Okay, so I got a disconnect here. It's no matter.

It's just that the application is currently being deployed, and we don't… [speaking indistinctly] Abrams: You should really work… [speaking indistinctly] That would be good. Ducrohet: Yes, I think I should. Um, so we have those two activities here– a view task activity and a task activity. Uh, I'm going to need a new custom application class, um–so I'll explain why later– and then after because, uh, I'm going to use a list view, so I have a list adapter specifically for my list of tasks, and then I have an asynctask, because you really never want to do any, uh, natural connection on the UI thread, so you should always use an asynchronous mechanism, and I'll explain to you later why you need actually that right now. So I'm just going to drop that here, uh, there's some compilation error, but that's no matter. The final thing that I want to drop is, um, after, we're going to have some sort of protocol in the communication between the client and the server, uh, so we have some, um, content in here that I'm just going to drop in that shared folder.

So Brad actually showed you that folder earlier, and it's there on my client, too. It's actually– it's not just a cookie. It's actually the same folder that is used by both projects. So when he created the RPC layer, uh, the TaskProxy was also created for that project here, and it's right there. It's an interface that's going to be created for you. Um, you don't have to go and do the same thing. That's where the two projects being linked together is interesting. So I drop that new, uh, class, and then I'm going to have to edit my–my manifests in– I did some components, so I'm just going to do that quickly. Mm. So the first thing that I need is, uh, on my application node here, I have a new class, so I'm declaring it here, and I have a custom theme, so I'm just declaring it here. And then, uh– that's not what I want– I have two new activities, so you always need to declare your activity, even if they're not, you know, they don't have.

.. [speaking indistinctly] So I'm just doing that here, too. Okay, so the last thing that we need to do is really look at that main, uh, activity. And, uh, so cloud task activity. So here, there's a few things like– there's a receiver here that you don't really need to deal with. It's there to deal with the, um, when Brad created that account, we got a notification saying the C2DM registration worked. Well, it's that one here, so if you want to change the, um, the workflow, you change that a little bit. Now there's an onCreate here that does– basically that HelloWorld content, so I'm just going to replace it with mine. So, um, actually, I'm going to start by dropping a few fields that I will use–use later. And just drop them here. Then, uh, I'm going to drop that and explain what I'm doing. Ducrohet: All right, so we're going to remove that here. So, um, I'm just doing a new site content view with my layout, um, I'm having some, you know, a listView object, I have a progressBar object, um, I have a task adapter, which is my list adapter from my listView that I store in my task application, uh, for some reason, and then I have my callback from my listView, uh, so I actually need to make that, um, class– implement that interface, um, and that's about it.

READ  Huawei P20 Lite Tips, Tricks & Hidden Features

And then there's this register, which also, you know, is registering the broadcast receiver that we have above to get the notification from the C2DM registration. And then, um, of course, we unregister it, in unDestroy. And then, uh, we have the menu creation, and that UI here, that we just don't need anymore, so I'm just going to delete it, and instead I'm going to replace it with all of that new code. So, um, it–it's a lot of, you know, boilerplate due to that demo, the way it's built, but the big thing here is really in the onStart– that's what I'm going to show you right now. Um, what I do here, if the–yeah, there we go– Uh, I just make sure that the device is registered, so as soon as the activity launch, if the device is registered and I have a Google account associated, I'm just going to go and fetch my task from the cloud, and that method is, uh, pretty basic.

You know, I display the progress bar to add some visual clue for the user, and then I create my asynctask, and I just execute it. And so that next task here with a nice, you know, "insert RPC command" here. So let's implement that, because that's really the part that's complicated. Normally, it's creating that– that RPC. So, um, we have this, uh, RequestFactory that's been created for you by the RPC layer, and it returns that TaskRequest, which is also located in the, um, in the shared folder here, and that's, you know, what Brad showed you. It's basically that new interface that has exactly the same method as the server service that you implemented. The only difference is that, you know, instead of returning directly the TaskProxy, to return the request object, which lets you actually do the RPC code. So we're going to do– use that. Of course, that's all interfaces, and, you know, you're probably wondering how we're going to create those. Uh, so what we do for you is, there's a utility method here, so, you know, it's not hidden from anything.

It's just there. You can go and look at it. So we're going to use that. So we're going to create our RequestFactory first, so, um… There we go. Ducrohet: And then there's the getRequestFactory, so it needs an– um, an Android context, and then the class of the RequestFactory that you actually want to, um, associate. All right, so let's look to see exactly what that does for you. Well, so first, there's that– it just creates it for you. That's, you know, the hidden part. Uh, the important part is here. It's actually going to look for the authentication cookie that was selected when the user selected the account, so it selected, you know, the Larry Page account, and then we got the authentication cookie from that, and that cookie was created, you know, when the user actually registered and tied it to his password. So we just saw that, and then we created the HTTP URL, that is– we're actually going to call, and we initialize the RequestFactory here with that AndroidRequestTransport, which contains the cookie/ And that makes it sure that when we do the HTTP query, uh, we're actually going to pass the cookie, so that on the other hand, the server actually knows which user is actually doing that request, and that's where the authentication comes in.

So we have that, uh, factory here, so now we're just going to do, um– so we need to get the TaskRequest for it, and then it's going to look like– exactly like the code that Brad showed. I'm just going to do a query task, do a fire, um, so the only thing that's a little bit different here– So the receiver is the– The type is based on the method, and here, it's a list of TaskProxy. There we go. Uh, and now– there we go. So that it–that's the method that you need to implement. There's other ones, like on failure, that you should probably implement, because if it doesn't work, it throws a wrench on us again. Abrams: We're not gonna have any failures, though. Ducrohet: We–no, no failure today.

Um, all right, so here– well, I have that list here that I'm going to return later, so I'm just going to add the list of tasks that I get to, uh, to my list– so the point here is, so right now, that act– that fire, that method that Brad showed on the GWT client, was, uh, asynchronous. On Android right now it's not asynchronous. Uh, it's actually a bug. It should be. We're working on that. Uh, so that's why I need to put all of that into an asynctask, so that we're sure that, you know, that doing by group method is going to happen automatically for you in the background, and then basically, that list here is going to be passed back to me in the onPostExecute, but that onPostExecute is called from the UI thread, and that's a very important distinction.

And then here, um, I'm going to… I guess I need to save that file here and do some import, yes, to make sure that it works. There we go. So I can go to a task– and so activity addTask, um, you know, all I do basically is pass back to my list adapter, give it the new list of task, and call notifyDataSetChanged, which is going to refresh my listView. Those code must happen on the UI thread, which is why I do that in the, uh, onPost background– uh, onPostExecute method. All right, uh, I think I have everything. Now, uh, we've recently encountered a bug into, uh, probably Eclipse, so I'm just going to do a clean view. You haven't seen anything. Abrams: Mm-hmm. Ducrohet: Um, all right, just to be sure, and we're going to deploy that to my emulator. So that's crazy. Oh, I forgot to show you, um, when you create that project– when that project is created for you, we do put some Java here, like, the C2DM Java is put there for you, and also the RequestFactory client. This is the part that deals with the RPC for you, so it's all there for you.

You don't have to deal with that. All right, so let's just debug that on my server– on my, uh, emulator. So, uh, as I showed, as soon as the activity is going to start, there's going to be a progress bar in the top right that will show that there's an RPC going on. Maybe. Oh, there it is. Okay, waiting for debugger. All right, so you see here there's an RPC going on, um, and so… Abrams: So it's talking from the emulator to the App Engine service, and it should find those tasks that I added via the GWT client. Ducrohet: Yeah, and it's running on the App Engine server, running in Eclipse… Abrams: Right. Ducrohet: So you can put breakpoint in that server, too. You can put breakpoint in the client. You can really debug both sides at once. And there we go. We have an RPC that happened. So we have those three tasks, and then if I click on them, um, you see that which– we have no task detail in there.

Abrams: That's beautiful. So that is a nice-looking, um, Android client, but I-I think you should give me a chance to work on the GWT client and make it equally nice. Do you think– we have a few minutes. Ducrohet: You think? Abrams: Yeah. Ducrohet: All right. Abrams: Okay, um, okay, so let's get this Android stuff out of the way here. Um… Ducrohet: [laughs] It's only fair, I put his stuff out of the way when I started. Abrams: Yeah, okay, so now we have a– we have a-a few more files to drag in here. This is our fancy, uh–whoa. This–this is a, uh, Um, I hope I didn't mess that up. Okay, here– here's our fancy GWT client, um, we'll copy in, and basically, we're just using a UI binder to make it a little nicer, and then, um, if you're a web developer, and you're used to doing a lot in CSS and HTML, uh, that works really great with GWT, as well. You just put that stuff in the WAR directory, um, and that gets bundled so it ships correctly.

Uh, and so with those in, now we should be able to switch to our client, um, and refresh it. So I–just notice, this is running in DevMode, um, but we don't have to stop and restart the server, so you can–you can just keep refreshing the browser as you normally would. So that's pretty nice. So those are the tasks. Um, and I think what we should do is I should go and add another task, like, um, How about, Do I/O twice a year? You think that's a good idea? Something–yeah? Ducrohet: Yeah. Abrams: So we'll go ahead and add that, and you see the little Android guy–okay. Um, and then we look over, and, you know, Xavier, um, I added this task, and I don't see it showing up here or anything. Ducrohet: Yes. Abrams: I–you know what I think you should do? You should implement some polling log. You should, like, every few seconds, you should ping the App Engine server, and then it'll tell you– Ducrohet: You're not listening when I'm talking.

That's what I said we shouldn't do at the beginning of the talk. Abrams: Oh, you shouldn't do that? No. Okay, So what– Ducrohet: Yes, you know, the graph, C2DM? Abrams: Oh, I remember that. I remember that. Ducrohet: That's what you need to do. Abrams: Oh, you want to do C2DM? Ducrohet: Yes. Abrams: So what you want is when I add a new task, you want me to send a C2DM ping that you'll pick up? Ducrohet: Yes. Abrams: Okay, uh, luckily, there's a nice place to put business logic like that. Uh, it's in the RequestFactory. It's in–sorry, it's in the CloudTasksService. So basically, what he's saying here is in update, we need to send the C2DM method. So this is back on the App Engine server in the cloud service, um, and Xavier's nervous that I'll typo this, um, so I better copy and add on the snippets. Um, so, uh, let's just replace this implementation with it. Uh.

.. Ducrohet: So the only reason we do that is that there's that particular myth out where we need to first update the datastore to make sure that we get the right I.D. once it's told, 'cause otherwise– Abrams: Yes, it crashes. It's bad things. Ducrohet: Yeah, we've designed a smart protocol here that is very busy, 'cause you can see, the message that we sent is just– so normally C2DM, most applications will only use it as a tickle, just to basically poke the app and say, "You know, something happened. Talk back to the server to get more information." Uh, but it–by C2DM, you can actually send a small string– Uh, I don't actually know the limit, but, you know, you can send a small string, so here, we're just sending basically a keyword, in that case, update, colon, and the idea of the task that we want. Abrams: Yep. Yeah, so we just made that small change, exactly as Xavier described.

Um, and then we just need to refresh the App Engine server, so we leave the GWT client as it is, refresh the App Engine server so it reloads with that– with that new code. And now what we should be able to do is go back to our cloud task and, let's see– "Give all employees a raise." I think that's a good– good task. And then let's bring up the emulator here, so you can see that worked. So I'll just hit "Go" on that, and then flip over here, and there's the message, and let's see if the I.D.– there's an I.D. Okay, good. So I think it's pretty good. So we're sending the C2DM message from the App Engine server, but, Xavier, you know, that UI with just I.

D., colon– I don't think that's so hot. Ducrohet: Yes, yes. Abrams: You know, maybe there's something you can do. Ducrohet: All right, so, um, here– so normally, when you build an application, you are not going to do what we're doing here, which is basically go live and request an RPC every time the user does something. Uh, what you want really is to have, like, a local storage, and have your task there. Uh, not that you can't receive a C2DM message even when the user is not using your application– that's the whole point– so what you should really– really do is, when you get a C2DM ping, you should, like, you know, launch a background service, go query the new list of tasks, have some fancy protocol so that you only get really the one that happened. So in our case, it's that..

. [speaking indistinctly] Uh, and then, you know, put that on your storage, and then your activity should only go and query your storage. All right, you shouldn't do– like, here, every time that activity is displayed, it goes and fetch back the list of tasks, and it's really not efficient, so you shouldn't do that/ But for that talk, we didn't want to go and, you know, involve the storage, so we just wanted to see the basic… [speaking indistinctly] mechanism happen. So what you are going to do here, uh, to bypass that issue, is we–what I did is, that activity registers itself as a listener to the custom application class that I did when it's being displayed on– and register itself when it's being hidden, so that's what we have in, um– Uh, let me find, uh– there we go.

Here. We'll just do that. Uh, so in the– there we go– in the onResume, I register my task as a listener, and here, I register it as an onPost. So what I need to do is basically to call that listener, uh, from my C2DM receiver, so that if the task– if the activity is not showing, then it's not going to do anything, and the message will be lost, uh, which is okay. So I mentioned that we had, in the project that is created, we have a class called C2DM receiver. Now that's a very basic class. There's a lot of things going in the background for you. But basically, there's this onMessage here, and that's what happens– that's what is called when the C2DM tickle happened, so you can ignore the rest. All you have to do is basically implement that, which is exactly what I'm going to do.

And that message display class that is created here is the, uh, notification thing, you know, you can throw that class away once your–if you don't want it. All right, so, um, I'm going to get my application, so it's a taskApplication object, and you can just call getApplication. It's just not our class in the receiver. Oh, I'm going to need a cast here–there we go. And then I'll do my notifyListener. And pass the intent– that contains the content of the C2DM. So in that method here, um, basically, if I have a listener, then I have to do some work, so I get that message from the intent. Um, and then I just do a split on the column, and send that to my listener, which is my activity. And that's what happens here.

Now as I mentioned earlier, you can get that C2DM tickle– you get it on a non-UI thread, so if you want to do some UI on it, you have to use, uh, run on your UI thread. Uh, and I really do that, because the asynctask needs to be started from the UI thread. So you're receiving from the non-UI thread. You go to UI thread to start the asynctask, which does the work in the non-UI thread, which sends the wizard to the UI thread. Abrams: Whew! Ducrohet: Okay, so here in my FetchTask, uh, I had, you know, planned for it, so I have a long– I have the I.D. basically as a parameter, so I– and I pass it here in the execute. So I'm just going to change it so that when we have a new task, uh, we can just go and fetch that one. So, uh, it's going to be pretty easy. I have no arguments here, so I'm just going to do– if I have no arguments, or if my arguments is– by default, I said… [speaking indistinctly] fetch all, so I'll do that.

And else, um– I'm setting a flag saying that it's just a new task. I'll explain that in a minute. And then I'll do another, um– and so here it's readTask, which is exactly the same one that you had in– that Brad showed on the server, and, uh, that's my argument. There we go, and then I need to do a fire, which, uh, should be asynchronous, but as I said, isn't. All right, and, uh, we send the TaskProxy here, and I implement my method. There we go. All right, and here, that's pretty easy. I still have my list. What I have to do is do list. add… [speaking indistinctly] All right, and so at the end of that execution here in the– that's where I do– I use my flag, uh, that setTask– actually, I showed you addTask earlier, but, uh, setTask here sets the whole list, and if I do "add," I just add to the existing. And basically in the "add after," I mean, all I have is really just a list of tasks that– and then I just sorted by date, basically, so very easy.

READ  Fusion 2.5 - Backdrops and collisions tutorial

All right, so, um, let's make sure that everything is saved here. Okay, and it's saying disconnect again–I'll work on it. Abrams: [laughs] Ducrohet: Um, and let's just deploy that as an Android application. Ducrohet: So the first thing that's going to happen is that it's going to restart my activity. It's going to redo that initial fetch, where it's going to get all the lists of, uh, activities– oh, of tasks. Ducrohet: All right, so same thing here. So there's that, you know, visual cue that the RPC is going on. Um, when I'm going to enter a new task, you're going to see that same visual cue, so just look at it when I hit "Enter." So we need a new task–um, oh, yeah, there we go.

All right, let's just wait for that to finish. So there's still–you know, it's debugging– the debuggers are connected to both the server and the front end, uh, it's going through the emulator. That's not exactly the fastest thing these days, so, you know, that's why the RPC takes a little bit of time. All right, so we have our task. I'm going to hit "Enter," and there we go. The C2DM was received, and then automatically, we do the FetchTask, and it's right there. We just refreshed it. Abrams: Yeah, so that is very good. [applause] Abrams: That is very cool. Just to recap–so, you know, a few of you that were clapping really got what was happening, and for those of you that weren't clapping, I'm gonna explain it to you so that you can–no… Um, so it's really–it's really impressive what was going on. So he–from the AJAX client, typed in a new task, hit "Return." Uh, that animated the cool little Android thing, um, and then– and then it did an RPC call to the server, stored that item in the highly scalable datastore, and then when that came back, it sent a C2DM ping, um, to a Google service, which then remoted it back down to the phone. The phone got the ping with an I.

D. in it, like, was it 7 or something? Uh, 7, and then– and then he got that 7, and then he said, "Oh, I'm gonna go do an RPC call, and find task with the I.D. 7." And then he did an RPC call back to the App Engine server, got that task, um, and brang it down to the client and displayed it, so it was very cool. Um, I think we're– I think it's pretty good. I think at this point, we're ready for them to use it, don't you think? Ducrohet: Sure, yes. Abrams: I think we should just deploy it to App Engine, so let's talk about how we do that deployment. So, um, what do we do? Uh, we go into "setup." Um, so there's one– one little thing. You go into "setup," uh, and you need to change this to match your App Engine I.D.– whatever–you can go to App Engine and register a free I.D.

You have to be somewhat creative with the names. Um, Sean's working on that– Uh, but right now, it's, uh, you have to be fairly creative to find a unique name. Um, and then you just–whoops– uh, you can't deploy, actually– yeah, you just– Ducrohet: It doesn't type, also. Abrams: Where is it? Ducrohet: Just Android–andriade. Abrams: Oh, yeah. We don't want to deploy to the wrong place. Um, and then we don't deploy the Android application to App Engine. We have to go to the App Engine application. Um, you just click this cool little icon here, um, and then in here, you give it the same I.D. Um, hopefully I typed it right– and then the cool thing is, you can use any version. So I'm gonna use the version "staging" right now, 'cause I'm gonna get it up there and test it out before I flip over and have everybody use it. Um, and then we just click "Okay," uh, and "Deploy." And now what's gonna happen is it's going to build a highly optimized version of the GWT client for– for many permutations of languages and browsers, so that each browser gets exactly the minimum JavaScript they need.

Um, and so while that's going– and then it's gonna upload it to App Engine and verify it's there, and you'll get a URL back. While that's going on, though, we already have one deployed, uh, ahead of time, kind of Julia Child style, um, up there, and we have it installed on these phones, so why don't you switch over to the thing? Ducrohet: All right, so, uh, we have two phones here, so I'm going to add a task to that one. Um… Well, I'm not going to type a whole lot on here. Um, there. "Save." So here you can see also, there's the RPC that's actually sending this time, uh, and then actually– or, querying. It just did– okay, it's there already. Um, it should happen on the other one. Oh, it's on WiFi. [laughs] Abrams: Oh, yes. We have– we might have a WiFi– We need everyone to get on– No, I'm joking.

Um, well, maybe it'll– maybe it'll show up. that one– Ducrohet: Yeah. Okay. Abrams: Okay, so we have it– we have it on one. Do you want to add– Ducrohet: Okay, yes. Abrams: Yeah, so let's switch over to this one, to the Mac, and so we have the exact same client. Um, and you'll see– I think we'll see, in just a second– um, that he– we got the exact same task here. So, um, if you add another task there, and just tell us when you hit "Go" there, so we can see how quickly– and you see, I'm not touching it, so I'm not gonna refresh the browser. This is one of the benefits you get from Request– using RequestFactory in GWT, is that as soon as– Ducrohet: Are you ready? Abrams: Yeah. Ducrohet: One, two, three. Abrams: Three, okay, so it sent it up to the App Engine server, and then back down, and there it is–again.

Um, so that's very cool. [applause] Abrams: [speaking indistinctly] Ducrohet: [speaking indistinctly] Abrams: Okay. Okay, so I think we'll– Ducrohet: Let–let's do it the other way. Abrams: Oh, you're gonna do it the other way. Okay, yeah. Ducrohet: So I-I'll switch back to that one. Abrams: Uh, you know, what's something that needs to be on Larry's task list? What should we add on Larry's task list? I need a– man: [speaking indistinctly] Abrams: [chuckles] Oh, yeah– better WiFi at I/O next year. Ducrohet: Yes, that should be good. Abrams: Okay, so now I'll do that. I've–I've sent it. Ducrohet: There you go. You've got the notification already, and there it is. Abrams: Yeah. So…

[applause] Abrams: Awesome. Ducrohet: Okay, very good. Abrams: Okay, so that was the demo, um, so just the summary of what you saw. Uh, we think that as an Android developer, you can increase your customer's love by building cloud-connected Android applications. We think App Engine is a really fantastic technology to go use for that, and we hope that this beta of GPE 2.4, uh, makes it very easy to go do that. Now I imagine some of you might want to play with this project, so it's available live right now– the exact, uh, one we were just running over here– if you go to the running application, cloudtaskio.appspot, and we can test the scalability of App Engine right now, um, by all of you going to that. Um, all of the source code that we typed in, this full project, is available for you at the code site there– cloudtasksio. Um, and then I thought it was very cool– the top project is just the starter project, just showing you C2DM working and the RPC working.

Uh, that's available for you to play with on your phone at that top URL. Then, um, hopefully, this gave you just a little taste for things, so Android sessions–do you want to– Ducrohet: Yeah. So, uh, first, if you want to go and download the Android developer tools, it's on developer.android.com, and we have, uh, two other sessions– the "Android Developer Tools" is tomorrow afternoon in room 11 at 3:00. The "Best Practice"–I don't actually know when that is. Abrams: I can't remember. I think it already happened, actually, so you might have to YouTube it to get there. Ducrohet: Oh, okay. Abrams: And then the App Engine and GP– a number of great sessions, um, that, uh– go to, uh, Google code appengine. Um, you can find all kinds of good data on it. Um, a couple of good sessions to go check out– the "Bringing the Cloud to Your IDE.

" If you want to hear more about things we've done with GPE, that's a good place to go look, um, and then "Scaling App Engine Applications," because I know your Android app cases will just be fantastically big– huge, I mean, successful. Um, okay, and then, uh, we– one thing I wanted to stress, um, I'll put it back– Uh, one thing I wanted to stress is we really need your feedback on this. We're very early. Uh, this is a very raw state, uh, for GPE 2.4. We want you to go build some apps and give us feedback so we know what we need to do to polish it up and finish it off, so… Ducrohet: And then we have five minutes for questions. Abrams: Yes, and we have five minutes for questions, so… Ducrohet: So there's two mics there, so… Abrams: Any quest– it was perfectly clear? man: Hi, I have a question for you. Abrams: Where are we? Over there. man: For.

.. [applause] man: For the RPC service that you're providing here, how are you serializing the data? Are you using proto buffers, JSON objects? Abrams: Yes, uh, it's serialized as JSON– I don't know the techn– somebody's gonna help me out in the front row? [chuckles] It serializes it as JSON… man: Okay. Abrams: And I don't know what technology it's using, but you can find that out. man: Okay, no. Thank you. Abrams: Anyone got a– Whoops, over there. man: You showed us how to create an app from scratch using all the generated code. What about taking an existing app and adding App Engine functionality as an added feature to it? How do you utilize all that generated code without having to start from scratch on your project, importing all your old code? Abrams: Yeah, I-I think the project structure we're using in Eclipse is a– I–we did a lot of really good work, but it's not rocket science work, and so you should be able to kind of go and see how we did the link folders, and whatnot, to make it– to make sure the tooling wizards and stuff will work. Ducrohet: Yeah, I mean, the only diff– the only hard part is the shared folder, which is easy to replicate, but, yeah, you could definitely just create a new project and then just drop your current Android application or App Engine in there.

I mean, it's–you know, that's the first very rough version. Uh, definitely it's one thing that we need to improve in the future, and have, you know, better things to work on for existing projects. man: Okay, thank you. Abrams: Cool. man: Uh, you showed, uh, Java Google App Engine implementation. Do you have a sample code or something for a Python-based? Abrams: Uh, no–so, yes– So App Engine supports Python just as well as it does Java. We–we chose to build the tooling in Eclipse up around Java, um, because it's just more common, same language, you know, on both sides, Um, the same basic system will work. Uh, we don't have the RequestFactory framework for Python yet, but that's, uh, I mean, if that's something people are interested in, it's good– really good feedback.

man: Okay, thank you. man: Is there anything special we need to do with the Android project to make it compatible with Proguard? Ducrohet: Um… that's actually a good question. Uh, there's probably– yeah, you're probably going to have to make sure that all the classes, like the TaskProxy, and all of that that are generated do not get renamed by Proguard. man: Mm-hmm. Ducrohet: Um, that's– yeah, that– I mean, at first, you're going to have to do that manually. It's definitely something that we'll have to think about to maybe integrate into the, uh, like the RPC tooling, so that it automatically add to the Proguard file, but right now, that's not… [speaking indistinctly] It's a very rough version. man: Okay, thank you. Ducrohet: But it's– it's a very good feedback.

man: Um, you showed GWT–you don't actually need to refresh to get the task updated. Abrams: Mm-hmm. man: What technology are you using for that? Are you using long polling? Are you using the channel API that's built into App Engine? How are you doing the push request from the server? Abrams: Yeah, uh, I believe it's doing long polling. It's using RequestFactory functionality that's been in– Is Chris giving me the answer? No. [chuckles] It's been in for– man: [speaking indistinctly] Abrams: Yeah. Yeah, so I believe it's doing long polling. man: Is there any, um, plans to support the channel API in the future, or is that not really where GWT is headed? Abrams: Chris, do you want to help me? Chris: This actually came up in the fireside chat today. We, uh.

.. Abrams: Go–go to the mic. Go to the mic. So Chris is the PM for GWT. Chris: Hey, how's it going? Hey, great demo. Yeah, that actually came up in the fireside chat today, and we're–we've had a couple teams messing with it, and trying it out, and, uh– so, yeah, we're looking into it. Nothing on… [speaking indistinctly] Abrams: Okay, thanks. man: [speaking indistinctly] Abrams: Okay, yeah. man: I have a quick comment for Chris– He should use this as part of a sample application. This is better-looking than what you have in GWT showcases. [laughter] man: And, uh, second thing is, uh, this won't work with remote service, right? This is for RequestFactory, so we'll have to put RequestFactory? Abrams: Yeah, this is RequestFactory. man: Okay, thank you.

Abrams: Yeah, we had a lot of debates about it, but decided to go to RequestFactory. man: That's fine, thank you. Abrams: Okay, yep, last–last question– man: You generate a lot of code. Is there any plans of putting that in the framework instead? Abrams: Yes, again, something we're debating a lot– we did actually put some code already for C2DM in the framework. Um, we felt a little weird about putting code in a framework that writes directly to DataStore, for example, because you might want to use a different store. Um, but it's a balance that we're going back and forth on. I think we erred this time a little bit more onto the generated code than on the framework, but that's an area where we're really looking for feedback, so jump on the forums and give us some feedback. Speaking of which, um, uh, we would–we would really love your feedback on the session, as well– Um, you can–you can tweet here, and I believe that code takes you to the feedback forum. man: Okay, and you can use that also from the, uh, Google app, you can go to find that link.

Abrams: Yep. Yeah, and so one of us has to buy drinks for the other based on your feedback, so please–please put that in. Ducrohet: [laughs] Adams: Okay, and we'll be around here for questions, uh, if you want to just come up, or in the Android booth. man: Great. Abrams: So thanks. Ducrohet: Thank you very much..