Testing, JSON serialization, and immutables (The Boring Flutter Development Show, Ep. 2)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
ANDREW: Hey, welcome back to the Boring Show, the roughly an hour-long show in which you get to see two engineers coding an app basically from scratch, making mistakes, looking things up, arguing about what to do, and so forth. This is our development show. It's not actually a Flutter tutorial per se. We do have a bunch of those, and you can find them on the Google Developer channel, and also some at flutter.io. So if that's your jam, you can find those there. FILIP: Also there's a bunch of community ones. So if you just search YouTube for a Flutter tutorial, you'll find a lot of amazing tutorials. ANDREW: Also true. All right, so this is our second episode. In our first episode, we kind of did that as an experiment just to see if anybody would want to watch us code for an hour. And we got about 12,000 views as of right now. We got a bunch of comments. It seemed to be some good interest there. So we're going to do some more of these. So this is our second episode. And one other thing to mention-- we read through the comments on the first one. We got almost 100 something comments I think on the first one. We actually read through them, and we wanted to call one out right now. FILIP: Yeah, someone actually found a bug. So-- I forgot the name-- Mihai, Mihai Minola found a bug in our code. So we want to address that first. And then obviously there's been a lot of other comments, mostly pushing us to the more advanced topics. ANDREW: Oh, that's something else we should talk about. FILIP: But first let's fix the bug. So the bug was about this. If you look at the app-- so this is exactly the state of the app that we left it when we stopped developing last time. And so we had this app. You can open the tile and then you know click on stuff and get somewhere. And we also implemented the fake reloading-- so if you drag to reload. So first of all, if you reload, it will just remove the first article from the top. So where's the code for that? Here. So article stream of add zero. So that's great. But watch what the tiles that are open would they do. So when I reload again, the opened is the next one in the-- so basically the list view only remembers which number was opened. And it still has that number open, which is not what you want. You probably want to, if someone actually reloads things, you want to collapse these things or maybe keep the one open that you had open before. So thank you, Mihai, for putting it this out. I didn't see that until he put it out. The fix is simple. It's basically you have to tell the list view the identity of each of its children. So normally you don't care or the list view you doesn't care. But if you somehow mutate the items of the list, then you want to say, OK, so this item is x. And even if you put it somewhere else, it's still that same item. Otherwise, it will just say, OK, so this item number three is item number three even though we changed the view. So anyway so that's why most widgets, or probably all widgets, have the key argument. And you can add a key to it. Now the key takes a string, which should be something that is unique to this particular list view. So in our case, it's pretty simple. We can do article URL, which we don't have at this time. We'll get to this. But let's just use stacks. Let's assume that all the articles are unique by the text of the article. And so now if we do this and we open that, it should just clear the opened thing. And that's probably what you want. If you wanted this to be open, then we would need to do some other stuff. But we don't have the time. And I think we don't want to do that. This show is about JSON and JSON-- JSON. ANDREW: Yes, so that's what we're going to cover today. We got a lot of requests in the comments to do something a little bit more advanced. And so we thought JSON parsing is on the way to advanced but still something you're going to see in almost every app that's using real data. Everybody uses JSON these days to pass things around. And so we are also going to talk about testing a little bit-- also something that almost everybody is going to run into at some point. I don't know a lot about testing because I just do everything perfect the first time. FILIP: That's right. ANDREW: But Filip knows a good bit about it. FILIP: Yeah, yeah. I have to test every-- as you can see, I do a lot of [INAUDIBLE],, so I have to test everything. So fortunately for me, as a pretty good testing framework-- ANDREW: There's like three different ways to test, right? FILIP: That's right. So I mean, Dart itself has a testing framework called package test, which I think is pretty sweet. And then Flutter builds on that with Flutter test testing framework, which lets you basically do kind of headless Flutter. And I'll show you that in a moment. And then it has a Flutter driver thing. ANDREW: It's like full on UI test. FILIP: Yes, yes. So if you're like familiar with Selenium on the web, that is basically that. ANDREW: Do you want to pull up a testing guide real quick just to show it to them, right? We have a doc on flutter.io that actually goes into a lot of these details as well. So that's a resource. That's flutter.io/testing. FILIP: Yeah, I forgot about this, but they even have units versus widget versus [INAUDIBLE].. So here's a widget test. And basically this is a smoke test. And the fun thing about this-- this gets included whenever you create a new app. You have this test with it. Since then, we totally changed the app. So this should fail. So let's try it. ANDREW: Is this built for the counter example? FILIP: Yes. So this basically says, oh, pump widget-- so everything in Flutter is a widget. So even your app is a widget. So you say to the tester, add this widget. And then I expect to find one widget that says zero and no widget that says one. And then I want to tap the thing, which has an icon, which is this one, the @ icon. And if you know that app, you know what I mean. There is an [INAUDIBLE] that lets you tap it, or FAB-- how is it called? ANDREW: The Floating Action Button? This is the default app that we're talking about if you just go new project and Flutter. FILIP: And then so you tap it and then you say tester pump, which tells to the testing framework, hey, do things. ANDREW: Run a loop. Run a cycle. FILIP: Yeah, and then it expects one widget to say one and zero which is to say zero. And so that already has five. So this failed very soon. It failed on number 18, which is the very first expect because there's no widget anywhere here that says just zero. So we want to change this test. We still want to do my app. We don't need new now. And what I'm going to test is simply just that, if I click on the tile, it will open. And how I'm going to-- so clicking a tile opens it. ANDREW: I don't think I've ever done a widget test. This is actually good for me as well. FILIP: So it's all asynchronous, obviously. So you want to do-- so first let's expect that you get this nice object that lets you find things. So find by type, and that's going to be an expansion tile. And that should be-- wait, what? No. No, no, no. What I want to do-- so let me walk you through what I have in mind. We expect that there is no launcher icon here. First we need to make sure that we are not actually having a false positive. So we want to know that there is none of these at first, which there isn't. ANDREW: Ah, I see. FILIP: And then we click, and then we find out that there is. ANDREW: Hopefully. FILIP: Right. So we want to find by icon, I guess. So find by icon, and it should be the launch icon, which is helpful [INAUDIBLE]. ANDREW: Yeah, it's so nice to get those previews in the icons and the colors. FILIP: And finds nothing. And then we do a way to tester tap, and again we find by type this time. And it's going to be the expansion tile. That's what I was going to do next. And this will find many things. So we want to say either just click on the first one. Let's just click on the first one. And yeah, that's it. And then we need to do tester pump. I'll get to pump [INAUDIBLE] soon. And then we want to expect that this finds exactly one widget. And now we can run it. And so you'll see this doesn't run this on the emulator. It just creates its own kind of Flutter framework and this passed. And that Flutter framework kind of like-- so it has everything in it. It should be the exact same thing as what you run in the emulator or in an actual device. What I'm going to do is we can actually-- so you can do Flutter test, which I just did. And so most of this, this is just waiting for the testing framework to go on. And then the actual test runs in milliseconds. ANDREW: We should up that font. FILIP: Oh, yes. ANDREW: There we go. FILIP: Oh, maybe not so [INAUDIBLE].. ANDREW: That's all right. We're just looking at this bit anyway. FILIP: But you can do Flutter run test, which it tests, and look what happens. A whole-- well, another thing here. So I don't want-- we'll see. ANDREW: So it's actually running it on device now. Before it was not. FILIP: Yes. So this is syncing files. And look what happens when I-- I really need to do this. Now I'll halt reload or halt restart and it's going to do [SOUND]. It made the test. Well, let me show it again. [SOUND] ANDREW: That's Filip making the noise by the way. That is not part of Flutter. FILIP: Yeah, that's not part of Flutter, yeah. ANDREW: I'm enjoying these unescaped color codes in the input. That's interesting. FILIP: So that's not what you normally run. This is for you to make sure that this does what exactly you want this to be. So one thing that why it's so fast is that it's a tap, and then Flutter runs one frame, basically. And then by that time, because we don't have any fancy animation that will create the state that we want to see after sometime, but it will instantly create the state and then it will animate to it. So we can use pump, and it's very fast. If you have, and you will often have, something like, oh, I click here and then there's an animation, and then after that animation something gets created and I want to do that, you do pump and settle, which will run more fluidly. ANDREW: So Flutter has-- it's a single threaded, Dart single threaded, right? Let me make sure I've got all of this. So you run your Flutter app. You have one thread that's doing an event loop, waiting for events from the outside. Then you also have a microtask queue, I think, which I sort of understand. But you mainly have one thread. So every time you hit pump, it's going to do one go round and process the top most events, I guess. FILIP: Yes. ANDREW: But if you do pump and settle, it will start and then it keeps going until there is no more events to go through, and then it'll stop. FILIP: Yes. Let me show you how that-- so now our test still runs and should just finish with a success. If it was a lot of pump and settles, it might take a little more time because it does these things. So for our testing, we don't need this. But for our you to see what's actually happening now if I perform [INAUDIBLE],, you should see the-- did you see it? You should see the whole interaction because it actually waits for this to complete. And you can even, if you're very good at this, you can see a little target where it's tapped. [SOUND] So anyway, our tests are passing. So let me go back to here, and I don't need this. Oh, and by the way, you can do things like-- I want to wait for a duration of nine days for this to finish, and it will still finish in milliseconds because it's all fake. ANDREW: It'll fake a clock too, right? When you're doing these tests. FILIP: Yes. But yeah, as I said, we only need this. And we have our widget test. So that's cool. And now obviously we would test it on development. So what we want to do is, if we're going to focus on JSON and parsing in general and getting things from network-- but this comes later-- we want to start with a test. And then it will fail, and then you will implement that. ANDREW: And then we can do some coding. FILIP: And we can do some coding. ANDREW: Because we're very responsible people, and we always code this way. FILIP: Yes, always, of course. So JSON test-- this is going to be I think-- because this has nothing to do with Flutter. We're just passing-- ANDREW: Yeah, this is just straight up Dart. It's supposed to be a unit test, right? FILIP: So, this is going to be a test, a unit test. And so parse this-- do we want to talk about the Hacker News API? ANDREW: Oh, yeah. You want to pull it up? FILIP: Yup. ANDREW: So the fine folks at Hacker News and Firebase, this is their API. And so there are two calls here that we're mostly concerned with. So there's the way that Hacker News does their API. They basically just take all their f objects, which I assume are just sitting in a hash table, a distributed hash table somewhere. And they just sort of dump them into this API as is. So there are two calls that we're concerned about. There's one that gets you the top stories by ID. FILIP: Yes, so something like this. ANDREW: And actually click it, and we can actually see exactly what it is. There we go. So right now those are the IDs of the top, I think, 500 or so stories on Hacker News. So we would want to get that because that's what we're displaying. So we get those IDs, and then there's a second call, if we back up one. And I think you had it up above. There's get a story by ID. FILIP: Right, like this. ANDREW: And so this is a single story, and you can fetch that by ID. You can see, I think, it's 8863 is the ID for this one and the URL. And of course, this is the famous Hacker News post that announced Dropbox. And so we need to get a list of ints. That's the first one. And then we need to be able to get this object multiple times, multiple different ones of these, and then deserialize that data into off the wire into one of our article objects that we made in the first episode. So we'll be focusing on that work, I think, for the most of the rest of this episode. And so those are the two things we do. So we'll need a function to take a string representation of a JSON list of ints. FILIP: So parses-- how do they call it? They call this top stories? ANDREW: Yeah. FILIP: Top stories, JSON. So testing framework-- it just takes a bunch of tests that are done by this, and then it has expects in that. So we want to have something like-- let me actually take this. Maybe I don't want to [INAUDIBLE].. So we have JSON string. ANDREW: Yeah, just a giant string constant. FILIP: So that's that. And then we want to expect. Let's just say-- OK. ANDREW: We're going to have to put a method somewhere. FILIP: Yes, so parse top stories is going to be a method that we don't have yet, that takes that string. And we want to expect that the-- oh. ANDREW: What are we actually testing? FILIP: We are hopefully testing that it-- for example, the first one of these-- OK, so first is this. I know it's not the perfect, best test, but you know. So now we need to implement this. I'm going to add it here. ANDREW: Well, do you want to go ahead the test for the second case? Then we can do them both at once. FILIP: You're feeling adventurous. ANDREW: Oh, that way we have the test done, and then we can go to our other file. FILIP: Yeah, that's true. So item looks like something like this. And again, I don't-- ANDREW: Yeah, take the pretty printing off. FILIP: And so the JSON string is going to be-- this is broken because we are [INAUDIBLE] this. ANDREW: The joy of single quotes. FILIP: Are there any single quotes? Because if so-- ANDREW: No, there shouldn't be. FILIP: So another trick is just to do something like-- ANDREW: Oh, that's right. You can do the triple quote. FILIP: And for the sake of space, I'm going to do [INAUDIBLE]. And this should be parse item. ANDREW: Yeah, they're for [INAUDIBLE] items, I think. Because in this API, everything is one of these objects-- like comments and polls and stories, they're all-- FILIP: I wonder if like, if in this function, we're going to assume that this is an article and not a comment, we should call it parse article and not item. So like this. And this should say something like, oh-- ANDREW: Does not generate exception. FILIP: That's one, but let's assume that we will have a by, maybe, field on that. So this will give us an article, and the by field it should be D. Houston. So now on to a new class, I guess. ANDREW: Yes, where do you want to put these? FILIP: So I was thinking we'll just lip. But we could do-- well, yeah-- ANDREW: I mean, if we're just if we're just working out how to do the JSON-- we're not really touching on app state stuff yet-- we could just put these as top level functions in some file. FILIP: Sure, yes. Yes. But, oh, yeah. So JSON parsing? ANDREW: Yeah, I mean, this is going to be a short-lived file anyway. We'll rip this out once we move on to something more-- FILIP: And I just want to make sure of that-- so this should give you [INAUDIBLE] list of end. It's called top stories, and it's a string of JSON. And this should [INAUDIBLE]. ANDREW: Is there an unimplement? There is. FILIP: And the next one should have an article. ANDREW: Which we're going to need an import for that-- article.dart. FILIP: So parse article, again, string of JSON, and throw an unimplemented error. And we need to import this. Cool. ANDREW: Boom. FILIP: So now I just want to run this test. ANDREW: And so this will fail. FILIP: This will fail miserably. ANDREW: How do you catch an exception on a test, like if you know it's going to throw one? FILIP: Oh, yeah, it's-- ANDREW: It's like except exception or something? FILIP: Yes, throws. And you could say, throws something, like throw at whatever error. ANDREW: I often throw cyclic initialization errors in my code. So that's cool. I do that all the time. FILIP: Yes, that's a feature? So do you want to take over? ANDREW: Yeah, we could switch. Do you want to do an effect? We can just like switch? FILIP: That was it? ANDREW: They can cut this part out. It'll look awesome. FILIP: Yeah. ANDREW: And we're switched. So now my job is to go back to JSON parsing here, and parse some JSON. FILIP: Yes. ANDREW: OK, so let's start with the list of ints. So let's get rid of this. And the first thing I need to do is take the string and parse it as JSON. For that, we need the handy-dandy Dart convert library. Thank god that's the first result. There we go. So the Dart convert library can do this for me. And this is built in. This is not a package I have to go get it, right? FILIP: Yup. ANDREW: So let me go back in here, import Dart convert. There we go. Cool. So now we can convert some stuff. And so what I'm going to use Dart convert for is just to take the string and generate that map of strings to dynamics. It's a pretty popular pattern in a lot of languages where you take a string of JSON, give me a map with all the fields in it. FILIP: So yeah, so while Andrew is working on this, I think what we want to do is first show you the most basic way to parse these. ANDREW: Oh, that's a good point. FILIP: And so that's the one that you get with Dart itself. And then, if we have time, we want to show how to do this in a more robust way. ANDREW: Yeah, so that's a good point. So yeah, this is sort of the quick and easy roll your own conversion logic way of parsing JSON and Dart. Once we get this done-- and we should have-- I think we're about 20 minutes in now, a little bit more-- we should have time to at least go on to talk about built value, which is a package that a guy named David Morgan, I think, is the chief contributor on. It's obviously open source Dart package. You can find it on GitHub and the Package Manager Pub. And that provides a couple of things. It provides immutable objects that use builders, and it provides a very flexible plugin-based serialization and deserialization architecture. But it is much more complicated to talk about than this one, which is pretty, like, I'll just write six lines of code and it's done kind of a deal. While we're stopped, do you want to talk about our hashtag? Now might be a good time because we didn't do that yet. FILIP: Yeah. ANDREW: We have a hashtag. This is our hashtag-- #BoringShow. Since this is a little bit about having ongoing communication with you guys out in the community, we wanted to make sure that you can put comments on these videos. We do look at the comments. You can tweet about us using #BoringShow and things like that. If you have questions about Flutter or things you'd like to us cover, let us know and we can look through these. And like if we see a question, we're like, oh, we know how to answer that and it would be good to talk about on the Boring Show, if we have time, we'll answer it right here. So this is a good way for us to have a little back and forth. FILIP: Yes. ANDREW: So #BoringShow. All right, so first things first. I'm going to use Dart convert to take the string that got the JSON in it and get it into a map. So do I'll final parsed equals, and then I'll do, I think, it's JSON.decode. It'll give it that. FILIP: Ooh. ANDREW: Oh, already messed up. FILIP: No, just that we have a thing here. So either you import the convert with as something-- ANDREW: There we go. That's [INAUDIBLE]. I'm not going to rename this. Hold on. JSON decode. There we go. Oh, I'm still not used to importing things by name like that. FILIP: No, this is great. So there's going to be times when-- this happens to me all the time-- when we have things that-- how do you call it? Naming conflicts. So that's how you do this. In Dart, you just import with a prefix. Cool, so we're done? ANDREW: No. So we parse it into-- this should be a map at this point, I believe. We can look in here. That gives me a dynamic. It will contain my-- or actually this will be a list because I'm deconstructing a list. FILIP: But you need to tell-- oh, yeah. ANDREW: Oh, you don't have your scrolling redone like I do. There we go. And so now that we have a list of ints at this point that we've parsed out, we need to convert it into a typed list of ints, which we can do as-- you OK? FILIP: No, I'm just realizing how many custom things I have on my ID that will now have to fight, and I'm sorry. ANDREW: That's OK. So also I misspoke earlier when I said that we'd be decoding into a map. If the JSON decoder sees a list of items, you will get a list back rather than a map. And so I'm going to get a dynamic here, and in that, which means not considered strongly typed. I'm going to convert that dynamic, which is holding a list of ints into a strongly typed list of ints so that I can use it as one. And that's what I'm doing here. So let's call list of IDs equals-- and I'm going to make a copy. That's how I'm going to do that. So I'm going to do list-- so int. I'm going to say from my parsed dynamic list, like that. And so now I'm taking that list that I got from Dart convert, and I made a copy of it that is strongly typed and that I can treat as a list of integers. And this is a bit verbose, but I'm going to return it. FILIP: [? SharePoint. ?] ANDREW: Just like that. And so let's put a breakpoint in it. Can we put breakpoints in tests? We can do that, right? FILIP: I think so. Yeah. Oh, yeah. Sure. ANDREW: We'll find out. FILIP: Well, yeah. Let's just run it first. I think if you run it, it won't hit the break point. But that's OK. I think you should parse the one. Yes. Cool. So do you still want to [INAUDIBLE]?? ANDREW: Oh, there we go. Debugging. FILIP: Yeah. ANDREW: In theory. Connected. Yay, there we go. So just step through this real quick. I've got my JSON string up here. Let's make this bigger. There we go. And I'll step. So parsed now is a growable list with 347 integers in it. But because this is a dynamic, we can't really treat it as one in a reliable way. So I step over that, and I get parsed and list of IDs. So this is a copy that is strongly typed, and then that gets returned. FILIP: Basically so JSON, untyped, very dynamic-- we want to consume basically a string of things and this kind of dynamic blob into something very much typed. And what you're doing here is you're saying to the compiler and to Dart, I'm sure this is a list of integers. This will fail if there's something in the string. If we put something in the string that's not an integer, this will fail miserably. ANDREW: Let's watch it fail, actually. FILIP: Yeah, we could even have a-- yes. Yes. ANDREW: Let's save that. Let's go in here. Let's debug that again. I think there was a button for that, and I missed it. This should work. FILIP: Yeah, I have never used this. ANDREW: So we'll pass parsed, because this is just parsing to a dynamic. So this will work, right? And then we get a growable lists with a string in it. But then when we try to do this copy, this should just choke and die right here, right? Yes. So now we ended up deeply in the Dart code. That's never good. FILIP: So that's what happens. If you just played it and not debug it and not having break points, it will just throw I think argument error. If you do the debugging thing and you actually have a breakpoint, you will go very deeply into Dart [? core ?] and see all the things that-- yeah, and the type string is not [INAUDIBLE].. ANDREW: There we go. FILIP: So cool. So I mean, we would probably want to test for that as well, but we won't do that now. Let's fix this now, and let's go-- I think, actually, why don't we keep this like just using the primitive parsing of JSON and go to parse article and do this the hard way? ANDREW: So go ahead and keep doing this one with Dart convert, you mean? FILIP: No, no, no. I meant, let's just keep this as it is, because it's three lines of code. It works. Or do you want to-- ANDREW: Well, because we haven't actually showed how to do Dart convert with an object. So we just used it for a list. We haven't done an object yet. So that might be a good thing to show, and then we can comment these out and try something else. FILIP: Yeah. ANDREW: We're also going to have to have a new article.dart. This is going to have some changes in it too if we're going to use [? bill ?] value with it. FILIP: Yes, yes, you're right. ANDREW: So in parsing articles-- so we're going to take the article string. And we'll have to rename this one too. So we have the same name collision there. So now if we go back to our test, we've got this bit here. That's the entire string for the article that we're going to need to parse. So step one is going to be the same. So we're going to do this and say JSON.JSONdecode, and JSON string. And in this case, this actually is going to get me a map. So this will be a map of string to dynamic, because we're parsing a single object. And then I could new up an instance of article and then just start trying to jam individual fields into it, but that's a bad idea. So what I would normally do here is go into article, and I would make a new constructor. FILIP: Can you just use one? ANDREW: I actually am going to use that one in a second. Yeah, so I'm going to make a factory constructor. FILIP: Fancy. ANDREW: [INAUDIBLE] we've been touching on all kinds of things that I almost know how to do. So this will be fun for all of us. So you want to talk about named constructors real quick? FILIP: Yeah, yeah. ANDREW: Because that's fairly Dart-specific. FILIP: So Dart let's you do not just one constructor, but many constuctors, which are named, that are like article dot from JSON, or article.empty. And that's convenient. What Andrew is doing is that, not only is it a named constructor, it's a factory constructor. And factory constructor is basically a function. But it seems from the caller's perspective as a constructor. And that function will just do whatever it needs to do and then will use another constructor in that same class and return a value. So with this, you can do things like singletons. You can do things like caching and a bunch of other cool stuff. So are you showing it in-- ANDREW: I'm looking for it. I know the factory keyword's there. FILIP: Yeah, I'm not sure if-- ANDREW: Let's go back out here. So yeah, what the key word factor gets you is that you can return, and you're not going to make a new instance by default. Up here, if you do article like-- there's a new instance of article that's made for your execution of the constructor and that you use as this. In a factory, you don't have that. It's just a method that can be used as a constructor and can new up an instance itself and return it. Or if you're doing object pooling behind the scenes, it can go get an instance out of a pool and return that. It can even return null, which I'm going to do right now. So I'm going to say, if JSON equals null-- just to show how to do this-- return null, deal with that. That's a thing that you can do. FILIP: By the way, as you see the yellow thing, that's our ID telling us, hey, you have this constructor. It doesn't return-- in some cases, it doesn't return anything. That's bad. ANDREW: That's a problem. So let's make it return something. So let's make a new article, and we'll fill it as we're making it with this information that we're going to pull out of the JSON. So let's say return article. And I'm not going to use the new keyword because this is Dart 2.0. So we don't have to use new anymore if you don't want to. Just like that. And let's put some properties in here. So let's see, text-- I could pull right out of-- this could be null, though. So do we want-- in theory-- I don't think it ever actually is. But do you want to have a backup text? Should we do it like that? FILIP: Yeah, that's always nice to see. Yes, sure. ANDREW: And then we have domain. That's also a text field, right? Let's refer back to the API. FILIP: Oh, domain is something I created just because I was lazy to actually parse anything. So yeah, it doesn't have domain. It has URL. ANDREW: URL, should we go back to URL? FILIP: We should, yes. Absolutely. ANDREW: So we're going to probably break some other stuff then. FILIP: [INAUDIBLE] ANDREW: Oh, you have real function keys. Boop. So the URL will be JSON-- URL. And you want an empty string or leave it as null ? FILIP: Let's just leave things at null. We've shown that you can do, if the null operator, if this is null, then have something else. But we probably, at this point, we'd rather this to break spectacularly then not break and show null somewhere that we wouldn't expect. ANDREW: Yeah, I'm not putting any sophisticated checking in here. I'm just sort of trusting that this data will be roughly what I think it is. Score will be JSON. Score-- actually these I am going to put as zero. And having a null for-- FILIP: Well, I'm not sure if it even shows age, by the way. I think it shows just datestamp. So that's why-- I'm really sorry about this, because this class was basically just me copy-pasting Hackers News and then creating something from that. ANDREW: Well, that's OK. So let's look at our actual data that we're going to [INAUDIBLE] marked up. So there isn't even an age in here. There is a score. FILIP: And a time. ANDREW: What is-- oh, there is no age, OK. FILIP: No, no. That was just me. We want-- ANDREW: I'm back with you now. Nope. Wrong file. There we go. So you want-- let's just take this out for now and put it back in. Or you want to put the time stamp in? Bear in mind, if we put the time stamp in, I have to go figure how to parse dates. FILIP: Let's just use it as an end for now. And that's OK. You know what? Maybe instead of trying to change all this, let's create a new article class with the JSON parsing and just, at some point, we'll just switch it around. And it's probably going to be easier because now like there's going to be a bunch of static errors everywhere. ANDREW: What you want to call it? Because we'll have a name collision if we use that. FILIP: Yeah, what I meant was just like go to [INAUDIBLE] parsing-- ANDREW: Oh, you mean have a new article in here. FILIP: And then we'll figure out-- ANDREW: Now I'm with you. OK, so let's take all this out. The magic of the undo stack. FILIP: Yup, that was probably it. Wasn't it? ANDREW: That would be my first thing that I added. So let's pull that out, go back to JSON parsing, and blort a bunch of this in. FILIP: Right, this is my fake-- ANDREW: I'm going to use the mouse. Yeah, this is. Oh, these are your fake lists. FILIP: Yes. ANDREW: Oh my lord. We're going to edit all this down. So nobody has to watch it. Don't worry. FILIP: I think you're-- Oh, wow, what? ANDREW: So let me parse this-- comment that out. See if I can format this now. FILIP: I think you have [INAUDIBLE] something. ANDREW: We confused the format at this point. FILIP: If you go back up, I think you were just doing this dot something, and that's what threw it away. So this is a text. This is a URL. And if you go-- ANDREW: I'm just going to redo it real quick. FILIP: Yeah, that's probably way faster. ANDREW: I'm waking up your laptop here. FILIP: Yeah. ANDREW: There we go. So now I can go in here and screw around, messing with these. So this will be an ints, which is the time stamp. What do they call it? FILIP: Time. ANDREW: Just time. Time, score, URL. We got comments count. We don't. So that's actually kind of difficult to get, I think. FILIP: Yes. ANDREW: You have to do a little work. So let's pull that out then. And this dot age, just making this dot time. Age equals time. Everything should have a time. FILIP: I used the [INAUDIBLE] age. ANDREW: Oh, thanks. And score subtext URL by time and score. FILIP: Yeah, I think that's good. ANDREW: And magic of format. FILIP: I think maybe you are still importing the article from-- ANDREW: Yes. [INAUDIBLE] FILIP: You can just-- yeah. ANDREW: Cool. So we're back on track. And then I can go back to-- let's see. So do I need something here? What do we got? It doesn't actually return one. Perfect. OK. So now I can get back to-- we're back in parse article now. So I'm going to now take the map that I got from JSON decode. I'm going to stick it in as the parameter to this constructor, and I'll get an article object in return. So I can say return-- actually. Article-- this way I can step through and see it-- equals article from JSON. And I'll just give it the JSON parsed, just like that. And then I can return that article there, and I can put a little breakpoint in here. So we can step through this. But that should be good. [INAUDIBLE] point. Am I missing anything back here in my test? FILIP: Let's try it. Let's see. If it crashes, then we're going to-- ANDREW: Now what was the command you used to actually launch the test a second ago? FILIP: I did this. Oh, it's just the top stories. So I just right-click on the-- ANDREW: Test, the JSON test? FILIP: Yeah. ANDREW: Cool. Can you also click over here? Ah, yeah, you can. So I'm going to hit my break point. So this is decoding the JSON string into that map for me. So I'll step here. And then, if I look at variables now, we've got JSON string. Where is parsed? I should be able to look at you. FILIP: That's weird. ANDREW: Interesting. FILIP: Are we below that line already? ANDREW: Oh, there we go. Parsed. What in the world have I done? All right, stop. Hold on. FILIP: See this is what the Boring Show is about-- looking, puzzling. Can you just run it instead? ANDREW: Yeah, and actually see what happens. What are we missing? FILIP: Why is it so-- ANDREW: Of course it did. FILIP: OK, can we can we try again? Can we debug again? I just step through it. So that was something with the watcher? ANDREW: I think that was just temporary weirdness. Sometimes turning it off and on-- FILIP: Interesting. No, no, no. I want to get to the bottom of this. Can you run it again? ANDREW: All right. So stop, full stop. FILIP: And debug. ANDREW: Debug. And correct me if I'm wrong, we didn't change the code, right? FILIP: We did not, but we added a watcher. So add the thing. ANDREW: Do you want take the watcher off? FILIP: How did you add it? ANDREW: I right-clicked here. I said add to watches. FILIP: Right. You know what probably-- that's interesting, but I bet that-- add a watcher for article now. ANDREW: This one here? FILIP: It will complain. And if you step through it, yeah. What? Is it done? ANDREW: It worked fine. FILIP: So the theory was that, if you add it to watcher, it will try to create a thing-- ANDREW: But by observing it, we're changing the outcome kind of a deal, some Heisenberg stuff? FILIP: Well, it definitely is like that. But anyway, so our tests pass. ANDREW: I think somehow we got the IDE confused about which version of the file it was running. I think we were looking at a more recent copy of the file than the test suite was actually executing. And once we stopped and started again, we were good. FILIP: Well, so I'll get to the bottom of this one way or another. And we'll report back in the next episode. This is interesting. Anyway, so the bottom line is, don't use watcher and just run your tests, please. OK, good. So you implemented this? ANDREW: Yes. That's took what? 20, 15? 20 minutes or something? FILIP: Yeah, with a bunch of things. But what do we want to do now? So this is great. You can do this, and it's pretty simple. But imagine that the structure that you're getting from your API is way larger and it changes a lot. And you know what? So you probably don't want to write these things by hand. We even show this by example when you were trying to take the JSON of age, which doesn't exist. And I saw that, but normally I don't see it. So it's probably better to generate these things. So therefore we have source generation and built value. ANDREW: Do you want to talk about source generation a little bit? Because that sort of leads in. So built value has a library that uses the source generation tech that's available in Dart to generate some code on your behalf in what's called a part file. So you'll have a main file that you use, and that will reference this other file. And you probably have more sophisticated things to talk about. FILIP: Yeah, why source generation and not something else? So I mean, this is my personal opinion. But I like source generation. And source generation means that you're saying, OK, I just write this kind of code, and you'll see in the little bit what it looks like. It's basically like I want a class that kind of wraps around the article, and that class should serialize to JSON and deserialize from JSON. And then the source generation will create that implementation of that class, which will do that work for you. And in your case, you just write that abstract class that is just saying what I want this to do. And then you leave the rest to the source generation. What's cool about that, that you can still look at the generated source. So you can step through it. It's always code that you're going to look at, whereas if there is kind of some kind of magic thing that will somehow [? burst ?] your app-- I remember this from other technologies where things kind of just worked until they didn't. ANDREW: Yeah, and then good luck. FILIP: And yes, exactly. You don't know what's happening. Here it's just like it's still Dart code. It's still the same thing that you would probably write. It's you just don't need to write it. So similar to Dagger in Java, as far as I know, where its dependence injection framework, it's source generated. And so you can, again, look at the code. ANDREW: And one of the things I don't-- did you mention the word reflection at all? FILIP: No. ANDREW: And the reason he didn't mention the word reflection is there is no reflection in Flutter because Flutter compiles to a completely native arm binary. And so I don't think you have the ability to do-- even if they wanted to code in reflection, I don't think that that really works that way. FILIP: Yeah, I don't know that. ANDREW: I know there is no reflection in Flutter. It's disallowed. There is a Dart mirror library, and I believe that's the reason. So there we go. So built value uses source gen. Built value is itself a package. FILIP: So, yeah before you go to built value. So using source gen, you can actually write your own source generation code, which is great. And our friend David Morgan did just that. He created built source generated library for built values. And what is built value? ANDREW: So a built value is a class that is immutable once it's built. You build it using a builder function. So you say, make me a new built value. Here is this method to populate it full of data when you make it. And then once you do that, you can't mess with it. It's immutable. You can use getters to get the data out of it, but you can't change the content. FILIP: It's like data class or like value class, basically. Yes. ANDREW: And a lot of language have immutable map, immutable list, immutable this and that-- this is an immutable value type for Dart. FILIP: Why is it called built value? ANDREW: Because of the builder methods. Yeah. So this is the package on pub. You can see it's already in version 5. This is a pretty popular package that a lot of people use. David has also written a bunch of articles about it, which are really great if you want to check this out. I highly recommend them. I've read through them both, at least a couple of times. So let's go look at his example and get this going. I think we're going to run out of time here in about 10 minutes, but we can probably get it integrated, at least the package itself. FILIP: Yeah. ANDREW: And there's a read me here that I think will give me the imports now. Let's go look at his example. [SOUND] FILIP: That's [INAUDIBLE]. So we did dependency for-- ANDREW: So there's some dependencies here. And we have dependencies for running it and dependencies for the development environment itself for when you're building it because there's actually Dart code that's going to be used to generate the source. So source gen itself is Dart. And so I'm going to basically copy these wholesale out of his example and stick them in our example. FILIP: So maybe a good point for me to talk about-- so dependencies versus dependencies-- sorry, development of dependencies. Oh, you're-- ANDREW: I'm just taking out some of these comments. FILIP: So dependencies are the things that your code uses and the libraries that will maybe use your package will want to use. Development dependencies are the things that you will need for development. So in this case, we need the built value and built for even the Flutter app to work because the built-- our article will implement built value. And we want the built [? runner ?] and built value generator dev dependencies because, A, we want to run the built [? runner, ?] which we will show you in a second. And then we will also need to generate that source code, which is the built value generated. ANDREW: Let's see if I can do a packages get. So hopefully, it'll grab all these. Excellent. So now if I were to go down into my external libraries, which are open here, I should start-- there is built collection. Built collection is the built value for collections like lists. So you can have a built list that is also immutable. Built value is down there. FILIP: So this should be-- ANDREW: Indented like proper? FILIP: Yes. ANDREW: Yes, probably. We'll just check on that. Whoops. There we go. FILIP: I'm sorry. Did I break it? ANDREW: Analyzer. FILIP: By the way, this is a really-- ANDREW: Do we have a bleeding edge of Flutter here? FILIP: I don't think so. ANDREW: Flutter test. FILIP: Flutter test from SDK Flutter. Can we just do-- so can we just remove that line? I don't know why there is-- ANDREW: Well, I mean, we're not going to be able to run tests with this anymore. FILIP: No, no. SDK Flutter. ANDREW: I don't know if it will help. FILIP: I [INAUDIBLE] or something. ANDREW: Let's try it again. FILIP: It depends on Flutter [INAUDIBLE],, which doesn't exist. Interesting. ANDREW: OK, so we have a dependency issue here. How do we solve dependency issues? That's not going to get me where I want to go. So let's look at the pub spec for built value itself. Where are you? You're up here. So that's five days ago. FILIP: Can I look at what [INAUDIBLE] tells us? ANDREW: [INAUDIBLE] generator. Look at the wrong thing. FILIP: What did it actually say? ANDREW: So it's complaining that we have a dependency from the analyzer here. It's 0.32.1, and Flutter test itself-- let's see. I'm guessing that's GitHub. If we go look at the pub spec for this one-- analyzer alpha two, which is pinned. This is not [INAUDIBLE]. So. FILIP: We may need to go back a version maybe of-- I mean, built value. ANDREW: What's the history of this? When did this last change? Is this 25 days ago? FILIP: So quick hack. I have a quick hack. Can we? ANDREW: Yeah, sure. FILIP: Go to pop spec yaml of our class and just do any everywhere-- no, no, in the ones that we want to pull in. So this, this. So any says, hey, I don't really care about the exact-- ANDREW: Just give me something. Is this going to pull in the most recent one and then still have a conflict? FILIP: Yes, yes. So that means that we probably-- ANDREW: 5.5.0, so one version back. FILIP: It might work. You actually write-- like I like to write any, because you shouldn't have that. We already have it in URL launcher. One quick hack is to just say, hey, give me any, and then look at pop spec lock and see what exactly you got and then pin it on that version. So you can see, it actually got me-- built value like 551. That's fine. ANDREW: So we got 551 here and 550. OK, so now go back and pin them? FILIP: Yes. But you can start with the carat. The carat says, anything [INAUDIBLE] still send [? they're ?] compatible to 551 is allowed. So 55x and maybe even 56x and-- ANDREW: These are the kinds of things you know when you've been on a team for more than two months I guess, because I look forward one day to having these Yoda-like capabilities. FILIP: Well, we shouldn't know this. We shouldn't have to know this. But anyway. ANDREW: We should point out, this will get fixed. This is just a dependency compatibility issue. This will get fixed. It probably is fixed in master, actually, in Flutter test. FILIP: Right. So we have this now. We have the packages. Let's go. ANDREW: So now we do the package.get. Now we can go look at how to actually do something. So let's go back to his example, because I like using examples, and look at built value. Example-- so let's take a look at some code that already exists. So we have some values here. So each of these classes in built value, actually write them as an abstract class. And then the part file up here-- this is the part-- you see the G for generated. That's the naming convention. That's going to contain an implementation of this class, if I'm not mistaken, that has a whole bunch of generated stuff in there for making it a built value. So let's go look at that real quick. So it's got the serializers in it. [INAUDIBLE] So there's a very simple value builder, which is going to-- FILIP: Right, so all this code is something that you don't need to write. That will be generated by the source generator. ANDREW: Source gen. FILIP: Source gen. But you can still look at this. ANDREW: So let me see-- I'm going to go back to the docs-- That's not the docs-- real quick. And just want to get another tap open here. So I think he as an example for what your class should look like. Yeah, here's the live. Yeah, I actually have the live template for IntelliJ. So I actually have this on my IntelliJ ID. You can put it in Android Studio as well. Actually, why don't I just do that right now? FILIP: Yeah, let's add it. ANDREW: Android Studio, Preferences, and it's called Live Templates. FILIP: Yup, I have a bunch of live templates. ANDREW: Oh, yeah you do. FILIP: Yup, can you put it to Dart actually, instead of Flutter? ANDREW: Yeah, sure. I can do plus, live template. Built I think is what I've been using. FILIP: Nice. ANDREW: And then we'll define a context. This would be in Dart. Does it need to be top level I think? FILIP: I think so. ANDREW: And [INAUDIBLE]. Now we'll go back into our JSON parsing. And let's just comment this out for now. You go away for now. Get rid of that for now as well. And you can return null just to get rid of that error message. I'm getting rid of all this [INAUDIBLE] so I can replace it. There we go. FILIP: Fun fact, just so that you know. You can just do this. And it will-- ANDREW: Oh, it will help it cast it for me, won't it? FILIP: Yes, it will cast it for you because it knows that you're meaning [INAUDIBLE].. ANDREW: There we go. OK, so let me go back up here, and I'll use my built. There we go. FILIP: Oh, love it. ANDREW: So I'll call this article still. And so that is giving me the basics. Now I'll import package built value. And then I need to do my part file. So this is something you would put in. So JSON parse. We should really move this into its own file in a second. FILIP: Yeah, that's fine. ANDREW: Just like that. FILIP: I think you-- ANDREW: Oh, and the G. Thanks. There we go. FILIP: So which doesn't exist yet. Also I think the life template failed here. You want underscore, dollar sign, and then article, yes. And maybe not this one. ANDREW: This guy. There we go. Oh, we keep that one [INAUDIBLE]?? FILIP: I think it's the first one is-- I don't know. It'll complain anyway. So we'll find out. ANDREW: OK, now we need to run source gen. So built value also gives you a little tip for this if I go back to it. There we go. So if you're using Flutter-- and here's the exact command, which I can just run from the root directory of this project. Can I kill this? FILIP: Absolutely, yes. ANDREW: And this is our app, right? So let's see what happens if I do that. I think it will give you an error about Dart UI that it gives you every time. There we go. FILIP: Severe. Error in built value generator. So we did not do it, which we know. So that's missing. So the cool thing about built value is it will actually tell you, hey, you are missing this. Please add it like that. And it actually will also-- ANDREW: There it is. You're right. We need that dollar sign in there. That's what it's complaining about. FILIP: Is it that though? I would be surprised. But let's see. ANDREW: Oh, ye of little faith. So I think we'll still get the Dart UI error. Yeah, one of the nice things about built value being on version 5 point whatever it is that they've really worked out a lot of the common errors and produced nice error messages. That's something I like about working on Flutter in general, like [INAUDIBLE] team. You screw up something. In your code, you get this paragraph-long error message saying, here's what's wrong. You probably did one of these three things. Here's an article. FILIP: So here's what I like about this. So it worked. It knows that it's always the same thing. So it actually created a hashcode that is an actual constant, which I just laugh. And it has all these things that we don't use yet because we don't have anything on the article class yet. So how about we go back to the command line and do built runner watch instead of built? So you can see the iterative process. So you can just go back to the command line. Do watch instead of build. So build run a watch. ANDREW: So it's going to watch the file system now. FILIP: It will watch the file system. It will still take its time to start. But from now on, changes get there very fast. ANDREW: So now let's go back and add-- so, question, how do we add the fields on here, right? Because they're going to be getters. They have to be getters because this is now an immutable class. So we can add-- What is it? Is it [INAUDIBLE] get ID like that? Or is it a final? Let's go [INAUDIBLE]. FILIP: Yeah, [INAUDIBLE]. ANDREW: Oh, that's right. FILIP: Yes, yes. So you're saying, on the main class, you're just saying, I want to be able to access ID of something like that. And so, did it update already? ANDREW: Oh, let's find out. It did. FILIP: OK, so the source generator for built value knows that, OK, it wants an ID class. And also if you go to article builder, that's the class that you will use for changing things, which you will probably not actually be using. But if you scroll down to article builder, the article builder has a getter and setter for ID. So if you provide the article class itself, that's immutable, You can't change it. But the article has something called, I think, tool builder, which will give you the builder class. And then you can change anything about that. So that's really nice because, if you want to provide just immutable class, you will just provide article. And in our case, we will just do that. But sometimes you want to modify the class and provide another immutable article that is different but-- ANDREW: Based on the one that you came with, right? So we are now at like an hour and 10 minutes. FILIP: Oh, wow. ANDREW: Do you want to call it now and we can finish this up in the next one? FILIP: Sure. ANDREW: Because I'm worried that this will take 10 minutes instead of two to just get the serialization stuff working. FILIP: Yes, yes. So we've covered a bunch of things. ANDREW: Yeah, we should recap. Recap. They're waiting to listen to you. FILIP: All right, so recap-- we started with the bug. We fixed the bug. We looked at widget test. We showed you how to run your widget test in an emulator so that you can see that it's working. ANDREW: Yeah, it was really cool. FILIP: Did we just-- oh, we wrote some failing unit tests for the JSON parsing. Then we went straight to implementing get into the manual.convert way, which worked. Then we found a bug, a weird IDE thing. And then we started playing around with built value, and we showed you the starts of having a built value thing, which is immutable and stuff like this. And that's where we-- ANDREW: Yeah, we were talking about getting communication going back [INAUDIBLE]. This would be a good thing. If there's any part of what we just showed that you feel we skipped over or went too fast on and would like more information, we can start with that next time and go into a little more detail. So comment on that, that would be great. If you'd like some more detail about anything that you just saw us sort of attempt to cram into about 65 minutes. FILIP: Yeah, yeah. And also if a lot of people feel like this is just going too slow and we should skip over things or maybe just, for the next time, we already have this built and we just show you things, that's also fine because it takes time to do this. ANDREW: You want to the hashtag again? Don't forget. If you have questions about Flutter in general or the show or whatever, #BoringShow, and we'll look for them. FILIP: Or just comment under the YouTube video. That's also fine. ANDREW: Or comment on the video. We responded to a bunch of them, as hopefully y'all saw. OK, anything else? We good? FILIP: Yeah. ANDREW: All right. FILIP: Thank you. ANDREW: See you next time, y'all.
Info
Channel: Google for Developers
Views: 90,881
Rating: undefined out of 5
Keywords: the boring flutter development show, debugging flutter, widget testing, flutter widget testing, built_value, dart built_value, built_value.dart, Flutter, Introducing Flutter, Flutter IO, Flutterio, UI Framework, mobile app SDK, cross platform apps, cross platform, multi platform apps, build native apps, native apps IOS, native apps Android, widgets, flutter widgets, mobile developer, mobile app developer, native app developer, expressive UI, open source, firebase, GDS: Yes;
Id: TiCA0CEePyE
Channel Id: undefined
Length: 73min 17sec (4397 seconds)
Published: Thu Jun 21 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.