Clean Architecture for Flutter: Feature-First, Built to Last
How I structure Flutter apps so business logic never depends on the UI or the network — and why it pays off by year three.
Most Flutter apps start simple and slowly turn into a tangle of widgets that fetch data, hold business rules, and talk to Firebase all at once. Clean Architecture is the antidote: it splits an app into layers with one-directional dependencies so that business logic never depends on the UI or the network.
I structure every project feature-first. Each feature — auth, transfers, contributions — owns a folder with four layers: presentation (widgets + BLoC/Cubit), application (use cases), domain (entities and repository interfaces), and infrastructure (the concrete repositories that call APIs or the database). The domain layer is pure Dart — no Flutter, no Firebase — so it is trivial to test.
Repositories are interfaces in the domain layer and implementations in infrastructure. The UI talks to a use case, the use case talks to a repository interface, and only the infrastructure layer knows whether the data comes from REST, Firestore, or a local cache. Swapping a data source becomes a one-file change.
DTOs, entities, and mappers keep the boundaries honest. A DTO mirrors the wire format, an entity is the clean domain model, and a mapper converts between them. This stops backend quirks from leaking into business logic and makes API changes a contained edit.
The payoff is testability and longevity. BLoCs are tested with bloc_test against fake repositories, use cases are plain functions, and the app survives framework churn because the core does not import the framework. It is more upfront structure, but it is the difference between an app you can still extend in year three and one you rewrite.