Multi-packages Clean Architecture in Flutter — Why
When I started a new project from scratch six months ago with my team, we took a few days to think how we wanted to code, how we wanted to architecture our app, etc.
Defining choice criteria
In terms of project architecture we could have gone in three different directions:
- not really caring about a specific architecture
- inventing our own code architecture
- using well-known architectural patterns
To make our decision we first started reading a lot of articles on architectures and tried to see which choice best fitted criteria we defined. Our criteria are the ones — we think — that make any serious company app actually usable in the long run:
- Simplicity of use. Will it be easy to add a new feature to the app ?
- Learning curve. Will it be easy for newcomers to understand the app ?
- Scalability. Will the code still be usable as the app grows ?
- Maintainability. Will it be easy to change an existing feature, to substitute a library for another ?
- Quantity of code. How many files and lines of code will be required to add a new feature ?
- Testability. Will the code be easy to test, will we have an overall confidence on delivered code ?
- Stability. How likely will we have bugs and regressions, how easy is it to fix them ?
Understanding Clean Architecture
Clean Architecture is one of the possible architectural pattern for an application, and is pretty easy to comprehend. Popularized by Uncle Bob ten years ago, it is not a brand new pattern but instead tries to aggregate existing and popular patterns such as Hexagonal Architecture or Onion Architecture, maybe in a simpler way:

At the center of this architecture we find the Domain layers that contain the most essential and business-specific logic of the application. It is isolated from external dependencies and frameworks, making it highly independent and reusable.
The next circle represents the Application layer, which orchestrates the flow of data and business rules between the Domain layer and the outer layers. It encapsulates application-specific logic.
Finally, the two outermost circles represent the Infrastructure and Presentation layers, which deal with external concerns like frameworks, databases, UI and external services. These layers also provide implementations of interfaces defined in the Domain and Application layers, enabling communication with external systems.
The arrows in the diagram depict the flow of dependencies, illustrating the rule that dependencies should always point inward, from the outer layers towards the inner layers. This ensures that the core business logic remains decoupled from specific frameworks or technologies, promoting maintainability, testability, and flexibility in the software system.
This architecture seems to fit pretty well our criteria mentioned above.
Clean Architecture in Flutter
We’ve seen the concepts behind the Clean Architecture pattern but you might still be wondering how this architecture translates in a real-world Flutter app.
From what I have seen so far, most Flutter implementations of the Clean Architecture use three layers as their core structure:

- Presentation layer is responsible for the user interface and user interactions. It encompasses the UI components, such as screens, widgets, and controllers, that are responsible for rendering and receiving user input. It also contains the display logic so this is typically where your state management goes.
- Domain Layer contains contains the core business logic and rules of the application. It represents the heart of the application and is independent of any external dependencies. It defines the entities, use cases, and business logic that drive the application’s behavior.
- Data Layer is responsible for handling data persistence and external data sources. It includes components like repositories, data mappers, and network clients. The Data layer provides implementations of interfaces defined in the Domain layer and interacts with external data sources such as databases, APIs, or local storage. It is responsible for retrieving and storing data, as well as mapping data between the format used in the Domain layer and the format used by the external data sources.
Finally, note how the arrows dict the flow of our architecture: domain layer does not have any knowledge of the outer layers, including the UI or specific data sources. It is very important to understand this: domain layer should be pure from any other layer dependency as well as agnostic from any framework or specific implementation details.
Let’s now see zoom on our Clean Architecture structure by looking at this great diagram from Reso Coder’s Flutter Clean Architecture Proposal:

Note how the colored arrows are showing how the data is flowing from the infrastructure details (getting data from an API or a DB) up to the widgets that actually displays something with the data. Keep in mind that those arrows do not indicate the dependency flow, domain is still a pure set of classes that has no knowledge of presentation and data layers.
Notice how the repositories component has two colors: this is exactly how we will have a great decoupling! Repositories interfaces will be defined in the domain layer (e.g. “a method that gets a user”) whereas the implementation of this interface will be left to the data layer (e.g. “I will fetch a user from the backend API”). We will later use Dependency Injection to register the proper implementation for each interface.
🧐 What’s next ?
Ready to see how we split our app into multiple packages ? Go!






