Streamline Flutter Development with Clean Architecture

Learn how to build modular, maintainable, and testable Flutter apps with clean architecture.

Dhaval Kansara
Simform Engineering
6 min readMay 31, 2024

--

The correct architecture can make or break your app’s success, as it affects not only the development process but also the performance and scalability of the final product.

“Clean Architecture,” coined by Robert C. Martin, is a software design philosophy that organizes code to keep business logic separate from technical implementation (databases, APIs, frameworks). This separation makes application functionality easy to maintain, change, and test.

Why Do We Need Clean Architecture?

Clean Architecture helps build modular, maintainable, and testable apps by separating the code into layers, each with specific responsibilities. This approach is particularly beneficial for large and complex apps.

Clean Architecture Layers

Clean Architecture consists of three main layers: Data Layer, Domain Layer, and Presentation Layer. Each layer has distinct responsibilities and limited dependencies.

Data Layer

Data Layer contains code that communicates with external data sources, such as servers or databases. It includes the implementation of contracts (abstract classes) defined in the domain layer, local/remote data sources, services, and models.

Domain Layer

The domain layer is positioned between the presentation and data layers and encapsulates business logic. This layer comprises entities, use cases, and contracts, which are abstract classes of repositories. According to clean architecture principles, this layer must avoid referencing classes, functions, or imports from the presentation or data layers.

The dependency inversion principle bridges the gap between the data layer and the domain layer. Higher-level modules should not depend on the concrete implementation of lower-level modules but on abstractions or interfaces. For a detailed understanding of the dependency inversion principle, please refer to this link.

Presentation Layer

The presentation layer deals with the UI of the system. In practical terms, any code that displays information on the screen or handles UI logic should be in this layer. Specifically, widgets, controllers, and state holders are citizens of the presentation layer.

This layer depends on the domain layer and must not have any reference to the data layer.

Let’s Understand with the Example of Counter App

We will explore Clean Architecture with a counter app that includes increment, decrement, and reset functions, storing the counter in a local database using Hive. We use MobX for state management and get_it as a service locator.

Counter App

Below is the complete project file structure of the application.

lib/
├── core/
│ ├── data/
│ │ ├── data_sources/
│ │ │ └── counter/
│ │ │ └── counter_local_data_source.dart
│ │ ├── models/
│ │ │ └── counter/
│ │ │ └── counter_model.dart
│ │ └── repositories/
│ │ └── counter/
│ │ └── counter_repository_impl.dart
│ ├── domain/
│ │ ├── entities/
│ │ │ └── counter/
│ │ │ └── counter_entity.dart
│ │ ├── repositories/
│ │ │ └── counter/
│ │ │ └── counter_repository.dart
│ │ └── usecases/
│ │ └── counter/
│ │ ├── get_counter_usecase.dart
│ │ ├── increment_counter_usecase.dart
│ │ ├── decrement_counter_usecase.dart
│ │ └── reset_counter_usecase.dart
│ └── presentation/
│ ├── controllers/
│ │ └── counter/
│ │ └── counter_controller.dart
│ └── screens/
│ └── counter/
│ └── counter_screen.dart
├── injection_container.dart
└── main.dart

Code Flow:

Note: We have used MobX for state management, so we only need one file, counter_controller.dart, for the store. If you plan to use a different state management solution, you can update the files accordingly.

Let’s understand each layer one by one along with the above example.

1. Data Layer

Responsibility

The data layer manages interactions with external data sources, such as databases, network services, or repositories. It also stores and retrieves data.

Components

  • Data Sources: Implement repositories that interact with databases, APIs, or other external services.
  • Data Models: Represent the data structure as stored in external sources.
  • Repositories: Abstract interfaces that define data access and data storage.
── data/
├── data_sources/
│ └── counter/
│ └── counter_local_data_source.dart
├── models/
│ └── counter/
│ └── counter_model.dart
└── repositories/
└── counter/
└── counter_repository_impl.dart

2. Domain Layer

Responsibility

The Domain Layer, also known as the Business Logic or Use Case Layer, holds the core rules and logic of the application, independent of specific frameworks.

Components

  • Entities: Represent the fundamental business objects or concepts.
  • Business Rules and Logic (Repository): Define core functionality crucial to the application’s domain.
  • Use Cases: They hold specific business rules for the application and manage how data moves between different parts. They are responsible for carrying out particular actions or tasks.

Why do we need entities even though we have models?

Entities and models serve distinct roles in software development. Entities focus on core business rules and logic, while models handle data storage and retrieval from various systems. Keeping them separate enables easier management and modification without impacting each other. Additionally, entities are easier to test and maintain because they are independent of external systems, allowing models to be changed without affecting the core business logic.

── domain/
├── entities/
│ └── counter/
│ └── counter_entity.dart
├── repositories/
│ └── counter/
│ └── counter_repository.dart
└── usecases/
└── counter/
├── get_counter_usecase.dart
├── increment_counter_usecase.dart
├── decrement_counter_usecase.dart
└── reset_counter_usecase.dart

3. Presentation Layer

Responsibility

The Presentation Layer is responsible for displaying information to the user and managing user interactions. It includes all user interface (UI) parts, such as widgets, screens, and controllers.

Components

  • Screens: These represent the feature screens.
  • Widgets and UI Components: These represent the application's visual elements of the application. This approach is useful if you need code separation for the UI component. However, we haven’t used it in our example.
  • Controllers: These handle presenting information and interacting with UI components, user input, use cases in the domain layer, and UI adjustments.
── presentation/
├── controllers/
│ └── counter/
│ └── counter_controller.dart
└── screens/
└── counter/
└── counter_screen.dart

Common Files:

Yeah, that’s it for the example.

Pros and Cons of Clean Architecture

Pros:

  1. Modularity and Maintainability: Clean Architecture promotes modularity by segregating components into distinct layers. This segregation enhances maintainability, enabling easier updates or modifications to specific parts without impacting the entire application.
  2. Testability: Separation of concerns facilitates unit testing in Flutter. Business logic residing in the core layer can be tested independently of external dependencies, ensuring more robust and reliable tests.
  3. Independence of Frameworks: Flutter's core business logic isn’t tightly bound to specific frameworks or libraries. This independence simplifies switching or upgrading an application without compromising its core functionality.
  4. Flexibility: Clean Architecture offers flexibility in the technology selection of different layers in Flutter. For instance, you can switch between different state management solutions, UI libraries, or data storage options without significantly altering the core business logic.
  5. Scalability: Clean Architecture's modular structure facilitates better scalability in Flutter. Different application parts can be scaled independently, and teams can work on specific layers without interfering with others.

Cons:

  1. Complexity: Implementing Clean Architecture may introduce additional complexity, particularly in the initial development phases. The segregation of concerns might result in an increased number of files and directories, potentially overwhelming for smaller projects.
  2. Learning Curve: Developers new to Clean Architecture might face a learning curve. Grasping the principles and correctly implementing the architecture could require time and effort.
  3. Boilerplate Code: Clean Architecture might require more boilerplate code, especially in data mapping between layers. While this may increase development time, proponents argue that the benefits of maintainability outweigh this drawback.
  4. Over-Engineering for Simple Projects: Clean Architecture might be excessive for simple or small projects where the advantages of separation and modularity aren’t as pronounced.

We hope this post was helpful and you learned something new.

You can find the full project and code on Github — Source Code.

For more updates on the latest tools and technologies, follow the Simform Engineering blog.

Follow Us: Twitter | LinkedIn

--

--