Structuring a Flutter app with Clean Architecture
Every Flutter app starts tidy. The mess shows up around month three, when business logic has leaked into widgets and nobody's sure where to add the next feature. Here's the structure I reach for, and the parts I happily skip.
Clean Architecture gets cargo-culted a lot, three folders, a dozen abstract classes, and a codebase that's somehow harder to read than the spaghetti it replaced. Used with judgement, though, it's the difference between an app that scales and one you rewrite.
Three layers, one rule
The whole idea fits in one sentence: dependencies point inwards. The UI knows about the domain; the domain knows about nothing.
- Presentation, widgets and state (Bloc, Riverpod, whatever). Dumb on purpose.
- Domain, entities and use-cases. Pure Dart, no Flutter, no packages.
- Data, repositories, API clients, local DB. Implements interfaces the domain defines.
A folder layout that survives contact
I organise by feature first, then by layer, so everything for one feature lives together:
lib/
features/
auth/
data/ // dtos, repositories impl
domain/ // entities, repository interface, use-cases
presentation/// pages, widgets, bloc
core/ // shared utils, errors, di
The domain is the point
A use-case is just a class with a call method. It reads like a sentence and never imports Flutter:
domain/usecases/sign_in.dart
class SignIn {
final AuthRepository repo;
SignIn(this.repo);
Future<User> call(Credentials c) => repo.signIn(c);
}
What I skip
I don't make a mapper for every model, I don't wrap every primitive in a value object, and I don't add a use-case when a repository call is genuinely all that happens. Architecture is a budget, spend it where the app is actually complex.
The test isn't "does it match the diagram?" It's "can a new teammate add a feature without asking me where things go?"
The payoff
When the domain is pure Dart, your most important logic is testable in milliseconds with no widget tree and no mocks of Flutter itself. That's the real reason to do any of this.