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.