Clean Architecture for Dart and Flutter Applications: A Quick Overview

Clean Architecture for Dart and Flutter Applications: A Quick Overview

Learn clean architecture from scratch in 8 mins

Experience is what separates Seniors from Juniors. While it often correlates with the number of years an individual has spent in the industry — it doesn't mean that the higher the number, the more experienced the individual is...

Instead, Experience lies in the ability to quickly recognize patterns, use them to solve complex problems efficiently, and make certain predictions based on a handful of observations.

Ironically, experience is the lessons accumulated from constantly getting burnt during exploration. It's these lessons that inform their decision-making process.

Those who do not learn from history are doomed to repeat it

- Winston Churchill

The introduction of the Graphical User Interface (GUI) led to improvements in user experience. But it also brought along a new set of challenges such as maintaining synchronized states, networking, and fostering separation, software professionals have sought to tame the complexity.

Over the years, most have arrived at a fundamental truth: a pattern - that most applications can be broken down into 3 fairly distinct non-overlapping layers with well-defined purposes, namely:

  1. The Data Layer

  2. The Domain Layer, and

  3. The Presentation layer.

Note: We are following the Feature-first directory model

Let's start with the Presentation Layer

This is the layer you will be most familiar with as a Flutter Developer. It encapsulates everything visible or interactable within your application — in other words, what users can perceive and interact with.

It's composed of three components:

  1. Screens: Referred to as Pages or Views by some, screens are the visual interfaces users navigate through. They serve as the primary point of interaction and information presentation.

  2. Components: While synonymous with Widgets, I prefer the term 'components' to emphasize their role as fundamental building blocks. Components are individual, reusable elements that collectively form a Screen. Examples are customized AppTextFields, AppButtons, or CustomDividers — mostly a combination of flutter widgets.

  3. Controllers: This aspect of the Presentation Layer focuses on handling user input and processing events. Controllers manage tasks like navigation, form validation, and other interactive functionalities. It's also important to note that files related to state management are typically housed within this section.

Up next is the Domain Layer

Specific code related to how your app functions resides here.

I call them App Logic, while some people call them Business Logic. As someone who came from Backend to Mobile Development, I find this very weird as I've always thought of the Backend as the single source of truth for any application.

Anyways, regardless of what you call them, the code that typically resides in the Domain layer doesn't have anything to do with Data presentation, storage or retrieval.

Instead, it's all about Manipulating and Transforming data to your specific rules.

A good way to know you are on the right part is when everything in your Domain layer has no code related to Flutter, an External API, or a Service.

It's also composed of three components:

  1. Entities: Entities are the fundamental data structures or objects manipulated in the application. They have no dependencies on external code or layers, but they can depend on themselves.

  2. Use cases: They define a user's unique interaction with your app. Examples include SignUp, AddToCart, and Logout, each representing a specific operation or usage scenario.

  3. Repository: Repositories are abstract classes that establish the interface through which the Domain layer communicates with the Data Layer.

What about the Data Layer?

This layer is responsible for Data Persistence: Storing and Retrieving Data.

Some folks call it the Infrastructure layer, but I will stick with Data because you can quickly infer what that layer does from the name. Any code that needs to communicate with an External API, A Sensor, DB, or Local Cache goes here.

It's composed of three components:

  1. Models: They are just like Entities, the only difference is they add Data Serialization and Deserialization methods to their functionality. Depending on how you are getting your data, the method name could be fromJson, toJson, fromString, toString, fromBuffer, toBuffer and so on.

  2. Repositories: While the Repositories in the Domain Layer provide clean abstract interfaces that only define the contract, the Repositories in the Data Layer implement them with concrete code.

  3. Data Sources: Code that interacts with specific data storage mechanisms, such as databases, APIs, or SharedPreference live here. They are often further grouped into remote or local data sources.

This is how your overall Folder Structure will look like for any Codebase that follows a Clean Architecture:

Are you confused?

Let's fit everything together using a real-world example for clarity

Say you are building a social media application like Twitter

