Clean Architecture in large Flutter teams
The problem with "just add a folder"
Most Flutter apps start clean. A screens/ folder, a widgets/ folder, maybe a services/ folder if someone read a blog post. It works — for the first five screens.
By screen fifty, with three squads shipping in parallel, that same structure becomes the reason every pull request touches the same three files. Nobody agrees on where business logic belongs, so it ends up everywhere: in widgets, in providers, in random "helper" classes.
Layering by responsibility
The fix isn't a smarter folder name. It's separating the app into layers that only know about the layer below them:
- Presentation — widgets and view models. No business rules, no direct API calls.
- Domain — use cases and entities. Pure Dart, no Flutter imports at all.
- Data — repositories and data sources. The only layer allowed to know about REST, GraphQL, or local storage.
class GetCardStatement {
final StatementRepository repository;
GetCardStatement(this.repository);
Future<Statement> call(String cardId) => repository.fetch(cardId);
}
A use case like this doesn't know or care if the repository talks to a BFF, a cache, or a mock. That's what makes it testable without a single widget test, and portable across the three or four squads touching the codebase in the same week.
What this buys you in year three
The payoff isn't visible in week one. It shows up when:
- A new engineer can open the
domain/folder and understand what the app does without touching a singlebuild()method. - Swapping a REST client for a GraphQL one only touches the
data/layer. - Two squads can work on the same feature area without a merge conflict, because presentation and domain rarely change together.
Architecture isn't about writing more code today. It's about the codebase still being cheap to change in its fifth year, with its third team.