Awesome, guys.
Welcome back to our Tuesday training.
We're hanging out with the team here again every Tuesday.
We've been doing these internal team trainings,
and now we're just going to be publishing them on YouTube X.
So go follow us over there.
And you can kind of stay tuned to how
we're teaching different things on everything
from shape up to breadboarding to how to use AI,
how to vibe code professionally.
These are all the topics that we're covering here.
And so today, we're kind of picking up on a part two
from Jonato, who's one of our team members in Brazil.
And he, last week, did an awesome training
on kind of an intro to Flutter.
So if you haven't seen that, go check that out.
And today, we're going to kind of wrap up
that introduction to Flutter, but also
talk about how we're using Flutter specifically
in Tribe Social, which is our main product we've
been building the last few years.
So we originally did have that project in Flutter Flow,
and then we finally exited that and put it into Flutter.
And now we've been kind of rebuilding and refactoring
the whole project.
So if you're working on any part of Flutter on our team,
or if you're just curious how we're
using Flutter for Tribe Social, this will be a good video.
So I'm going to hand it over to Jonato.
So hey, everyone.
Nice to be here again.
And for this session, I wanted to start
with just reviewing the key takeaways from last session,
just so we are on the same page and to keep things fresh.
And then we'll dive directly into how Tribe architecture
is currently looking like.
And we'll review one specific case
where we had to migrate from Flutter flow code,
let's say, to a more well-architected and cleaner
way of building things, and how we did this migration,
and what are the key things we keep in mind while doing
this kind of migration.
So let's start.
So reviewing the four main takeaways
from our last session, basically,
so Flutter has this cross-platform power.
We build once and deploy everywhere, mobile, web,
desktop.
And it has a, I wouldn't say like a native performance,
but a very close to native, especially
on the mobile side of things.
The Flutter handling is doing a good job on this side.
Of course, with some, let's say, with some differences,
depending on which kind of feature
or which kind of structure you need to show,
like if you are going to be very animation-oriented
and you are going to need heavy animation, let's say,
then sometimes you may see some type of stuttering,
but that's not the overall experience.
So it's a very stable cross-platform tool.
Also, we have the widget-first thinking.
So like we saw in the previous session,
everything on Flutter is a widget.
And we compose complex UIs using simple reusable blocks
using this declarative syntax.
And this part of using reusable building blocks
is very important.
And I will show how we think about it
when we are migrating from the Flutter flow-generated code,
which, in general, tends to generate a lot bigger
than needed pieces of code, and how we broke this down
into smaller and more reusable components
that we can use across different places in the application.
We also reviewed some of the Dart's modern features,
so strong typing, new safety, how
it's handled async and await, and asynchronous communication
in general, and a few patterns that
makes our code safer and more maintainable.
And we also saw a bit of the state management in Flutter,
starting with the most basic one, which is the set state,
and going up to providers and pubs
that are specifically created for state management.
So that were the main points from our last presentation,
just so we had them fresh on memory
before diving into the next thing, which
will be our type social app architecture overview.
So here, we'll take a quick look into how we are structuring
things on the tribe side and how we are
thinking about this structure.
One important thing to note here is that this is still a work in progress.
We've come a long way with our app and it had a lot of different phases.
As any big production app that we have,
it has its own stages and its own development.
The things we start with is very different from what we have now.
And you see that a lot of this structure, a lot of our architecture,
also reflects the history that we have.
So you see a lot of Flutter Flow legacy things,
some decisions that were made in the past are still present,
and we are always reviewing and updating things as we go.
And the architecture is basically a snapshot of the current moment
and how it's currently looking.
So starting with the tech stack itself and how we are handling deployment,
we use, of course, Flutter and Dart for mobile and web clients,
targeting iOS, Android, and web from one single repository.
We use Firebase services like Firestore, Auth, Storage, and Cloud Functions
to handle a part of the logic of application
and also managing the authentication.
It's also important to note that we also have our own API
that's built and separated from the Firebase structure.
And we are currently looking to the future of migrating completely
out of the Firebase and removing this dependency
and only relying on our API.
We currently have this dependency on Firebase
because during the Flutter Flow phase of the project,
it was a lot easier to have it integrated directly with Firestore,
and this way you could leverage a lot of the Flutter Flow structure
in terms of making it easier to access content from Firestore
and to use it on the app using the Flutter Flow structure.
But now that we are out of Flutter Flow,
we have the ability to kind of gradually migrate off
at least from the backend side of things,
from out of Firebase,
and to be able to rely on our own API for everything backend-related.
But this is a long way.
This is something that takes a lot of work.
And the difficult part about refactoring
is that customers usually don't see it.
So it's a balancing game of delivering new features
that customers will be able to use and see
and also being able to refactor these parts that we are touching.
And here on Tribe, we avoid trying to tackle refactor
as a project in and of itself.
So we usually associate the refactor
with doing some feature-related thing and improving on this feature.
And if this feature is going to touch some places in the app
that need some refactor for some reason,
that need to, for instance, remove some dependency,
then we tackle them, we group them,
and we use this as one big project.
This way, we don't have this huge refactoring sessions
that could take months.
We break them in smaller pieces that are more manageable
and allow us for what I think is actually the most important part
to be able to do constant releases to our customers.
And they will be gradually experiencing a better app overall.
So we have also Flutter fully generated code
that lives in the pages part
and also on the component part of the app.
But like I said, we are gradually migrating it
from clean architecture inside the features tab,
inside the features folder inside the project.
And one interesting piece and one interesting fact
and one interesting part of the Tribe's tech stack
is that we have a flavor-driven strategy.
structure that builds nine different apps.
I think it's nine from my current count,
but we build nine different apps launching
different Firebase projects, bundles IDs, apps on the App
Store and Android Store, and assets configured per flavor.
So this is a very robust structure
that we have implemented using CodeMagick,
where from a single repository, we
are able to deploy to nine different apps,
and we can easily add new customers to this structure
just by setting up the configurations file
that you have for CodeMagick, then setting up the assets.
Even yesterday, I think Bruce, in one afternoon,
was able to easily, like, three to four hours.
Yeah, that's...
Yeah, easily is up, maybe even underrated, but yes.
Within a few hours, we could set up someone new.
Yeah, but see, we are kind of spoiled now,
because now things are a lot faster.
But in the past, like, three to four hours
was, like, nothing in terms of development.
Now we can do a lot in three to four hours, but yeah.
It's nice to have this structure in place,
and even though it's not so fast,
but it's still very robust and allows for creating new tool,
inviting new customers to the app,
and creating their own white label versions of Tripe
with too much of a hassle.
And like I said, we did it.
Actually, Bruce did it this yesterday afternoon
with a new client, which was very cool to see.
So this is a very important part of the app,
and you see that we try to abstract this from the code
as much as possible.
So if you are walking through the code,
you almost never see anything that
will point to the fact that this is serving
nine different apps.
So we have specific places in the app
that we have created abstractions layers that
allow us to touch as little as possible of the application
if we need to have a new customer running.
So like I said here and here on the right,
we have a little bit more of this new client architecture.
So we have a Flavor Config.
We have our CICD running with the flavor structure
from Flutter itself.
Flutter has this flavor system that allows
to generate different apps.
And this also configures the Firebase for each customer.
So each customer has its own Firebase,
and this keeps the shared code clean
while injecting client-specific dependence when we need it.
So here below, we have a, we will
see this a little bit more in Courser directly.
But here is, let's say, the main structure
inside the lib folder, which is basically
the main folder of the Flutter application.
So we have the main, which is the entry
point for the application.
We have inside the config, we have the application
configuration, including flavors.
We have features, which are a work-in-progress, let's say,
migration folder, where we are refactoring things and moving
into that.
We have the pages with all the UI screens,
mostly generated by Flutter Flow.
And we will see the difference between what Flutter Flow
generated code and how it's the code that we actually want
or a code that's more like what we want.
The components, these components as well,
are mostly generated by Flutter Flow.
We'll see how they look.
The data layers, the back-end folder
with services for Firebase and also access to API.
And we have a Flutter Flow folder.
And I would say that one of the main goals
from migrating out of the Flutter Flow,
including this migration, is to eventually get completely
rid of this folder.
Because this folder has a lot of dependencies
from Flutter Flow.
by Flutter for maintain libraries
and it's all uses a lot of this structure
that Flutter flow defines as ideal for their projects
but it's not what we think it's ideal for our own project.
So our goal for the future is probably remove
all dependency from this flow folder.
This will probably be done in small steps,
like I said, as we work on different projects
and different features.
Custom code is basically all the custom code
that was generated, that was created inside Flutter flow.
So in Flutter flow, we have the ability
to create specific sections of code that are custom
and this can be called by our components
and all these codes lives inside this folder.
Auth is everything of authentication related
and app state is a legacy global state management
from Flutter flow as well.
So that's it for the main structure
and for the key folders, key files of that.
Now let's move to how we are thinking
in terms of the desired architecture for the app
and then we will see what's current.
Then we see a little bit of how we use also cursor rules
to try to enforce this desired architecture.
And finally, we will see the current state
of the architecture and one example from migrating
from the old architecture, let's say to this newer one.
So very basic, we have three layers in general,
the presentation layer where widgets, pages
and providers live.
We have the domain layers with the logic itself,
with the business logic.
So use cases, entities and the repository interface.
And finally, we have the data layer with calling,
with access to the Firebase slash API
and the implementations for the repositories
for the interface of the repository
that we define it on the domain layer.
So going in more detail of which one,
presentation widgets depend on domain contracts only.
So everything that's on the presentation layer
should only have access to what's on the domain layer.
So that's the idea of avoiding having to add
all the models and all the stuff
and to import everything that's on the data layer
to the presentation layer.
We don't want to create this cross layer interconnection.
We want to keep things as clean and separated as possible.
So presentation layer only communicates
with the logic layer that informs the presentation layer
how it should behave and what information it should show.
And the presentation layer itself is responsible
for deciding how it's going to be shown,
but not what's going to be shown.
And that's the main difference between the two.
So I think we can, I can...
Actually, I will wait until the migration part
to show how it's currently looking on the implementation,
just so we don't take a lot of time
because I have the dentist to talk a lot.
So let's keep things short for this part.
The domain layer defines the business rules
with entities and use case and never,
and we do never import the widgets related to Flutter
or any of the Firebase direct code.
So it's a cleaner layer
with just the application business logic.
And finally, the data layer implements the repositories
that these in turn will talk to the backend
and map the documents to specific entities
that will return be used by the domain layer.
And the domain layer will pass only the information
that needs to be shown back to the presentation layer.
So it's a very straightforward structure.
And like I said, this is the structure we are aiming for.
This is not what we currently have,
but that's the overall desired state for the application.
We have a lot of cursor rules that we use.
And when I say cursor rules,
you can take into account that this can be migrated
to the cloud code SDK with specific rules
or any other type of AI helper that you are using.
Basically, it's the same thing.
It's just a document of text
that highlights what we want.
So the clean architecture,
basically, it's a folder specifying
and spelling out the solid patterns
and how this is applied to our project.
We have one very interesting rule,
which is the Flutter flow migration rule
in order to clearly define what we want
when we are removing Flutter flow dependencies from the app
and what are the things that we want to remove.
And we have some layer,
which is the Flutter best practice
that's basically oriented to better widget composition,
state handling, and single responsibility principle,
which I think it's basically the main thing
when we are thinking in terms of architecture,
especially in the age of AI,
I would say that good architecture
is even more rewarding to have now than before.
Like before it was very important
because we needed someone else
to be able to jump in our code and to find things easily.
And we also wanted to be able to have the architecture
on the back of our minds
and to work with it in a simple manner.
So if you have like a lot of interconnections
and you don't have a pre-established structure,
then you probably aren't able to create
this house of cards in your mind
of how the app should work.
And when you were working,
you probably would make decisions that were not aligned
with the original architecture
or with the desired architecture.
And I think a lot of this go back
to the single responsibility principle
that now with the age of AI is even more important
where since we have limited context windows for LLMs
and even like 1 million context windows that we have now,
it's not actually 1 million
because if you are going to try to add more stuff
and go to the max amount of context that you can use,
the model starts to get number.
So the idea is that you want to create an architecture
that you need to feed as little as possible
of the architecture, of the widgets, of the files.
You need to feed a little as possible
to be able to get the model
to understand your architecture
and to be able to follow through the changes
without exactly breaking into more small files
rather than fewer larger ones.
That's the best deal of the air for it.
You want to have smaller files
because they are easy to work with
even from a human standpoint,
but also now from a machine standpoint.
So very important to keep this in mind.
Now let's go to the most interesting part in my opinion,
which is this migration case study
for the groups community feed.
So the groups community feed
is basically the main screen of our application.
It's the feed that has all the communication
that's happening, all the posts,
all the lives that were recorded or replaced.
So everything that's important, not important,
but everything that's happening inside the apps
tends to go into the community feed somehow.
Exactly, that's very important.
Lucian just mentioned one thing very important
here on the chat,
which is properly named functions, classes, and variables.
And it's funny because that's even a meme,
like we take like X amount of time to create something
and then 2X the amount of time to name the variable.
for it and I think this is very true and I think this from someone who maybe hasn't worked with
code could think that this is useless and but it's one of the most important things that that we have
is like to have clear functions, definitions, to have clear named classes, to have clear named
variables. It makes your life easier and it makes the AI life easier because then, for instance,
you can have just the definition of the function, meaning you have just the information,
the type of the function, what it expects as a parameter and what's the expected output
and with all the information, with only the contract information, you can make decisions
about it and that's the ideal image or the ideal point that we want to have.
We want to be able to just look at the name of the function, the parameters that it needs and
what's the return and we want to be able to, from this, take this, use it and take into account
into our decisions just by the contracts. So now going back to the migration case study,
like I said, why this page? Why we refactor it? So first, it had 1094 lines of code and for
let's say just a feed, it was a very big component and it was basically all
generated by FlutterFlow, so it was a key component from the app and all the code in it was basically
generated by FlutterFlow, so this was a very strong motivation for this change
and everything was built inside the build function of the widget, which is a very poor practice,
like when you read the build function of the app, it should be quick, clean and easy to read and
with just a glance, you need to understand, hey, by reading the build function of a widget,
I want to be able to imagine what this widget will look like and when you have like
a thousand lines of code, this is not possible because you have paddings, you have other widgets
intertwined with each other, so I will show the before and after for you guys to
be able to have a more concrete example, but the monolithic build, they have dependency on the
FlutterFlow app state management and also inline Firebase queries were all things that were
happening inside this component that we got rid of. The refactor component now has 429 lines,
so less than half and has helper methods, extracted sections and testable streams
and a few of the highlights was, first, the UI was sliced in different focused widgets, so instead
of having one big widget, we broke this down into feature content section, category filter section,
create post section, post list section, all listed under the group components page,
group components folder. We also centralized the logic of the life cycle inside the initialized
page data and show update dialog if needed, so as you can see, just by reading the function, you can
imagine what they do. One is for initializing the page data, so pretty self-explanatory,
and the show update dialog if needed is basically it. We have a dialog that asks the user to update
the app if they need to update it instead of scattered around the schedule by their callbacks
that it's a thing that Flutter Flows relies a lot. And we have a model for delegating the
updates to the selected category with a local set state, avoiding the global state churn because
it was previously dependent on that app state of that Flutter Flow app state management system,
and we replaced it with the simpler option that we have with the set state, so this was a very
Very interesting change as well, and it helped a lot.
So the difference walks through.
Let's say, so pre-reflector defeat
used a paged list view with paging controller,
mixing Firestore queries with widget concerns,
which is very bad.
Like, when we are in the presentation layer,
we want to be seeing just UI-related stuff.
We don't want to be seeing, like, queries and logic,
and that was what was happening.
And Bruce?
No, this is great.
I was going to say, like, maybe you could
dig into that a little bit, because so the, like,
backstory on this particular group page
had been a pain point for probably a year or two.
It's like, we're making a social media app.
This is the feed.
It is kind of like the whole app in one thing.
And we've probably spent months and probably
over, like, a few hundred hours optimizing this page
to get it to work better.
And so we're inside of Flutter Flow, that is.
And so once we moved it out of Flutter Flow, obviously weird.
I mean, I think this maybe, what,
did it take, like, a week or so?
Yeah.
Yeah, it wasn't a long time.
So had we known this sooner, we could
have pulled it out of Flutter Flow sooner
and maybe got some better results faster.
And it really comes, just when you're
using these low-code tools, it does take,
there's so many layers of abstraction.
So you just end up looking at the design.
And we had some pretty smart people,
including yourself, like, all trying to be in Flutter Flow,
trying to fix certain things.
But once we got it out of there, and we kind of
broke all these pieces into kind of getting back closer,
basically losing layers of abstraction,
we could get more control of it.
And so this all looks better.
You said it went from 1,100 lines to 400 lines.
That's great.
The other thing is it fixed so many errors.
It wasn't even working well.
It was slow.
It was laggy.
We had a bunch of weird logic, where
you had to basically post, and then you
wanted to immediately see your post,
or if someone liked your post, it
needed sort of instantaneous updates.
But also, it's an infinite scroll.
There could be thousands of lines of posts in this feed.
So there's lots of little, weird, quirky things.
Basically, it was like an infinite scroll.
But also, we needed to have dynamic heights on the images,
because we have, sometimes there's an image,
sometimes there's text, sometimes there's a video.
So it's a very complex set of parameters.
So not only did we make it much simpler to work on,
and the code's cleaner, it actually works,
if you can imagine that, now that it's built right.
It actually is much faster, it's more instantaneous,
and it's much more reliable now that we've cleaned that up.
So I just want to give some context from the end user
experience as well.
So nice.
You can keep going.
Perfect.
And I think this is what Bruce said,
about layers of abstraction.
I think this is very, very important.
We even were discussing inside our Slack channel
about an article regarding the great collapse of software
quality, or something like that.
And basically, it's an article showing
a lot of problems with the current state of how
software development's working right now, especially with AI.
But I think one of the points that's important for us
right now is this, of the many layers of abstraction.
I like to think about it, of layers of abstraction,
in terms of, it's almost a lever connected to another lever,
and then another lever, and then another lever.
And then you are on the top, pulling one of these levers,
and these go, like, triggering all the levers
from the layers below.
And yeah, it's easier than, it's,
let's say, easier and faster to move the top layer lever than
going down and working on the behind of the scenes,
let's say.
But this also comes with a lot of trade-offs,
especially in terms of having less control over what
you are building.
And also, and which I think it's the most risky one,
is you actually don't know what's happening.
So that's one thing with FlutterFlow.
We can know so much.
Like, we can understand so much, to a point
that if you want to understand more, it's more.
are worth it to just migrate out of it and do it by yourself,
then try to understand their form of abstraction
and work with their abstraction layer.
So that's one thing we actually learned by doing
and by exploring, because we really took the Flutter
flow to the limit.
I really believe that.
We did a great job with it.
And it was a very helpful tool.
It really makes things faster on the beginning,
especially because all this started before.
We have tools like AI to help us with accelerating
some part of the beauty.
But now that times change it, and also
that Tribe itself is a more robust application,
we kind of grew out of Flutter flow.
And we kind of decided that we needed
to have fewer layers of abstraction between us
and what we really wanted to do.
So that's an important thing to keep in mind,
that every time that you are adding a layer of abstraction,
meaning if you are adding a package that
will be doing something for you, this is a layer of abstraction.
And this is fine to a point, but you really
want to think about it.
Like, for instance, this page, we
were using an external dependency
to have the infinite scroll, to have
a paginated infinite scroll.
This component, the way Flutter flow implemented,
it was by actually importing an external dependency
of another developer who built this component,
and we were reusing it, which is fine.
I think it's very useful to have this kind of tools.
But it's an independence in the end.
So you don't know exactly how the developer built.
You don't know when they are going to update it.
You don't know if they are going to update it.
So every time that you are adding new stuff,
you are adding these layers of abstractions,
you need to take this into account.
And in this specific case, we opted
to remove this dependence completely
and to build our own pagination,
our paginated infinite scroller from the scratch.
And it was one of the best things we could have done.
It was like, it took, I think, two or three hours maybe.
But the overall experience was a lot faster and a lot easier
to use from a user standpoint and also
from a developer standpoint.
So yeah, it's worth to keep an eye on it.
Sometimes you have to balance this trade
between ease of use, fast, and the performance
and the control you have over the code.
Nice.
That's awesome.
I know you got about 5 or 10 minutes.
I don't know what you wanted to hit on the last little video.
This is super helpful to kind of see this.
Or if you guys, Lucho and Ira, anyone on the team
want to see anything specific on this,
what else did you want to touch on and jump in?
I will kind of just wrap this part.
Actually, this whole slide right here
is basically a summary of what I did.
So I will just touch these five points.
And then we go to cursor to EDI just
to see how this is implemented, in fact.
So in terms of the migration itself,
here is a small playbook that we have.
It's map the page, identify responsibilities, navigation,
filters, content, and draw a desired widget tree.
And this is really, you can draw it.
You can take just a piece of paper
and write it down and see, hey, I'm satisfied
with how this should look.
And then you have a goal in mind in terms of the widget tree
itself.
Then you extract the components.
You map the components.
Then you extract them out of that big chunk of code.
You isolate the state management.
So we replace the FFF state with scoped providers
or any other state management alternative
that you think is suitable for that component.
You move the logic out by removing the five-star queries
or any type of logic that's embedded into the presentation
layer.
And then you can, in the five and last step,
is that you can add widgets or unit tests
around the new components before deleting the FFF original.
So that's something we also do.
We keep the original components in the code base for a while,
for like three, four months.
for weeks until we see that everything is okay, that it's working properly, and then we delete
the old components. So, key takeaways. Migration will happen incrementally. Keep the generated
widgets around until priority is proven. Aim to shrink files and reduce the cyclomatic complexity
on each pass. I even added a link here to this part because I think it's very useful to think
to have this in mind when you are thinking in terms of code because it allows to visually see
the complexity of your code. So, I really like this. So, basically, every time that you have
a decision, you create new nodes on your cyclomatic tree, let's say. So, for instance,
on this case, they have this very simple example of code, and here is how the complex,
the cyclomatic complexity is drawn for this case. We have, I think we have one,
oh, this one I like to use it. So, let me open it.
So, basically, it's a quick formula that you take your code and you pass it through, and it will
give you a value that you more or less can estimate the complexity of that code. So,
this is a mathematical way of measuring code complexity. So, it's very useful because then
you can basically, this is the kind of thing that you can take and feed to LLM. It will analyze the
code and give you a value, and then you can compare it to the value from your refactor
component. So, this is a good thing to keep in mind. It's useful, too, to have while evaluating
code complexity. And finally, clean architecture folders are the destination and refactor widgets
are the bridge. So, now let's take this last couple minutes just to see how this is looking
in practice. So, first of all, let me show the original component. Let me change the screen I'm
sharing right now. Okay. Perfect. So, this is the original component. This is the widget component.
So, as you can see, we have this state. It's necessary for every widget, but I think the
most important part here to look at is this build function that we have. So,
the build function is basically the main function of every widget. And if I close this,
you see everything is inside the build function. So, this is crazy, to give many
things. Because once you open this page, you really have to look at all this to understand
what this page is doing. Like, what are components of it? How it's laid down? What's the logic behind
it? And it's just and even like here we have like responsive behavior. Like, if it's on
a certain size, we are making things different. And we also have like the logic here
embedded into the widget. And we also have like let me see if I can find
like you have dependencies on buttons from Flutterflow. Like, why do we even need the FF
button widget? It's just a button. We don't need it to be from Flutterflow. So,
this is useful for the Flutterflow itself to reuse its components, but it's not for us anymore.
So, this one example. And as you can see, overall, it's just very difficult to read,
very difficult to understand what's happening. And it is a very chunky piece of code. And
let's just let's see now how we got this a little better. So, this is the refactor component.
And this is the build function for it. So, a huge difference. And it's easier to read.
You can come here and just see, hey, we have
a FAB, if the FAB is expanded, we have a different,
we show it or not.
We have also a drawer, a app bar and we have a body
and we have the FAB, the floating action button
that's associated with that screen.
So let's see what's on the body.
We can come here and here is the body function.
Like I said, very small, very clean, easy to read.
I really like to have like this helper function.
So if we come here, we just literally read.
So we have a top web nav,
then we have the group navigation,
then we have the main component and we have the bottom bar.
We can click here and then we go to the main contents.
Let's see what's there.
So we have a sidebar, nice.
We have the main content column.
Let's see what's on the main content column.
It's right here.
And here's the chunk of the build of the app.
So even here, it's easier to read
because we have separated it in different sections.
So we have the feature content section.
So we have a feature contents widget.
We have the category filter section.
And finally we have the post list section.
So in just, and also you have the create post section,
which just visible depending on the float action button
or if the user presses the button.
But as you can see, even with like this chunkier component,
we just can scan it and we can see, okay,
that's more or less how it will look like
or that's what this page has as a content.
And if I go here into the post list section,
which is kind of the biggest thing I believe here,
then it's on a separate page completely.
That's one of the things that makes it easier.
So if I'm working just on the post section
and I don't need anything else on the page,
I can fit just this file to the LLM for instance,
or I can look at just this file
and this makes my life a lot easier to give my intent.
So you can see just, we have like the helpers functions
here on top.
And if we go here, we also have by the end of the file,
here is the built component again.
Here is the build content.
Here is how it's being built.
So we have a loader.
Then we have the placeholder if the empty state,
basically, if there's no posts.
And then we have a list view with infinite scroll,
which is the list view that we built from zero.
So if we come here and we go to our pre-refactor component,
we were imparting this infinite scroll pagination
and we are not anymore.
We don't need this anymore.
And we are not relying on the external dependence
to build like the key part of our app.
So this is a very important win
that we've got out of this refactor.
And yeah, I think actually we are kind of approaching
the end of time, but I think this is a good overview
of how we are thinking of it
and how we are breaking things down
and how we are making it easier for us to give maintenance
and also making it faster, better performance
and just better overall.
So yeah, I think that's a wrap.
Now I want to hear if you guys have any questions,
if you guys want to take a look at anything specifically,
but that's it for my side.
That's awesome.
Yeah, and so, you know, on a practical,
you guys can drop any comments if you have them
or we'll wrap it up.
But, you know, this was something that,
you know, I said a few hundred hours,
like that translates to tens of thousands of dollars, right?
If not more.
And so building it much now,
of course we had to kind of iterate
and figure out all these things in pieces,
but now that we're at a lot larger scale,
like this, you know, these little inefficiencies
are like, you know, making a huge difference to cost
and ultimately the experience of our customers,
making sure it's a lot, lot faster.
So we'll have a part two or part three of this,
where we have Lucian talk about
how we're removing all the Firebase out of this code
and getting it over to SQL and API
and using our totally different backend.
But it'll kind of,
it'll cool to see kind of the iterations of the.
the code, first getting it cleaned up,
because now we can actually work with it.
And that's one thing I would highly
recommend anyone who's doing any AI coding.
I recently was refactoring another part of the Flutter app
last week on the Create Post section.
And basically, it was just struggling.
It was about 1,500 or 2,000 lines of code.
And it just was not getting things done.
So at first, I just stopped.
And for 30 minutes, we just totally refactored the entire
code, broke it into seven different pages,
seven different files, pulled common things out,
and made it much, much easier to work on.
So it only had maybe three or four files
that were a few hundred lines each, if not that.
And all of a sudden, it just became so much more reliable.
And then I could actually start working on it.
So if you're getting stuck on these AI things,
take time, fix the code, clean it up,
make sure it's logical.
And this is true if I was working on something
and I was handing it to Jonathan,
or Jonathan was handing it to someone else on our team.
If you just built it clean and simple,
it makes it easier for someone to read it from top to bottom.
But sometimes, between AI or using these low-code tools,
the code that comes out is just basically unreadable.
And so if you're having a hard time reading it,
AI is probably going to have a hard time.
That's the moral of the story.
But very cool.
Yeah, Lucian said, he had a comment there.
I want to give the HM function class.
Yeah, that's good.
Okay, that sounds good.
Well, we'll wrap it there.
If you guys have any questions, drop a comment.
We're happy to chat.
We'll continue next week back to some other
different training and stuff.
But yeah.