In this scenario, thePresentation Layer would house:

  1. Components: They are things like AppButton, TweetWidget, ProfileCard, etc. They contain the visual elements and interactions users engage with.

  2. Screens: Your Screens will represent different views within the application like the Timeline page, Profile page, Bookmark page, Following Page etc

  3. Controllers: Your controllers manage the state of UI components and user interactions. For instance, the controller might determine whether an AppButton displays "Tweet" or "Reply" based on the current context or user actions. It's worth noting that Controllers act as an intermediary between the presentation layer and the domain layer.

And the Domain Layer?

It handles the application's core functionality such as managing user profiles, handling friend requests, and posting updates. It houses:

  1. Entities: They represent the fundamental objects or concepts within the application. In our Twitter case, some Entity examples are:

    • User represents the platform user, containing attributes like username, email, profile picture, etc.

    • Postis another Entity. It represents a user-generated content item, containing attributes like the post text, image/video attachments, timestamp, likes, and comments.

  2. Use cases: Usecases encapsulate complex operations and business rules that involve multiple entities or require coordination between them. Use cases in an app like Twitter include:

    • Liking or commenting on a post.

    • Posting a new tweet or status update.

    • Following/Unfollowing another User.

    • Searching for users or posts based on specific criteria.

    • Retrieving a user's timeline or profile information.

Use cases often involve validation, authorization, and coordination between different system parts such as multiple Repos or other Usecases.

  1. Repositories: Repositories in this layer are abstraction layers for data access, hiding the details of how data is persisted and retrieved. They define interfaces for performing CRUD operations on entities. A PostRepo, for example, would include the following methods:

    • getUserById(userId): Retrieves a user entity by its unique identifier.

    • createPost(postData): Creates a new post entity with the provided data.

    • getCommentsForPost(postId): Retrieves all comments associated with a specific post.

In the same way that Controllers act as the intermediary between the Presentation layer and the Domain Layer, Repositories in the Domain layer are the intermediary between the Domain Layer and the Data Layer.

Finally, the Data Layer

It's responsible for persisting user data, including profiles, posts, comments, etc., using their corresponding serialisation and deserialization models. Repositories manage data access, and data sources to interact with databases and external APIs.

It houses:

  1. Models: Models are similar to entities in the domain layer but incorporate data serialization and deserialization methods. These methods enable conversion between the model object and its serialized form, which is crucial for data interchange. For instance, in a social media application:

    • The User model might have methods like toJson() to serialize user data into JSON format and fromJson(jsonData) to deserialize JSON data into a User object.

    • Similar methods would exist for other models like Post, Comment, etc.

Models ensure seamless conversion between application objects and their serialized representations for storage or transmission.

  1. Data Sources: Data sources interact with databases, external APIs, or other data storage mechanisms.

  2. Repositories: They implement interfaces defined in the domain layer, bridging the domain logic and the data access layer. They shield the domain layer from the specifics of data storage mechanisms and query languages.

They all follow the Unidirectional Data Flow principle

In a unidirectional data flow, data moves in a single direction throughout the application. In our case, from the Presentation layer based on User input or action to the Domain Layer, and down to the Data Layer if the changes need to be stored.

Data also flows in the reverse process. This approach ensures consistency across the user interface and it simplifies debugging and reasoning about application state since data mutations are centralized and predictable.

To Test your knowledge, What if you are working on a Healthcare Application?

  • What would be the Entities?

  • What would the Usecase look like?

  • What are the Repos you will have?

  • Which layer will generateUserMedicalReport fall?

  • Where would the code for Interacting with Firebase go?

Write what you think in the comment section. I would be there to resolve any confusion. Remember, no wrong answers, we are just learning here. Share a picture or link to your Structure...Let's go 💪

Credits:

  1. Efficient CRUD operation with Clean Architecture with Dio and Riverpod in Flutter by Nikki Eke.

  2. Flutter Clean Architecture - Learn By A Project | Full Beginner's Tutorial by Flutter Guys [A Youtube Video].

  3. The Clean Architecture by Robert C. Martin.

  4. Flutter App Architecture with Riverpod: An Introduction by Andrea Bizzotto

  5. And a bigger thanks to Ayodeji Ogundairo for his comments and guidance whenever I tweet for Clean Architecture.