CRAIG LABENZ: Hello, everybody. Welcome back to another
episode of Observable Flutter. Let me see how I can
remove the overlay here. I don't know. Oh, I'm live. It's already been removed. How great. Today, we're going
to pick up where we left off from last week-- and I'm hearing myself
and need to mute myself. There we go-- where we
last saw our heroes. We worked through
some code sharing, and had a shared
user model that was available to both our front end
Flutter code and our back end server-side dark code. And we also established an
extremely primitive connection to a Postgres database and
just asked it what time it was. And then finally, using Dart
Frog, we displayed that time. We saw that running in a window
that I'm going to bring back over here and make much larger. So this said-- oh, I'm on
the wrong thing entirely, aren't I. Here we go. I'm back. It said, welcome to Dart
Frog, blah, blah, blah. This is the current time. So that's where we're going
to pick up from today. And we're going to dive deeper
into talking to Postgres from the server to get ourselves
set up to complete whatever app it is that we're even building. We haven't decided yet. Maybe we should take
suggestions from the chat. So that is today's agenda. But before we dive into that,
just a quick reminder, folks, this is Flutter. We are, I think, the
friendliest community in tech. And let's keep it that way. So we're operating
under the, obviously, the YouTube terms
of service, but also the Flutter code of conduct,
which is very important to me. So we're going to be
debating different things on this show, different
packages, different options, different technologies,
and we always want to stay respectful of
any package or technology that we're not
advocating for still. A lot of the authors of those
things might be in the chat. So let's just remember, always
stay respectful of things. I think we're ready. So I'm going to have a
quick tour of the code that we wrote last week,
for anyone who either has forgotten, which kind of
includes myself, or hasn't been able to watch
last week's stream. So to begin, and actually I'll
open up the side browser here, in one folder, we have
the client subdirectory and a server subdirectory. And this client
subdirectory is all Flutter. And the server subdirectory
is all Dart Frog. And that client directory,
in its pubspec.yaml-- actually, I'm going to start
with this part of the story, from the server's perspective. The server has nested in
it a packages directory. And the packages directory, this
was created by good old dark create. And it's a simple package. And it has in its lib
directory some shared code. Actually, the whole
package is called shared. And we've got a user model here. And this is kind of the crux
of what we're worrying about. I want to have some shared
front and back end logic. So that lives in the
servers directories package shared subfolder. And it is available to be
installed in our top level pubspec.yaml. So if we look at the
pubspec.yaml that's directly in server, we'll see that we're
including packages shared. So this just brings
in the user model. That's all it does. Then, the same trick can
be done in the client. And I actually think that I
may have moved this around since I looked at the client. But if we go to
pubspec.yaml-- oh, no, I did edit it late in
the episode last week. So this pulls in
that same user file and we're able to seamlessly
serialize and deserialize from the server and
know that we're not going to have any issues. We've got some starred
questions already. Before we get any
further, let's go. Someone says, I think MongoDB
is better than PostgreSQL. Well MongoDB is very
different than PostgreSQL. It's a document store. And so it has all of the
document storage strengths and none of the relational
database strengths. So those are two-- they're just pretty
different databases. If you like document
databases, anyone who does, and there are many
such developers, will definitely prefer MongoDB. MongoDB also does have
a lot of really great-- oh, I accidentally
clicked on one-- I'm not sure. You always have questions
in your application. MongoDB has its own use cases. But it's hard to beat-- oh, this
is a response to the last one. The query system SQL
provides out of the box. Well, I agree with that. Yeah, MongoDB is excellent. There's no-- it also has a
great offering on Google Cloud. And it has a really
nice two-way sync, a product called Realm, which
has existed for a long time, and at some point joined
forces with Mongo. Or I'm not really sure what
the back story is there. But MongoDB has a really
nice client for that as well. So more comments on
SQL versus NoSQL. They're both great. After a huge, amazing success
of the Flutter Dart framework, could there be any possibility
about a new server side Dart framework? Well, the community is
making some of these. I don't think Google is going
to make this thing that you're talking about. I think if I had a crystal
ball and it could tell us in 10 years from now, has
Google worked on a server side Dart framework, I would
bet $1,000 that the crystal ball would say, no. But they're coming
from the community, and we're playing with
Dart Frog right now. But in my opinion, honestly,
the biggest task really is how you talk to
whatever database you're using, whether it's
Postgres or Mongo or something else, which is why we're going
to play with an ORM today. What kind of version of
the boring show is this, Verde asks. I don't know-- astute. I have no incense again, but
I do have two cups of coffee, so I don't have to get
up for my second one. So I burned some coffee
beans, and my mouth. Goodness. I think we have somewhat
covered what we did last week. Oh, no, there's still one
thing I want to get back to. We have this-- nope,
that's not the right file. Where did this come from? The middleware situation-- I'm going to make
some more space here. To talk to Postgres, we need to
have this PostgreSQL connection class. Although, as soon as
we get into stormberry this may even just go away. We'll see what we do with that. But we have this raw
PostgreSQL connection, and we did not at all last
week, figure out an elegant way to make this available
to our class. Actually, this actually
does look fine. Why am I-- why do
I keep thinking that this isn't going to work. Let's refresh the page. Oh, no, it doesn't work. I knew last week
why it didn't work. And it still doesn't work. I attempted to reopen--
where do we actually reopen? Oh, in the route, I think. Yes. Here we go. We open the route. We open the connection
in the route. And this is just
absolute madness. That's what I couldn't remember. So this is the first
thing that we're going to think about today,
is how to have a sustainable connection to Postgres. But we're actually going
to approach that problem from the perspective of
this stormberry library. So let's say pub stormberry. And let's take a
little peek here. Stormberry is a strongly typed
ORM code-generation package. Oh, ORM-like-- interesting. I don't know if there's a
firm definition of ORM such that a package
like this should be hesitant to don the full
mantle of a complete ORM co-generation package,
instead of just being an ORM-like
co-generation package. Anyway, that provides easy
bindings between your dart classes and Postgres database. It supports all
kinds of relations. I love that. So I think I added
this last week. But I don't know if I
did anything with it. Let's remind ourselves. Our server,
pubspec.yaml and, no. I thought I did, but I didn't. I really thought I did. Shared pubspec.yaml? This would be a silly
place to add it. No, it's not there either. Because there was
the whole thing, if I added the wrong package instead. Did I never come back
and add the right one? That would be quite funny. So we're in the
server directory here, and I'm going to run the
command of adding stormberry. And then we're going to add-- we'll build runner and we
probably already have that. Yep, we do. And it's a dev
dependency as well. So I think we are
ready to get started. In your code, specify
an abstract class. And they, even, are talking
about a user as well. And it should look like this. Oh, one of the questions
that we had last week was whether or not the ID
should be nullable or not. And I was pointing out in our
code right now, it is nullable. Theirs, it is not. And I said, well the database
is going to give us users. So it will generate the ID. That's, whether you
use a sequence or just an auto increment, the database
will give you that primary key. And lo and behold, they've got
a different way to handle this. So we'll learn how
that goes together. But for now, I am just
going to copy this code. And we'll also
have to think here. Remember, the whole point
of this is code sharing. But this is a database class. So we're obviously not going to
use this class in our Flutter front end. And for anyone who's
watched any of the videos on clean architecture,
I love those videos from ResoCoder, where he talks
about having your to domain and you're from domain
and you're from data and to data, however
they're all called. I think we're going to
need some stuff like that. So put on your ResoCoder hats. Anyway, I've got that
code in my clipboard. And if I-- let's see,
where should we put this. I'll open the
sidebar again, which I'm going to try to
keep closed today. It wastes a bunch of space. But in the server-- I guess, we'll just
make a new folder here. I don't even know
what's going on. I'm going to get out of here. That was a fail. So in the server, let's
make a new directory here. We have to go into the Lib. No, there is no lib,
because this is-- well this is a
question on Dart Frog. Where do I just
put a random code. What are my options here. All I have is routes, and
then this packages thing that I made. I think the idea is that I
put this in a subpackage. Let's make a
database subpackage. I think that is a
good idea, actually. So in packages, let's
run Dart create again, which I'm going to hit
by typing control R, and then typing Dart create. And editing this, and
I'll call it, just DB. So this was how we made the
shared directory last time. And now that will be
where stormberry goes. This will be nicer. So let's go back to our
server pubspec.yaml file, and get rid of
stormberry, and save. And then we'll open
our DB pubspec.yaml file, which is this one,
and we'll add it back in. And now we will also need
to bring in build runner, because that was a step that I
was able to skip a second ago, by way of it already existing. Felix, in the chat, points
out a lib or subpackage, which is how we're going. Felix, I just, I asked
myself what would Felix do. And I remembered in all of
your Dart Frog tutorials, this is what you do. So great. Welcome, Felix. Great to see you. So we've got stormberry. We've got build runner
in this subpackage. And now I think we need
to run some pub gets. So let's CD into DB, which, of
course, stands for database, and run Dart pub again. And now, we can return to
our server pubspec.yaml and we'll add DB, like this. And I guess we'll go full
alphabetical sorting. I wonder if we should have all
the shared path ones together. Don't know. For now, we'll just do
more alphabetical sorting. So we are now in
the DB subdirectory package, whatever it's called. And let's open up DB main.dart. No. What's going on? Lib, source, db.dart. What does db.dart have? This just has the export. Yeah. So CD source. Oh, it's probably
called db.dart. db base. Right. Right. Right. So let's rename
this to DB models. We call the other
one models, as well. So we're already kind of
colliding with our own naming convention here. But let's rename db
base.dart to DB models. This is imperfect. Db models. And now, we don't
need any of this code. And my clipboard
has been overridden. Does anyone else sometimes--
you save something important in a clipboard. And you're like,
this is critical. If I don't paste this,
I lose so much work. And then instead of opening
text edit or something and pasting it
somewhere safe, you just keep it in your clipboard. And you keep doing
the work until you're ready to paste it, just
like praying that you don't forget and
copy something else and then lose all your work. Oh, no? Just me? OK. Well, sorry, guys. I do that. Let's import package
stormberry.dart. So returning to
the instructions, it now says, you're
going to need this build.yaml shenanigans in
your root directory of whatever package you're in. So for us, of course, that's db. So I'm going to return to the
root of our database directory and run touch build.yaml. And then we'll open build.yaml
in the database directory and paste it in. Now lib models is not
where our stuff is. We are in lib source db models. Lib source db models. Modify this to match
your library file. We did that. Nailed it. Let me pause and look
at some questions. Why Dart Frog rather
than server pod? Have to start somewhere. Honestly, not any more
sophisticated than that. I am pretty sure that
server pod is quite good. But I have just
used Dart Frog a bit before, so I thought I
could move slightly more quickly with Dart Frog. And I like Dart Frog. I'm not going to lie about that. What is the topic? Some shared logic between
the front end and back end, and we're also going to be
talking to Postgres as well. So what we're imagining
here, is folks here, we all know, that we
like to build our client UIs with Flutter. But that doesn't
necessarily inform what we do on the server. There's a ton of great options. And so we are, if we really
zoom out on what the topic is, we are exploring end
to end fullstack Dart. How can we put the middleware
on routes like get or post? Oh. Whoops. We double clicked. How can we put the middleware
on routes like get or post? Well, the middleware,
I know, can-- it lives in every
specific directory. So if we look back at our-- and Felix is in the
chat, so while I guess, he might just be
typing the answer here. But if we look at
the routes directory, and this is all
server pond stuff now. If I wanted to have a URL
like /users/ the user ID, I would make a new folder
here, called users, and then another file
in there, called, depending on my URL scheme,
it might be actually kind of a wonky file name. It might be kind of the
matcher as the file name. But let's pretend it
was called userID.dart. And then I wanted middleware
only on that route. So I could put another
underscore middleware file in there. Now that doesn't quite
answer your question, but it sets us up to look at
this handler that comes in. And I bet this has a
pointer to the request. And I think it might
be as simple as we just check the method. So let's look at
the handler here. Oh, this is the context. This is, I think,
what we actually are going to request context here. So I think we're going
to analyze that, and say, imperatively, not very
declaratively, are you get, are you post. If so, apply the
appropriate middleware. Good question. Oh, hi. Maintainer of stormberry here. This is so exciting. I hope everything
works smoothly. But if not, you're here to help. Killian, I'm so excited
that you're here. Yeah, I hope everything
goes smoothly, too. This is-- I'm truly flying
by the seat of my pants here. But so far, the
documentation is strong. So nice. Build.yaml-- we're good there. Closing some files. Index.dart-- this is all so old. So we're focusing on
database models on Dart. And I suspect we
are ready to build. And we are. So I'll return here. We are in the
database directory, which is where we need to
run this build command. And I'll just add delete
conflicting outputs, because folks suggest
that you do that. I'm not going to lie, I've not
studied the full difference. Alexander says, oh,
and why no conduit? I think they're following
up with a previous question. I'm guessing conduit is
more backend Dart tech. Again, just, got
to start somewhere. But conduit, server
pod, any others you can get your hands on. If they feel strong, then
they are probably strong. Dexter says, Windows has a
clipboard history feature. Get it. Catch up, Mac. Come on. Come on, Tim. Have you heard about Alfred? It's a Dart backend framework. I heard about it right now. Thank you, Nathan. But I have not used it. I love that there's all
these options out there. So we have generated
our code here. And remember, last
week, I said that I had configured VS Code
to hide the build files. Well, that's actually
kind of biting me in the behind a
little bit right now. So let's print out what
we have in lib source. And we see that it added
this schema.g.dart file. So I'll open that
db models.schema. Oh right, I am not
in that directory. So I have to type all of these-- models.schema. Let's see what it made. So we have this extension
on a database class. And database comes
from stormberry core. And then we have a
new model registry, which starts with a map. So obviously, this is
going to be something that's mutable, which is fine. We're going to register
the different models that are there. So now we have our user
[INAUDIBLE] man, that tooltip, so aggressive. We have a user repository--
implements all these different things-- insert, update, delete. So we see the different CRUD
operations that we can do here. Love that. A couple-- oh, a
query for a single. Query for multiple. And this is very interesting. We've got this nice helper,
this query params class. I was so excited. I like this. And here's some
internal stuff, which we know we don't
have to worry about, because it has an underscore. But just some more glue code,
I guess, connecting things. So user queryable. See, that does not have
an underscore, so we-- I don't know if we're going to
get our hands on that or not. And it generated. Look at this. We have our full query
here ready to go, generated off the
fields in our database. Goodness, I just love it. An update method, a
delete method, and then we get down to-- oh, I love this, too-- a specific class
that encapsulates the idea of the request. I love doing this. I have-- when I-- like networking code
on the front end, I'll have a single class that
encapsulates any parameters that I need for every
single network request that I make to the server. So it's like, maybe you can
optionally filter on something. I'll just have a
class that knows how to accept that
optional parameter and pass it to
the network layer. And it seems like we're
going to basically be kind of getting that same
very nice usability here. So this-- oh user queryable,
we saw this a minute ago. So it extends this other thing. And it's got two
types-- user and string. So this is just a lot of
database glue code here. This really tells it-- I bet these things are
going to fill in the query that we just saw. Update-- does this
take a queryable-- Oh, the update request, I
think that had a queryable, but I'm not sure. Where was the insert? Yeah, so this takes the database
and user insert request-- well, I'm not 100% sure,
but this looks really good. I love it. We have one question right now. Can we implement
microservices using Dart? I mean, if we build
backend server using Dart, is it possible that we do use
microservice architecture? Yeah, of course. Just have smaller
Dart Frog or conduit or server pod applications
that are really targeted and deploy them to different
Cloud Run services. Obviously, you can go
the Kubernetes route, and it's really
sophisticated at coordinating different microservices. But I am allergic to yaml, so
I'm not good at Kubernetes. And Felix agrees. He says, good point, definitely. Oh, point is actually
part of proto coders name. The protocoders
point was the name. Wur AA says, there's
possible SQL injection with these queries. We should replace every variable
with dollar placeholders and use the args field
in the query method. That is quite a good point. Let's remind
ourselves-- oh, yeah. Look at this. So this database query
method, let's look at this. Because the raw--
who pointed that out? Excellent observation. And I know we have the-- yeah, there it is-- Wur AA-- keen eye. So SQL injection is
a nasty little trick where the user types a
username like closing quote, semicolon, drop all tables. There's a great XKCD little
Bobby Tables about this. And if they kind
of land just right in the way you compile your SQL
at the end, it can terminate. You just-- you're trying to
build SQL with code like this, and a name drops in, and it's
actually not a good faith name. It's something very clever. And it ends up
terminating your query and starting another query, like
delete all tables, or whatever, drop table users, and
that's called SQL injection. And the way around that is
you use the database bindings. They basically, now, just say,
give me your query in one place and all your variables
in another place, and I will ensure that they
are not ever SQL injected. So let's look at the method
that we're using here. This query method
comes from stormberry. And this PostgreSQL
result class comes from the raw
PostgreSQL bindings. So we know that
we're using that. And then we have this
transaction context query. Oh, we have
substitution values here that are coming in with values. But I don't think we were
passing this second parameter back in our query. No, we aren't using that method. We are combining the
query ourselves here. So I know we have the creator
of stormberry in the chat, and I would take this as a big
action item that all of these values here that we're kind
of flattening ourselves, unless you are just sure that
this encode method is going to guard against SQL injection. But even still, I wouldn't
take that work on myself, if I was you. I think those values
should be passed in to that second parameter,
the optional values list here. That is an excellent point. Another viewer that ate
their Wheaties this morning. But for now, I'm
going to pretend that we don't know that,
and that we are just loving everything
that we see, and we're continuing with stormberry,
because that problem is fixable. We have our user here. Let's return to
the documentation and see how we can continue. Set up. So we did that. And this will
generate this table, which looks like a nice match
to the class that we made. You can also make any
field auto increment. Well, I'm not going
to worry about that. Relations-- OK,
this is a huge part of why anyone would use
relational databases versus document databases
is, of course relations, and the join queries
that you can write. So this is just a wonderful
syntax for a join. It's just so simple. I'm very excited about that. Views-- I love a good view. That's, basically, like
you can just save a query and pretend that it's a table. And you can do clever
things with views. You can chain views. You can have views
that depend on views. They can also become
a caching layer. You can have the database
live update them. So you don't have to
run the query every time you access the view. So they're just
kind of meta tables that can be a little ephemeral. They can do clever things. And a nice syntax here to
add different kinds of views. So this would be the
full user, I guess. And it includes the post. That's what we're saying here. So we add in the
posts, and we can see that that's a
relationship as info. I'm not sure what
as info means, but I think we'll find out,
probably shortly. And then there's another view,
which is like a thinner view. So this doesn't
have their address, and it doesn't have their
posts, and maybe this one is suitable for
other users to see. And this one's called reduced. I don't know-- I'm curious-- so we have
the as parameter here, and we don't have it
on this other one. So, I think, we'll probably
see this shortly here. Oh, info-- no, not sure yet. Anyway, then there's
some more views here. This has info. I wonder if these are
supposed to match. I'm not sure. The above model would
give the following things. Let's kind of work out what's
going on with info here. I'm going to use the
old Control F to hide all appearances of info. Info post view got connected
to the complete user view. So we saw that here. So our complete user view,
we're basically adding in posts. So this is, basically-- this is
just going to bake in the join, essentially, saying give me all
these users and to their posts, which, of course, is
actually going to yield some interesting-- you're caring about the
posts primarily, here, because you're going to get
all that user data repeated. I'm going to come back
to this, because I'm not worrying about
relationships yet or views. But this seems
pretty interesting, and I think it will
just take some time to really look at
and fully understand exactly what's going on here. And knowing for
sure whether or not this name is required
to match this name, or if they're actually
just incidentally the same. Maybe another action item on
the documentation for this. Serialization--
when using views, you may need to use
serialization capabilities to send them through the API. While stormberry does not
handle serialization by itself-- of course not. That would be out of scope-- it enables you to use your
favorite serialization package through custom annotations. I love that, because
we were already talking about using that freeze
class and sharing that code. So I would love to be able
to use that freeze class when I deserialize things
from the database. Now this was just on view,
so hopefully, there's a similar trick for non views. Indices-- well the primary
index is already in-- or the primary key
is already in index. But this is where we
would add other indices. Great. Type converters--
yep, this makes sense. This is a very familiar
thing for anyone who's used freezed
or JSON serializable. Usage-- this is what
I was looking for. So this looks-- this code
looks pretty similar to what we had in the last round. So I'm going to copy
this real quick. And I'll return to index.dart. And this was where-- no, it was in the
middleware file. In this middleware file, I
created a connection object. And this was the raw
PostgreSQL library. So we're going to
comment this out. and add in its
place, the new code. This means stormberry is
going to need to exist in the top class as well. I'm going to abstract this
away better in the future. I don't want stormberry to
be a thing that my top level library knows about. But for now, I'm going to add
it so we can just get to a point where we see stormberry replace
the raw PostgreSQL bindings. So I'll hop over to
a server, pubspec. I'm searching in
not the right thing. Server pubspec and
add stormberry. So we'll add a
library stormberry. While it does this, I'll take
a look at some questions. Phaylali-- I hope
I said that right-- asks, is Firestore for desktop
possible in the near future? Let me click that. I gotta do the old
double click again. Is Firestore for desktop
possible in the near future? Well, Firestore for
desktop exists on Mac OS and not Windows, as you know. And there are folks,
including myself, and Eric Windmill, you may know
also on the Flutter dev rel team, Kevin Moore, who are
pulling all the strings that we can to make this happen. And that's all I
can say right now. We're trying our best. What is the Flutter
wrapper backend-- what is this, a Flutter app
or a backend app using Dart, Lufti asks. Lutfi-- sorry. This is both. We're currently
looking at the backend. But we want to share
code with the frontend. So we're going to, I think,
once this is all set up, have really a truly
special setup that very clearly competes
with other backends, especially with
stormberry, the ORM. OK, nice. Killian has said,
totally valid point. It's planned for
the 1.0 release. I presume that goes
back to the SQL injection and the
substitution variables part. Killian is the
creator of stormberry, and is kind enough to
be joining us today. And Wur AA says, I prefer using
clear Postgres, rather than stormberry, but stormberry
is a great solution for parsers or any software
that loads data in the database. Yeah. So I presume when you
say clear Postgres, you basically mean just kind
of writing raw SQL yourself. And I certainly-- I think, it's very
helpful for anyone to know how to write those
kind of raw queries themselves, for sure. Let's continue here. So in the middleware file,
we, of course, need to-- well, the magic IP
address is often swappable with local
hosts, but sometimes not. Sometimes, it isn't. So we went with change me. I know, I remember like MySQL
had annoying things about that where you could only use
local hosts, not 127. I don't know. I don't remember. We can add stormberry now,
because I added the line-- I added that import. So let's go to stormberry. And there we are. I think I will need
to save this file, and then, oh, it's running
[INAUDIBLE] on its own. Great. We were getting the
depend on actual things. Some stuff that we
can close right now. So we are now making a database
object in our middleware. And we're going to
inject that database. And this one is called db now. It's not the connection. So we're no longer making
available to our URLs the full-- just the raw Postgres bindings. We're, instead, exposing
this heavily wrapped-- this strong wrapper
from stormberry. And if I return to
index, I can no longer read from the context the
PostgreSQL connection. I can, instead, only
read the database. So I'll say final dB equals
context.read database. And this will, of course,
need to be imported. stormberry-- there we go. And we don't have a
connection anymore. So there's a lot that we
can't do from the old code. We also aren't going to
just bother running, like, select now through it. So I'm going to return
to the documentation and see how we can actually
instantiate this table. We'll put a record in it. And then we'll read
it, and show it. So all the parameters
are optional. Well, we're going
to provide them. Repositories-- a
repository exists for each model on which
you can do all the things. Great. You can also get a
model's repository through its property accessor
on the database instance. Oh, OK. For the above example, two
views, complete and reduced, we'd have all these methods. Query complete views-- so
that returns multiple things. Query reduced,
plural again, insert, and those are into
the views, as well. No. So we don't get
CRUD into the views. That doesn't make sense. You still only write
to actual tables. User for reading. And we saw that each method
had a single multi variant. I like that. User insert and user update
are special generated classes that enable type-safe inserts. Yes, we were talking
about this earlier. So wonderful. So nice. With this, stormberry also
supports partial updates of models by update one. And then we pass something. So this ID would match a record. And then, we would
basically only change the name Tom on the
record with ABC as the ID. Queries-- you can specify custom
queries, actions, blah, blah, blah, and database migration. This is what we've
needed this whole time. So let's do that. Let's run this Command
and see what we get. I'm pretty sure that I still
have my database running. Let's connect to it. And I launched this
via Docker Compose. Yeah, this is where I
ran select now last week. So we have the database running. And let's now run the migrate
command and see what it does. So we don't need
to type up there. That's right. Select a database to update. Oh, interesting. I'd like to be able to
configure this so that this can be fully automated. But for now, I'll say Postgres,
because that is the default database we were using. Oh, no. [LAUGHS] We've had a-- the feature
already completed. Where is that happening? Where's this? We're in some raw stream stuff. Async. I'm not seeing any stormberry
code in this stack trace. Oh, Postgres-- upgrade socket
to SSL, is where this happened. I think the fact that this
is a local host database and is available over
not SSL is potentially causing some issues. Killian, if you are
still here, does this sound reasonable to you? I don't know if
you're still here. I saw someone say,
Killian, have a good day. [INAUDIBLE] Someone says, here-- Vijay asks, can I use this-- can I use for backend
API with Axios? I don't know. I'm not sure what Axios is. It's a news company, as well. Let's look up what Axios is. Axios-- let's see
if it's a database. How do I make an Axios
post to a database? Oh, Axios might be
a networking thing. What is Axios? NPM-- here we go. Promise based HTTP client
for the browser with Node.js. So that question was, can
I use for the backend API-- oh, can you use Axios as the
backend communication thing? And to that, now that I know
what all the things are, I can definitively
tell you, I'm not sure. Good question. I'll have to play
with it and find out. TheFable asks is the
db object secured since it has the password? And should that file be
included in gitignore? Good question. This is some very proof of
concept stapling together of the code here. I would pull this out of secrets
from Google Cloud, for sure. Having the password, along with
it even still being change me. Having the password sitting
right here in source code is, of course, totally terrible. So I would pull this
code out of SQL, or out of a Secrets Manager
from Google Cloud. And Stefan asks, are we
able to compete with .NET 6, 7 and/or node? Well, I mean, those have-- node has like 12 years
of a head start on us. And .NET has, I don't even know,
30 years of head start on us. So I think Dart on
the back end is always going to be pretty niche
for Flutter developers. It would be a surprise to me if
we really ever got to a point where non-Flutter developers
even remotely considered Dart on the backend. So, I think, we're only
competing with those things in a context of
our own projects, the backends that
complete our Flutter apps. And we're going to find out
together, here, whether or not it's a good enough experience. I think it is likely going
to be, but we'll see. So we have this error. And it's coming from the attempt
to upgrade the socket to SSL. And, I presume, that
this means stormberry thinks it's talking to
a production database. But it is, in fact, talking
to a local host database. So the first thing I want to
do is go to stormberry and-- oh, db socket falls. Db ssl default, true. Where did that go? In order to continue to provide
the following environment variables, is what it says. I'd like to do this without
worrying about an environment variable. Let's see if there-- there we go. Use SSL. So the environment
variable just serves as the default value for that. So I will say,
didn't I copy this? Use SSL as false. And now let's try again. So this, we ran the
migrate command. Engine Denise asks, can
I watch this video later? You're too tired to
follow along now. Yes, you can. It's going to be on
YouTube after this. By the way, hello,
everyone from the future. So we are still updating
the Postgres database. And we've had another error. Upgrade socket to SSL did
not get any better for us. So was there a socket command? Because there was a-- oh, is Unix socket. I don't know if we're connecting
to this local database via Unix socket or not. But I don't know. Let's see here. One day, I'm going to get rid
of the pub in that command. Postgres-- there's
still an error. Phooey. Upgrade socket to SSL. I wish Killian was still here
so I could ask him about this. Let's now go to the GitHub
repo and see if there are any issues about it. GitHub stormberry issues. Let's see if there are any
closed issues about it that might describe a workaround. Large connection parameters on,
problem encoding-- no one else is reporting a problem
with a migration, and that it is a surprise to me. Improve migration
tool-- oh, here we go. This is promising. Except database arguments. Currently all
database parameters must be provided through n vars. Oh. Oh, of course. The code that I write here is
not being used in the command. Yes. And Killian was
just typing that. Killian says, unfortunately
the migration cli does not look at your
in-code db variables, since it's an external tool. Of course. Yes. Yes. Yes. Yes. Duh. Well this changes things. By the way, Killian,
another action item. The documentation
here where you talk about running this command-- oh, I should make this a
little bigger, shouldn't I-- where we run this migration-- and you say it right down
here, provide these things. But even, potentially,
below all of these db-- or all these
environment variables, you could have
another one that shows the syntax of providing those. That might be helpful. And it would help goofs like me
who can't figure anything out. So we've got our
same command here. So I'm going to
copy this back in. I didn't have it
in the clipboard. Copy. Goodbye. We'll go to the front. Let's get rid of pub again. And then we'll say
db_SSL equals false. And this is one way we can
provide these parameters. And then the db socket, we
know that was false as well. So db socket equals false. And then our db-- was
it just db_password? It is, db_password. For us, it is change me. And all of the other
default values should work. So let's run this. It looks like the database
upgraded to Postgres. So I bet we could say db name. Db name and give that Postgres,
and it might just know this. We'll see though. Hey, we got a migration. Woo. Do I want to apply
these changes? Sure do. Yeah. So now, let's open up Postico-- where you at-- and refresh. And we have a users table. Let's go. And we have a default view. OK. I don't know enough about
views to know the value in having a default view versus
just hitting the table itself. That's interesting. I would like to know
more about that. This is-- oh, man, I'm
really excited about this. So now we can go back. And first, let's just
add one user ourself. Let's do that. And then we'll read
it out and show it. So how do I want to do this? Yeah, in Postico we're
going to add a new user. And they're going to have--
so let's say, add a row. And their ID, I
think, the database-- the data type that we went
with here was an integer, but it also was
an auto increment. So I'm going to let the Postgres
generate that value of one. And for the name, we'll say
Homer Simpson, my favorite test user character. Could not Save Changes. Oh, well that's surprising. I expected-- So if we look at the structure
here, it doesn't have-- it just says primary key. It doesn't have an auto
increment thing on it. Oh. We saw that something-- there
was a way to have tables-- there was a way to
specify when something should be an auto increment. And I, falsely,
poorly assumed that it was implied by primary key. And actually, now
that I think about it, primary keys can be different. So I shouldn't
have thought that. So I'm going to grab that
auto increment declaration and return to our model
class and we'll add it. And we get to really test
an interesting flow here, a migration not from
scratch, where we just see stormberry's ability
to suss out the difference. So we'll now run the
build command again. And then we'll run-- oh, OK. It failed to build
the database schema. The following
field is annotated, but has an unallowed
type string. I didn't remember
changing this to a string. I do want it to be a string. So then we can't use
an auto increment. So it's time to decide, do
we want to go auto increment, or do we want to go string
and provide UUIDs ourselves. Let's do auto increment. We're going integer. And then maybe we'll
have a UUID field separately that our
frontend can treat as an ID. Actually, I love that idea. That is what I
normally do myself. We'll get to that later. So we're changing a
lot of things here. Now we're really
putting stormberry in a tough place
where we're changing the value of this column. So I almost don't even
want to ask stormberry to get this right, but
let's see what it does. We're rebuilding. And this table
definition, which isn't in this window, this
table definition of text here-- so this is
just all wonky. Let's find out what's going
on when we run migrate again. The migrate code to power this--
oh, yeah, I want to also test-- control A here, db
name equals Postgres. Is this going to not ask
us the database name. It did. All right, here. Wow. The database code to
do this is really-- this is a lot of logic to write. I'm so impressed. So we're migrating this column. That's very good. I do want to apply. And it added next Val here. That is the auto increment. Wow. Very good. Oh, but it finished
with an error. Cannot alter the
column used by a view. Oh. I didn't even really want the
view, I'm not going to lie. So this didn't work
because we have the view, and I'm just going
to delete the view. And we'll see what happens. OK, let's run it again. Let's see if it notices
that the view is gone, and regenerates the view. It is going to do that. Let's see here. Yes. Update successful. We return to the database,
and let's see what we have. So users now uses bigint. It has a sequence. So it's going to
be auto increment. And the user's view has-- well, exists again. Great. Really nice. stormberry-- woo. This-- I can't even say, as
a long time backend developer an old Django fan-- I'm still a Django
fan, honestly, specifically for it's ORM. Man, this really
just warms my heart. So now we can add that user. Content, and then add a row. This-- Discard Changes,
because a lot of things have changed since
that UI was correct. So now we'll add a row. I'm not going to type
anything to the ID, but we, again,
get Homer Simpson. Save Changes. The database gave us our value. We got some starred things here. Tomas asks, could we find
your source code somewhere? Yeah, I should post
that to GitHub. Last week's was so like
immature and bad, that I didn't. But we're going
to get to a point where I should post
things to GitHub. That's a good point. I'll tweet about that later. ShadRack says,
the Dart community is so positive and on fire. Yes, let's go. Killian, also earlier,
helping me out of the-- yes, thank you for that. I was realizing that
as you were typing it. I do that kind of
thing a lot where an external cli-- it's like,
of course, it's not-- it doesn't know that the
changes that I'm typing deep in my application
code, like obviously, that's not going to work. And it just takes me
a while to realize it. Minnu says, more
about serverpod. Yeah. So folks, when people
ask about serverpod, I would encourage you all in
the chat to tell them that-- maybe, one day, we'll get to
serverpod, but in the interim, you should. That's the official stance
of Observable Flutter on serverpod. Do not let me talk you
out of trying it out. And Mouaz-- can I get a phonetic
pronunciation of your name in the chat, by the way. I don't-- I want to know
how to say it again. It's been a minute. But anyway, you're asking,
can we deal with objects not as foreign keys-- we deal as objects not as
foreign keys with relations? Yes, I think that
object under the hood, it's going to make
a foreign key. And Ernest asks, I just started
learning backend this week with Django and Postgres. And today, finding this-- Earnest, sometimes,
serendipity strikes. Django is the gold standard
of ORMs, in my opinion. As the Dart community,
I look at that as the place to
which we can aspire. But what you won't get, what
you will never get with Django, is shared logic with
your Flutter app. That is exclusive to
what we're working on. Antonin says, shouldn't
you have a REST interface between PostgreSQL db
and the client app? Of course, that's what
Dart Frog is going to be. But absolutely, yeah. We're-- I'm just
getting the raw bits, like the connection working,
and getting a model going. But yeah, absolutely, 100%. So let's return to
what we're doing here. We have made-- we
inserted a record. And we just had a question
about the REST database. For now, we have the simplest-- here's our index. We have the simplest REST app
where there's only one URL, and it just gives you a user
that's not at all implied by the URL structure. But this is a URL that's
going to return us a user, and it will be-- this URL will not survive when
we actually become more mature. But let's read a user. And so I'm going to return
to the example code. And this was all on generation,
indices, type converters, usage, repositories. So we've got our db object, and
it has a users attribute on it. And we want like, select one. Query. Oh, we have to-- interesting. So yeah, this is where we need
to understand the repositories again. So the repository exists
on each model in which you can do these things. And you can get a
models repository through its access or property
on the database thing. db users is the repository. I'm so excited. So db.users-- Good. We're off to a good start. Let's reload this, because
we've done generation, and I think we're just going
to have stale intellisense. Or at least that's the
thing that happens a lot. db.user-- oh, did they
name the class plural-- users-- in the thing? Well, I never do that. No-- singular. Oh, maybe it's just a
little typo in the docs. We can live with this. db.user-- there we go. Wait. No, I'm not sure. What is db.user? What are you? It's a string. This is the connection
information. That ain't it. That's not helping us. Interesting. Why are we not getting db.users? Nothing. Let's look at our db models. Odd here. So code db schema. Let's open this file again. And we see that we have
these repositories extension on database. Do I have to import-- I am importing. Because this is an extension,
and they only become available when you import
the file, I think, I actually have to import db
models into my index file. But I had thought-- oh, no, I'm just not
importing db at all. db-- and we'll put you in
the correct sorting order. And now, let's remind ourselves
what db lib, db.dart says. And it says export db source,
which is not correct anymore. We called it db models. So we'll export that. And now in db, it will
find that extension, and I think we can say users-- Nope. I still think this
is what we had to do, but it's not quite working yet. And that could be because
I just updated the export path in a nested file again. So I'm, again, going to
reload just to rule that out. No, it still isn't seeing it. db.user only. OK. It's there. We see it. We've got this user repository. Yeah, no, this is the thing. Let's import the
file itself directly, which should be
redundant, and I do not want to have to do this,
but lib, [INAUDIBLE] oh, no, we don't type. Do we do a source? What do we do here? db-- Yes, source. And then db models. Now we're definitely
bringing this in, db.users-- man, nothing. Am I missing anything here? Repositories-- no. I don't think so. Query complete-- yeah, we're
not getting to those yet. We can't even do this. Let's look at the issues, see if
there was anything about that. Custom view, or custom
SQL view, or whatever, a generator, more coherent in
existing generator packages. Well-- Oh, I see, it would
just be more similar. Type degree, virtual views,
connection pull-- yeah, connection pull. That will be important. OK. Is there anything
closed about this? So our problem here is that we
aren't even able to import-- [INAUDIBLE] sense
isn't finding-- can't generate schemas
from many files. Mark field as auto increment. Escaping variables-- Default view is missing. Oh, this was a thing. So this person wanted
a default view. No default view is generated
for a model without views. But I guess it shouldn't be a
view or a query, in that case. So this person, who's Killian,
he knew why he wanted this. For a second, I thought maybe
it was an external feature request. But no, Killian, himself
wanted that default view. I still don't know
why it's a thing. Killian, feel free to,
if you're still here, say something in the chat
about the value of that, because I honestly don't know. I'm not seeing any
open or closed issues about this problem. So I'm really going to have
to put on our thinking hats. Let's look at the chat and see
if any of you have any ideas. I'm talking about
Firebase, which is a great. Great library-- AMR. We are being respectful
of other technologies. That's a hard, no. Mouaz, you typed
your name again, but it's just the same spelling. So I do want to figure
out the correct way to spell your-- to
pronounce your name. But I don't-- I'm not sure
I'm saying it correctly yet. In the chat, I'm not
seeing anyone with-- is db database? Ah, Killian, you're here. Woo hoo. Yes. So the extension is
within the generated file. You're importing the wrong
one, someone says, but-- oh, is there a no part file? Oh, of course,
there's no part file. I didn't add that. I wonder if that's
how we should do it. This is absolutely the problem. Calamity 210, you're on it. Thank you. You're smart. So I was coming at
this from the freeze, like mindset, where that
generated file is always a part of the main file. And let's see if that's
how we should handle it. No, because this
has its own imports. And it imports db models. Oh, definitely not a
part file situation. That is it. Thank you, Calamity. 10 points for house Gryffindor. So we can now go to
our db source, db.dart. And we will export db
models.schema.dart. There we go. This is why it's great
to have backseat help. Don't need you anymore. Now there, we already saw it. It knows what we're
talking about. Incredible. Query-- now what did query take? No, query is too low level. I don't think we want query. Update one, update many-- I want like-- Oh, query user,
that's what I want. And what do you take? Just an int ID. All right, one. I love it. Oh, wait, and this will
be final user equals that. And now user is-- we just
get that user object. Amazing. Now we have a conflict
between our different users. So I'm going to get rid of the
shared directory right now. And then, we'll return
to that in a second. So user.email doesn't
exist anymore. I think we called it
user.name exclamation point. What's the problem here? User.name can't be
accessed unconditionally. Oh, user could be nothing. I see. Oh, interesting. That's a good point. So if user equals
null, then we will return a different response. So we'll say body not found, and
this should be single quotes. Wrong character to type. And then, let's add a status
code-- there we go-- of 44. Now we have type promoted
our nullable user to a user. And the current
time is not a thing we're worrying about anymore. This should say, welcome to
Dart Frog, Homer Simpson. TheFable asks, Oh,
my gosh, TheFable coming in hot with,
literally, the video game Fable as your icon. That's so good. So what was the problem? I think I missed something. This is definitely a good
one to go back and hit. So essentially, all
of the good code, all the stuff that
we care about, is sitting in this schema file. That's what was
generated by stormberry, and that's where
the extension is. db.users becomes a thing
because of this line of code right here. And database extensions--
in Dart, extensions are not visible in calling
code, so they're not visible anywhere else, unless you
explicitly import the file. They don't magically attach
themselves to the class. That's what views will do,
which is a feature coming soon in Dart. But for now, until this
syntax change is to be views, and thus, it would attach itself
and be visible everywhere, I always wonder is that name
inspired by database views, and I don't know. But, boy, are they
similar things. While it's still
an extension, it is not going to be
visible to outside code. Now I-- almost all
code generation that I do is from the
freezed direction, and in the freezed syntax
has that main class, include as a part file,
the generated code. But this just goes differently. The generated code is
actually its own thing, and you have to import
or export that directly. So in our export file,
in our nested package, we simply had to
explicitly export to expose all of
the generated code. And that meant that our index
file was finally able to read-- here it is-- db.users. Good question. Thank you for asking
me to hit that again. So you have to explicitly
export the file and explicitly
import it as well. Yep. Yeah, oh, I think I
see what you're saying. Well, you-- not really. So kind of. I mean, you definitely have
to explicitly export it, which we're doing here. And then, of course, you have
to explicitly import the file where you export it. So that's the, yes. And we're doing that here
by importing db/db.dart. Good question. Oh, there's some starred ones. I'm going to get to those in a
second, after we see this work. Let's reload here. We've done a lot. I'm just going to really do
a hot restart, as we would call it in Flutter parlance. We are live. And if I refresh, welcome
to Dart Frog, Homer Simpson. And now the next
question, because we knew that we had a really
terrible database setup, where if I hit
Refresh right here, it would just immediately error. But I bet stormberry
handles that for us. So I'm going to refresh. And it works. Yeah. Let's take a look
at some questions. Tozzi says, hello,
I'm a web developer. I need desktop applications. Do you advise me
to learn Flutter? And, yes. Why, yes, I do. Flutter is great for this. Now if you are a
web developer, you may already know JavaScript. And if you need
desktop applications, Electron is, of course,
a framework to consider. And you should also,
especially if you're a JavaScript developer, don't
just gloss over Electron VS Code. One of the most
important apps in my life is made with Electron, and it
is so performant and just great in every way. So don't pass-- don't
sleep on Electron, but yeah, Flutter has got
incredible desktop support. TOKwe says, probably
instead of using the code as a source of truth
for the schema, it is better to write the
schema in SQL files yourself and use flyway to migrate safely
on a new database versions. Yeah, this is definitely-- actually, let me switch to-- let's switch here while
we look at questions. How to handle
database migrations is a really
interesting question, and it's not trivial,
by any stretch. And a lot of times, again, I
come from that Django Python background, and a
lot of times, there, it has actual files
that it generates that handle the migrations. And so, kind of run the Django
command, generate those files, and then, we'll actually
apply those files separately. And you can test
them and look at them and make sure you
know what's going on. They're version controlled,
all that good stuff. And that is
definitely very nice. And sometimes, even,
really kind of getting to what you're
talking about here, sometimes I'll generate in a
shared kind of Python Django project, I'll
generate those files, but that's actually just
for all of my teammates who aren't otherwise
going to easily get their local databases to get the
changes that I'm introducing. So once they check
out my code, they'll get that half of my
change, but their database won't know the difference. So I'll use the Django
migration kind of pipeline to catch up all of my
teammates' databases. But then on the
production database, depending-- certainly if
it's a high traffic table, or something like that,
you visit it with care and really assess, OK, does
this query need downtime? Is there-- maybe we can
add the concurrent flag to the query, or
something for Postgres so that it can alter
the schema safely. But that's a great point. Jay asked, can we integrate
Python code with Flutter? Well, kind of. You can write Python-- you can write a
server app with Python and send it network requests. And in some sense, that
integrates the code. Can you integrate it
more directly than that? Not easily. I'm not like a C and
FFI expert, and whatnot, to know really where or
all the boundaries there. But I'll just say, not easily. TOKwe then, adding to
his previous comment says, and then use a framework
only to map database rows to Dart objects. Yeah, I think that's
totally valid. The migration tool for
stormberry looks functional, strong, but like there's-- obviously, I don't think
Killian considers it robust and totally perfect yet. I think you'll need
some kind of pipeline for those database migrations
to get those schema changes to your teammates. That need will remain. RogueTravel asks,
one of the reasons why I don't use
Firebase is the lockin when migrating databases. Is there a happy
path for migration to a relational
database within GCP? So at a minimum, to
migrate off of Firestore, which is not a thing I'm
advising everyone do, you will have to pay the read
costs for all the records in your database. And you'll have to write
the application code that figures out how to
translate those document collection-based concepts into
relational concepts, which is an enormous task. So if you're already
using Firestore, you do have some lockin, in
that it's just hard to move off. But the data is
always yours, and you have to pay a little bit to
read your entire collection. So that's definitely a thing. One thing I'll
point out, though, is you have database lockin
no matter wherever you go. Now Postgres translates,
so you could pg dump your Postgres database
and upload it somewhere else. You could have a
Postgres database that runs on any Cloud provider. So you're not really locked
into who you pay a bill to, but it's like, if you wanted
to get out of Postgres, and use something
else, it's not going to be much easier than getting
out of Cloud Firestore. So now to your specific
question about, is there a happy
path for migrating to a relational database
that's also in GCP. If you have a
pretty big project, you might have a contact at
Cloud, and I would ask them. I'm not an expert on that. That is a good question. Daniel asks, do you
have to use db connect or something along those lines? I think stormberry is doing
that under the hood for us. But we did pass it all of the
raw credentials, so I mean, it is certainly
doing the same thing. All right, Killian, on your
way out six minutes ago. Thanks. What was that-- Oh,
can we inspect Chrome? Yeah, sure. So this was our source code. And then we can also
look at our elements. And this was the
HTML that came out. And let me make this
a little bigger. Oh, I'm not on the right thing. Here we go. Here is what was
returned by Dart Frog. Let's return and think about
some of this code a little bit more, because I
still want to have-- code sharing is the
whole point of this. And we just introduced
a new user class that we will definitely not want
to bother our frontend UI with. So I'm going to
end today's stream by thinking about
and, hopefully, solving in a reasonable way
how we're going to handle that. So let's think here. Our user class, that's-- let's
look at what we generated here, because there's a possibility
that there's some user class in here that is actually
unaware of databases. And if so, we could use
that, maybe, on our frontend. That could be the shared code. And it would be a real
shame if I missed that-- oh, user view. But it doesn't have JSON
serialization or anything like that. So we would have to add that. But I think this user view will
be the class that we access when we think about our
bridge between the database aware code and the shared code. So user view is going to
be our starting point. But it's not our ending
point, because it doesn't know how to call to
JSON or from JSON or anything. And we do not want to
write those ourselves, so we aren't going to write
our own extension class and just write them. We want to use, at least,
JSON's serializable and probably freezed, as
well, for the whole thing. So let's think about this. We already have our JSON-- we have our freezed class. This lives in
shared models.dart. And it's already out
of sync, as well. We've got email, whereas in the
database class that we made, we called it just name. So that's already a big issue. We are out of sync,
which is pretty annoying. We can potentially
handle this with tests. But even still, you just
don't update the test, and you get false positives
on all of your stuff being in sync. We really want a build command
to ensure that this is correct. And that going to be an
interesting problem to solve. How do we want to do this,
because in theory, the frontend could know about-- oh, wait a minute. Wait a minute. This user view class, let's see
if we can implement user view with our freezed class. Oh, this might be the trick. Let's try this. So in our models class, let's
add implements user view. And we will have
to in our shared code-- of course,
the shared code will have to know
about the database. That's totally expected. So shared pubspec.yaml
because it is going to have to
translate database objects into the shared
log domain space. So here we will add db,
which is the path out of the shared
folder and into db. And stormberry has
a version here. Oh, freeze depends on analyzer. And stormberry depends
on analyzer freeze 2.0. Oh. Stormberry is using an old-- stormberry is pointing to an
older version of the analyzer. And I am not going to go back. I'll add publish to down
here, make that happy. Publish to none. Is that what it wants? Yeah. There's a way to
force this, right? How do you do that? dart pub get-- just
like, I don't care. Do it anyway. Let's go to here. And in our shared folder,
let's say Dart pub get-- I don't know. Oh, dart pub get help. Let's see if this tells
us how to force it. Offline-- report
what dependencies. No, no, there isn't one. I think we can't force it. Does anyone know how
I should get by this? And I'll look at the
chat real quick here. A lot of debate about
different platforms. We've got a couple
of issues that we can file with
stormberry, including the version that we need
to bump for the analyzer that it depends on. If I go to stormberry--
that I am using the latest version, of course, right. I just added it via
the command earlier. And if we go to its GitHub,
and we look at its-- let me make this
a little bigger. We look at its lib
directory, no pubspec.yaml. Where is the analyser? Oh, less than five. Oh, boy. That is a painful one. How old is this commit? The last commit on this
file was yesterday. I wonder if this is
actually required, as opposed to just a relic
that hasn't been looked at. Where's the history? Oh, just a version bump
is all that we've had. But these included-- of course,
they include the pub spec, because that's where the version
is specified on this line. I don't have any great
options for this. I'm wondering,
should I check out-- oh, dependency overrides. Oh, that's the thing. Yes. Dmitry, great. Let me look that up. Dart Dependency overrides--
didn't have it in my clipboard apparently. I thought there was something. Dependencies override. Ooh, there we go. This should do it. So this is, presumably,
just a dev dependency. I don't know. I don't know if there's
dev dependency override, so we're just going
to do this for now. Dependency overrides and
this is the analyzer. And I think it was 521. So in our shared
directory, we ran pub get and we didn't
have an-- oh, 3.1. Overridden dependencies--
it was 3.1, OK. OK. Cool. Cool. Cool. So now we can get
back to having a user class that implements user view,
and we can import user view. That wasn't the button
I meant to press. We've imported user view. So if I were to run code
generation right now-- actually, let's see
what our error is. Awesome. It is not defined. Great. Get out of here. OK, next error. Super interfaces don't
have a valid override for-- see, these are the kind
of errors that I want. And I'm missing
implementation for name. Great. Great. Great. Great. And I think this
override thing might be a similar problem here. So let's just change this
to a required string name. And if I rebuild now, let's see
if that makes everything work. And I want it-- Oh, it's showing me more tests. Get out of here. I don't have any tests yet. I will in the future, I promise. In our shared directory, let's
build dart pub run, blah, blah, blah. Do I need the pub? Is that what it
always barks out? We just run Dart run, right? Dmitry, I appreciate you
having the right answer. It's building. It's building. We still have one error. So the name parameter
email is not defined. Yeah. This is now just name. OK, that's good. You can go away. Super interfaces don't have
a valid override for ID. Yeah, I'm not 100% sure
what it is that it-- What is the thing that
we're not overriding. It says here-- we'll
make it a little bigger for everyone, even. Super interfaces don't have
a valid override for ID. Oh, it's nullable. I wonder if that's the problem. Because remember,
we had the whole should the ID be
nullable or nothing. And ID is an int in interface. There's a string, Dmitry says. Did we make it an int? Oh, we did make it an int. Yes. Thank you. Because I was going to have
the UUID be the string. Again, Dimitry, you
are like a hawk. How much do I have to pay you to
follow me around all the time? OK, there's all the rubble. Oh, it has to be
required-- of course. It works. Hey. Hey. All right, we now have
Daniel Lutton also catching that it needed to be an int. Great job, Daniel. We have a shared code-- shared file between our
frontend and backend. And let's actually
switch back to Flutter and make a network request,
get the value back, and then show it in our UI. That will be the
really cool conclusion. So we've got our server running. So I'm just going to
close all these files. And we're going to return
to our main.dart file. So we're back in Flutter land. Let's make this a little
smaller-- too big. This whole user is
silly, of course. We don't need this anymore. What we're going to do
instead is open up our clients pub spec file. And let's add a dependency
of HTTP, that's what we want. Add dependency, HTTP. 13. Very good. Now we're back in our main file. And let's add HTTP as HTTP. And we alias that just to avoid
conflicts on names and whatnot. It's mad that I'm importing
it, but not using it yet. That's a short-term situation,
because now, in an int state, I'm going to say http.get,
and we're going to go to localhost:-- I don't remember the port-- 8080. And that is actually it. Oh, wait. This has to be in another file. Actually what we'll say is,
the user is now nullable. And then we'll just use .then. So this is going to
get our response here. And what we can now do-- Oh, you know what else we
have to do-- index.dart. We're back on the server
side, and we are not going to return the raw server. We're going to return-- we have to turn it into domain
logic and then serialize it. So our body here-- we've got a couple
of things here. I'm kind of all over the place. Let's recollect ourselves. On the server, we have
a non-shared object. This is the database user. I also really need to change
one of these class names so that it's more clear. Maybe we could call our
shared one, literally, like shared user, or something. I don't want to do that. Actually, we'd namespace
the database one. We'll call that db user. But I'm also going
to do that later. Anyway, this is
the database user. We have to turn this
into the shared user. So now we're
finally going to get to put on that ResoCoder hat we
talked about a long time ago. So if I open up
shared models.dart, here we're going
to add a new class, and this will be
from domain, I guess. So from-- or it
will be factory-- or from database user.fromdb. And this will take a-- we're going to need-- let's add this as db, just
so we're really starting to namespace everything here. So this is going to
take a db.user view. And it'll be the user, and
we'll call it the db user. And this will return
one of its own things. And it will say the
ID is the db user.id and the name is the dbuser.name. So now this
constructor, of course, will bark at us if we add fields
and we forget to add them here. So we're type safe again. We can use a little
tighter syntax. We do need that semi-colon. So we have our translation. And now, so this says, db user
turn into shared domain user. And we need that for our JSON
serialization and our copy with, all those
other things that are application level only. The database does not know
about copy with, right? This is why we have
this distinction still. So we can now turn our-- so we'll say, final. We'll just call this
the shared user, even though, again, our
naming convention isn't great right now. And this is our database-- no, this is a user .fromdb,
and we pass it this. Now the user class-- I think we have to actually
run the generator again. And we're importing. And we have to
import shared again. Oh, yeah, now we
have that conflict. So this will be as db again. OK. Here we go. So user-- it might be a
user versus user view thing. Yeah. This doesn't return user views. It returns users. Well, that's OK. This can just be db.user. So now we have our shared user. And now our shared user
we can say to JSON. And then we'll have
to stringify this. So we'll import encode. Convert. And then it's
JSON.encode, or something. I never remember. JSON encode. Are you right? That is right. So we took our database
user and we converted it into a application-level user. And then that's our shared user. That goes everywhere. Then, we take that shared
user and we convert it into a pure Dart map right here. And then we convert
that map into a string so that it can actually
go on the wire. And let's add a header, by the
way of, content type, JSON. So headers, we'll
add content type. And this will be
application JSON. This should help
our frontend make sense of all of this madness. Now the application is reloaded. So if we return to
our app and refresh, it isn't doing what I expected. Did I not save the file? This is a surprise. Let's run this again. Dmitry asks, what's
the difference between single quotes and
double code strings in Dart? Not much, other than the linter
just picked one to favor. And so, I do what it says. Refresh. What's going on here? You can return response on
JSON, someone points out. Amazing. Does this take a map, and
does it convert it on its own? Oh, it takes the map. Woo. Love it. So we now do this, I guess. Body. Here we go. That's a great tip. This will likely also
imply the header. Very good. But why is this still
just returning-- Oh. No, it is a JSON. I thought it was
still the string. Interesting. We are almost there,
I think, folks. Because now, we can return
to JSON or to our Dart app and type string. Oh, URI.parse--
yep, I saw someone mention that in the chat. URI.parse. We can't just be throwing
around raw URLs anymore. Who was it that said that? Forgot URI.parse-- well,
I was looking for it. But oh, there it is-- Matt, Matt Souza, good eye. URI.parse. And our response
here, we can now say that our late user, well
they're not late anymore, they were just nullable--
user equals user.from JSON. And this is our
response-- .body. And we probably have to
cast it or something. Oh, we have to decode this. So we'll add convert here. This is Dart convert, because
this is a string still, and we have to turn
this into an actual map. So this will be JSON decode. There we go. And we're not checking
our status codes, and whatnot, because this is-- you don't make network
requests in your int state. We're obviously not having
a very robust implementation here. We're just connecting
the technologies. And then once we have a
user, we'll call setState. I guess we'll just do
all of that in setState. I accidentally pressed
Command up, instead of up. So we'll call setState. Here's an oldie, but goodie. Finally, a familiar face. setState-- gosh, I can't
type to save my life. There we go, folks. So we have the user. Now down here, we'll
say, if user equals null, then, I guess, we
return the spinner. So that's the circular
progress indicator.adaptive. And otherwise, I guess, I
don't know if you use the l's. I'm not sure. Otherwise, it'll be user-- why is ID-- property ID can't
be accessed unconditionally. Oh, got it-- not indented. It's an attribute. It can't-- I see. Const class properties. Con, from the
Flutter deverel team had a great decoding
Flutter about this, about how you can't type promote
out of null ability class attributes. So even though I just said
right here, that it's not null, there's nothing to prevent
that from having changed. But let's print
the name instead. That's going to be
way more exciting. We don't need to
do this, I guess. We can just do this. Great. Holy smokes, folks,
this may work. Holy smokes, folks, also has
a pretty nice jingle to it, doesn't it. Out of shared. Out of packages. Out of server. And into client. And we will run Flutter. Run on Mac OS. And big money, big
money, big money. If I've been paying
attention, this might work, which would
be immensely exciting. And while it loads, I
will look at the chat. If I suggest Nest
JS with Flutter. I'm unfamiliar with Nest JS. OK, it's spinning and iterating. And the problem is that
the connection failed. Phooey. Oh, I know why. The connection failed
because of entitlements. I've been here before. I've wasted time on this. Gill-- we need to
open this directory. So in the client, here,
we need to go to Mac OS, open our Workspace. This will actually launch Xcode. Please don't make me
install anything right now. Oh, I have-- no,
that's built in. So I'm good. I don't need to install
anything, I think. I don't know if it's
going to install. I really don't want it to. Lord, have mercy. What a nightmare. Please install quickly. The world is watching. Not really. There's just a few of us here. Xcode is turning on. We have to tell Xcode
A, this is an app that can make outbound
network requests. If you don't do that,
you can't do that. So Xcode-- yeah, launch. There's another window on the
other screen where it's like, do you want me to load? Yeah, I do. What's going on here? Xcode, open this window. Here we are. And now, we go into runner. No. The top runner,
not the bottom one. That'd be silly. Signing and capabilities,
outgoing connections. So we've added that entitlement,
and now we can build again. Woo hoo. This time, it might work. Team, we did it. We've got shared code in our
Flutter app on the frontend and in our Dart Frog
server-side app on the backend. They are talking to Postgres. They are loading a record. They first load it into
this database object. That database object
then gets converted with a little domain-driven
development style thinking, gets converted into
our shared user. I don't endorse this
naming convention. It's quite poor. That shared user is
intelligible by both the front and the backend,
so we return that. On the frontend, we
load it from JSON. And what's critical here, the
reason we care about having this shared logic, and
the reason it was a win to have our shared user be
tied to the implementation of the database user, is that
so we can go into our database file and into our models file
database folder, models file, we can add something new. We can run the
migration command. Migrate. Can't find build yaml. Of course. I am in the wrong folder. packages shared. No. db-- now we run it. And it's going. No changes. Oh, we have to build
again, of course. A couple of steps here
to show the payoff. We build, then we migrate. Now it should say, I'm
going to add a new column. Amazing. That's exactly what
I want you to do. Yes. Transaction error--
column email of users contains a nullable value. So this is just
a database issue. This is not actually-- this is not a problem
of stormberry column. It contains null values. That's right. So we'll just make it
nullable, just for now. So I'm going to regenerate. And then we will rerun
the migration tool. Basically, I
instructed stormberry to do something
incoherent there. So hopefully this now works. There we go. We have the nullable column. We applied it. We're good to go. But we can't forget to add
that value to our shared user. This is the payoff. We must add email
to our shared user. And this will keep
everything in sync. And once I add it
here, in run build, let's just do, by the way. So this will be string. Let's give it a default. Actually, this will be-- yeah, default of empty string. And then not nullable
on this end, email. And I say, aha, I've done it. Now I'll make everyone happy. And I return to shared. Out of db, out of packages--
no, not out of packages. Out of db, into shared. And I say, I'm so smart. Let me build again. And I do. And I'm all snooty. Oh, I made this default. But let's say I didn't make it-- I didn't provide a
default value here, then this would have errored. And it would have
said, hey, you forgot to include that field in your
from db translation method. And then I would
have added it there. So we have some excitement here. I'm going to close by
hitting some questions. Akilesh, which I hope I'm
not totally butchering, says, can we write
code through open API and implement in our Flutter
app, and will it work or not? I have not used
much open API stuff. I know, actually, Brett Morgan
was just looking at that. I saw some messages
from him earlier today about his exploration
on that front. And Brett and I are going
to be working together over the next few months on some
new entries on the cookbook, on either Flutter.dev
or Dart.dev. We haven't really decided
where it's going to go yet. And they're going to explore
a whole menu of options for this kind of stuff. So you might find some
answers when that lands. Good question. Shivam says, can you
suggest a structured way to follow the Flutter
development path? Sure. For my money, the best way
to learn something new, and this is kind
of tightly coupled to how I personally
learn, but I really learned great from videos. But not videos like what
I'm doing right now. This is not the way to have a
structured learning experience. Take a course. [? Van don, ?] the Flutter
GDE in Stockholm, I believe, has an amazing free
course on YouTube. It's like 40 hours or
something like that. 37 was the last
number I saw, but I wouldn't be surprised if
he's continuing to extend it. And he's an excellent teacher. And if you want to
have a structured path through learning
Dart and Flutter, something like his course. There's also paid ones. ResoCoder has one. There's a ton on Udemy. And the paid ones, they're
paid, but they're still very cheap when you consider
how much value you're getting for like $20 on Udemy,
or wherever you buy the thing. And if you just work
through, watch every video, do the exercises,
you will come out the other end, not an expert
yet, but actually ready to work on your own project. And you'll have an idea of
where to go, what to type, how to get started. And, I think, you'll
be successful. Yeah, JSON viewer
extension in Chrome to get a better
experience, Gabriel asks. Yeah, absolutely. I think I just don't have
it in my work persona, but I always use it
in my personal ones. And that's a Chrome persona. Islomkhuja Akhrorov,
which I hope was within in the same galaxy
of how your mother would say your name, says, that's
a lot of boilerplate code. Serverpod generates the
client code for you. Oh, if serverpod
generates client code, that is pretty interesting. And, man, the audience-- you guys are all-- there's just a
steady drumbeat of, we must look at
serverpod, as well. So I think we're going to
have to look at serverpod on Observable Flutter. And can I write state
management, Mohammad asks? Yep, we're going to have
to do that at some point, but not today. So stay tuned. Mouaz asks, and what is
the response code from-- and what the response
code from localhost? Thanks. So I think you're talking about
here, checking the response code, which would be 200. But yeah, I just kind
of glossed over that. Muhammad asks, can I share
a roadmap for Flutter? Well, there's one published. Tim Sneath publishes a
Flutter roadmap every year. And I think for Dart, as well. So the full vision is out there. And I would just
have to Google it. I forget where it lives. And Hector-- Hector
is heckling by asking, what is the best
state management? There's a lot that are great. But start with either,
river pod or Flutter block. That's my recommendation, which
is purely based on the fact that those are the
two that I use. But this is the forever topic. All right, folks, we're
coming up on two hours, so I think this is a
good place to call it. Thanks, everybody, for
tuning in and showing up, asking questions. I think we got some pretty
good stuff done today. I'm happy with our progress. And next week, the schedule
might be a little different. I have a conflict on Thursday. So stay tuned for info on that. But for the most
part, it's going to be a large percentage
of Thursday's, this time. So I will see you
all in the next one.