I’m looking for examples of apps whose architecture is a great fit for their size.
For an extreme example on one end, I consider the flutter create Counter app to have a great architecture for its size and use case. Single file, simple setState.
On the other end of the spectrum, there are apps with millions of LOC. For those apps, single file + setState is possibly not the best choice. So people go with some very sophisticated[1] approaches on this end of the spectrum.
I think there’s a lot of focus on app architecture for huge, MLOC apps.[2] But not all apps are meant to be MLOC size. I’d like to hear what people consider good fit for smaller apps (on the order of thousands or tens of thousands of lines of code).
I’m not just talking about state management, but also code organization, tools used (source gen? i18l?) or even lint sets (less draconian than for huge apps? same?).
If the example happens to be open source, all the better. But it doesn’t have to be. I’m happy to hear about approaches for proprietary apps as wekk.
I have a Podcast app that is approx 25k LOC. A lot of the design decisions I made when I first started building it were based on existing knowledge and personal preference. It works well for an app of this size, but might not scale and be suitable for apps in the MLOC arena.
For state management I use the BLoC pattern (not package). This stems my from Java background and familiarity with Rx, and I use RxDart to implement it. I find this really easy to use, but it might be too verbose for very large applications. I use BLoCs to marshal events to and from the UI. I still use some setState() for widgets where the state is only local to that widget
Below the BLoCs I have the services & API layer. This is where the the non-UI specific business logic takes place.
At the bottom we have the database layer. My choice of DB is Sembast. This is a NoSQL database, which has proven to be very reliable, fast and easy to use. It’s an in-memory based database, so perfectly fine for a podcast app but, again, likely less suitable for a larger, more data-centric app.
l10n is handled by the original intl_translation package. To start with, all the arb handling I did manually, but as the language support has grown I have moved to the Weblate platform which is making life a lot easier for both myself and the contributors who are handling the translations.
I’ve been trying to write everything in dart first these days. Basically make it all work as a cli app, and then wire in the ui with flutter. I don’t have a formal background in software development, or work as a coder full time, so I often get lost in the weeds if I try to wire up those packages too early. I like writing it all out in dart as much as possible, so I can just focus on passing objects around and figuring out what I need for the mvp, before I start writing all the epic animations and other stuff that makes the app look nice, but also fun to use.
I was just playing around this week writing something with a few 3rd party plugins, and it was becoming challenging managing things, because I had no plan. It became huge, like things started turning into features, separating it into domains, and all that stuff. I re-write it from scratch with pure flutter/dart, just mashed things together, and got a way better product right out of the gate, but I think that’s because I’m still really green to coding.
I always try to refer back to the flutter recommended stuff for examples of good apps, like gSkinner, VGV, game toolkit, for ideas on how to implement things ‘like a pro’ if that helps :).
I’d be curious which of these you find the most approachable. I’d like to factor in exactly your kind of experience, as a counter-balance to the usual “let’s scale this to MLOC-sized behemoths” architectures. (For the record, I’m not saying that scalable approaches are automatically bad for smaller projects. But some might.)
fwiw cubit was added to the bloc library as a simpler way to manage reactive state that doesn’t require event sourcing. It was also designed to make transitioning from cubit to bloc very easy and requires minimal changes to the view.
Zoho Tables mobile apps are built using Flutter and has a million lines of code. All state management is done using BLoC, ValueNotifier, ValueListenableBuilder.
Our production app which is a location based social media app has about 120k lines of dart code (according to cloc)
Our state management is pretty simple, just a view model and view that’s arranged feature wise.
We use provider to read the viewmodel which themselves are all change notifiers. All dependencies of the view model are injected in using get it. If a view model feels like its getting too large, I usually write manager classes that groups related functionality which this viewmodel can then use
Our rule has more or less been to keep it as simple as possible and only refactor when there’s a reason to instead of prematurely “architecting” because not all features need crazy code patterns, and if one does there’s no reason that everything should be refactored.
We use graphql so that generates the models for us synced across backend and frontend from the schema file (I think this is a very useful with non dart backends to keep the models in sync - flutter graphql also has the benefit of automatically providing caching and optimistic updates)
I think the most interesting thing in our code are a few custom lints I wrote. For example technically you can just use sl() to access the singleton instance anywhere in your code, but we want them to always be injected in and not be used directly.
So we have a rule that warns you if you access this outside of a constructor
There’s another one which enforces that all button taps have logs and analytics callbacks
I think this is a smart approach for many apps. I wonder, though, how do you deal with things like lists (e.g. a ChangeNotifier of a list of social media posts with each post itself being a ChangeNotifier?), and how you deal with pagination. I’m asking because some architectures can code themselves into a corner in regards to these things.
Our parent view model (for eg PostsViewModel) has the list of posts themselves. And then each Post view is able to instantiate its own PostViewModel using the object passed in
So this parent view model is responsible for fetching more posts on scroll , handle refresh pulls etc.
And the child view models are responsible for handling actions on the post for eg reactions/likes etc
Since I want to really learn Dart and Flutter, Im leaning towards using them without any third party packages to sketch things out. I’m pretty close to releasing my first official game, and I’m converting it Bloc now that I have everything mapped out.
It’s probably an experience thing. I suppose if I was doing this as a job, there would be client guidelines, engineering specifications, etc. that require QA/QC along the way. I think that’s where I would reach for Bloc/Cubit right out of the gate because its chefs kiss.
I’m in a transition phase between vibe coding and half vibe coding, and I find that sticking to pure dart/flutter is easier to learn with the help of a LLM as well. I use LLMs all the time to explain old, unmaintained examples, and I find that contextually LLMS can’t handle abstractions well, using the tooling available. If I tell it to stick to pure dart/flutter, it can do that really well, and as I’m reading its explanations, it’s helping me learn the core frameworks.
When I ask it to use Bloc/Cubit, the context of the project dramatically changes into an abstraction conversation, where it starts pulling in all sorts of dependencies, and changing the direction of the project, probably because the separation of concerns is too well executed when bloc is correctly implemented. llm really struggles with Bloc, and I find some struggle with core flutter/dart, some work better than others.
So, as a vibe coder trying to learn, that’s where I’m at I suppose lol.
Using contents of this forum for the purposes of training proprietary AI models is forbidden. Only if your AI model is free & open source, go ahead and scrape. Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.