An Introduction to Flutter Clean Architecture

Andreas Widijargo
Ruangguru
Published in
6 min readFeb 11, 2022

Clean Architecture

Before we go deeper into Flutter Clean Architecture, let’s talk about Clean Architecture in general.

Clean Architecture is the blueprint for a modular system, which strictly follows the design principle called separation of concerns. More specifically, this style of architecture focuses on dividing software into layers, to simplify the development and maintenance of the system itself. When layers are well-separated, individual pieces can be reused, as well as developed and updated independently.

Clean Architecture is one of the most powerful solutions to build clean apps with independent data layers that multiple teams can work on. The resulting app would also be scalable, readable, testable, and can be easily maintained at any time.

Separation of Concerns

Separation of concerns is about “order”. The overall goal of separation of concerns is to establish a well-organized system where each part fulfills a meaningful and intuitive role while maximizing its ability to adapt to change.

It takes a lot of practice to understand the separation of concerns principle well. But we can start small by implementing these principles/practices:

  • Do one thing: A function should do just one thing and do it well.
  • Single Responsibility Principle: A given method/class/component should have a single reason to change.
  • Dependency Injection: As much as possible, class dependency should be provided by objects outside of the class.
  • Code Architecture: i.e Clean Architecture.

As many great programmers and storytellers have quoted before, slow and steady does win the race. This is a mindset that can easily be lost in the chaos of sprints and release weeks. In hindsight, the pressure to get things done quickly over getting things done correctly is something that we need to reconsider.

“The only way to go fast is to go well.”
- Robert C. Martin

Implementing the Clean Architecture

The Diagram of Flutter Clean Architecture

As we can see in the diagram above, we have 3 main layers of the architecture: Data, Domain, and Feature. We also have 2 additional supporting layers, the Resources and Shared Library layers.

The Dependency Rule

Source code dependencies only point inwards. This means inward modules are neither aware nor dependent on outer modules. However, outer modules are both aware and dependent on inner modules. The more you move inward, the more abstraction is present. The more you move outward, the more concrete implementations are present.

Layers

Feature Layer

Feature Layer is the presentation layer of the application, it is the most framework-dependent layer, as it contains the UI and the event handlers of the UI.

We will need widgets to display something on the screen. These widgets are controlled by the state using various state management design patterns used in Flutter.

  • Pages: These are simply the pages of our app.
  • State Management: BLoCs, Provider, GetX, etc.
  • Widgets: Any other specific widgets needed by our pages.

Domain Layer

Domain Layer is the innermost part of the layers (no dependencies with other layers) and it contains Entities, Use Cases & Repository Interfaces.

The domain layer would be written purely in Dart without any Flutter elements. The reason is that the domain should only be concerned with the business logic of the application, not with the implementation details. This also allows for easy migration between platforms, should any issues arise.

  • Use Cases: Application-specific business rules
  • Entities: Business objects of the application
  • Repositories: Abstract classes that define the expected functionality of outer layers

Data Layer

Represents the data layer of the application. The Data module, which is a part of the outermost layer, is responsible for data retrieval. This can be in the form of API calls to a server and/or a local database. It also contains repository implementations.

  • Repositories: Actual implementations of the repositories in the Domain layer. Repositories are responsible to coordinate data from the different Data Sources.
  • DTO Models: Representation of JSON structure that allows us to interact with our data sources.
  • Data sources: Consist of remote and local Data Sources. Remote Data Source will perform HTTP requests on the API. While local Data Source will cache or persist data.
  • Mapper: Map Entity objects to Models and vice-versa.

Resources and Shared Library

These 2 layers can be accessible to all other layers:

  • Resources: Contains assets (images, fonts, colors, etc), and other configurations.
  • Shared Library: Contains reusable components, functionalities (navigation, network, etc), and 3rd party libraries.

The Codes

Enough theory, now let’s jump into the code. In the example below, I will show you the implementation to get the user details from the API.

Project Structures

Project structure can be built in several different ways. It is different from layers but it still represents the layers that we talked about above. We will create a core folder that contains data & domain layers.

Note that we can also separate data and domain layers into two different folders. But in this example, I will group them in the core folder together.

Application- core
- user
- data
- dto
- user_dto.dart
- user_response_dto.dart
- datasource
- user_remote_datasource.dart
- user_local_datasource.dart
- mapper
- user_mapper.dart
- repository
- user_repository.dart
- di
- dependency.dart
- domain
- repository
- user_repository_impl.dart
- entity
- user_entity.dart
- usecase
- get_user_list_usecase.dart
- get_user_detail_usecase.dart
- di
- dependency.dart
- feature
- user
- page
- user_list_page.dart
- user_details_page.dart
- controller
- user_list_controller.dart
- user_details_controller.dart
- widget
- user_card_widget.dart

Is it clean for structuring the project? At the time, I found out that this project structure does not only help newcomers to catch up easily and start working on it but also helps code readers figure out easily what we aim to do and navigate through the large codebase.

Data

The role of the data layer is to deal directly with the raw data from different data sources, either from local sources (Local DB, Shared Preferences) or remote sources (remote API), usually in JSON format. It is then parsed to a Dart object called DTO.

I created user_dto.dart to map the JSON structure that allows us to interact with our data sources. user_remote_datasource.dart is our remote source, to perform HTTP requests on the API.

user_repository_impl.dart contains the real implementations of the repository abstraction in the domain layer, so we define the abstract class in the domain layer, then we implement that class in the data layer. By doing so, we can change or add multiple implementations without interacting with the Domain layer.

Mapper is a straightforward class, to map data that we get from the API — in our case the DTO — to our business object.

Domain

The most interesting layer in the architecture — the inner part of the layer — means that our domain’s entities are completely independent of any changes that could occur outside this layer. Both Feature and Data layers depend on this layer, since the data layer will implement whatever contracts are written here, and the feature layer will use those contracts with the implementations to be used as injected dependencies.

Keep in mind that the feature layer will only get data as entities and not DTO, this is why we separate each layer individually and independently.

Feature

Presentation is where the UI goes. You need widgets to display something on the screen. These widgets are controlled by the state using various state management design patterns used in Flutter.

Flutter has a lot to offer in terms of the user interface. You may notice, though, that the code becomes heavily nested almost immediately. The majority of the UI code should be divided and organized with some sort of logic in mind.

I have two subfolders called ‘Page’ and ‘Widget’ in this layer. The term ‘Page’ is to show each page of the applications. ‘Widget’ is a collection of components, such as a card, list, or decoration box, that is part of the page itself.

In this example, I use GetX as the state management. A controller is a place where we can call and get the data from the Usecases in the domain layer and handle business logic. The Controller has no access to the Widgets themselves.

The controller will extend the GetxController class, which is an abstract class that extends the DisposableInterface. By extending DisposableInterface, GetX helps us to reduce memory consumption by deleting our controller from memory immediately after the widget is removed from the navigation stack.

Conclusion

Good architecture is key to building a modular, scalable, maintainable, and testable application. Clean Architecture is a core architecture for many applications nowadays, mainly because it allows to separate business logic and scales well.

The architecture we use can have a significant impact on the structure of our projects. Clean architecture does need more code to implement, so, implementing a clean architecture for a small project may be overkill. But, it reduces the complexity and it is necessary for large-scale projects. Bear in mind that every project may have its requirement, so this article does not contain the exact, perfect project style.

Hope that this has been helpful and you learned something new. Thank you for reading and feel free to reach out should you have any questions or comments about the article!✨

--

--