Hello! Welcome to your first lesson. This course is designed to build on your existing Flutter experience and guide you toward a mid-level professional standard.
We'll begin by diving deep into the foundations of Clean Architecture. While you've worked with it before, our goal here is to solidify your understanding of the core principles that enable truly scalable and maintainable applications.
In this lesson, you will learn to explain the dependency rule and the flow of data between layers in Clean Architecture. Mastering these concepts is the first and most critical step in applying this architecture effectively in complex projects.
1. Why Clean Architecture? A High-Level View
As applications grow, codebases can become tangled. UI logic gets mixed with business rules, and data fetching code becomes scattered, making the app difficult to test, debug, and update. Clean Architecture, proposed by Robert C. Martin ("Uncle Bob"), provides a blueprint for structuring software to solve these problems.
Its primary goal is the separation of concerns. It divides an application into distinct layers, with the most important rule being the Dependency Rule.
To get a quick visual intuition for how these layers are organized, let's start with a short, conceptual video.
Flutter Clean Architecture Visual Explanation
This video from Flutter Mapp provides a simple and effective visual analogy for Clean Architecture using a cone. It will help you grasp the overall structure and the general flow of information before we dive into the technical details.
Please watch the entire video. Pay close attention to: The three main layers: Presentation, Business (which we'll call Domain), and Data. The "cone" analogy and what it represents. The visual representation of how a user action triggers a cycle that flows through the layers and back to the UI.
As the video illustrates, the architecture is typically divided into three primary layers:
| Layer | Responsibility | Examples |
|---|---|---|
| Presentation | Displays the UI and handles user input. It's what the user sees and interacts with. It should not contain any business logic. | Widgets, Pages, State Management (BLoC, Cubit), Animations. |
| Domain | Contains the core business logic and rules of the application. This layer is the heart of your app and is completely independent of any UI or data-sourcing frameworks. | Use Cases (e.g., GetUserProfile), Entities (e.g., User object), Repository Interfaces. |
| Data | Manages all data operations. It's responsible for fetching data from external sources (APIs, databases) and providing it to the Domain layer. | Repository Implementations, API clients (using Dio), Local Database access (e.g., SharedPreferences, Floor). |
2. The Dependency Rule: The Core Principle
The single most important rule in Clean Architecture is the Dependency Rule. It states:
Source code dependencies can only point inwards.
This means that outer layers can depend on inner layers, but inner layers must never depend on outer layers.
- The Presentation layer depends on the Domain layer.
- The Data layer also depends on the Domain layer.
- The Domain layer depends on nothing.

This raises a question: If the Domain layer can't depend on the Data layer, how does it get data from an API or a database?
The answer is the Dependency Inversion Principle (the "D" in SOLID). Instead of depending on concrete implementations in the Data layer, the Domain layer defines an abstract contract (in Dart, this is an abstract class). The Data layer then provides a concrete implementation of that contract.
This way, the Domain layer only knows about the contract it defined, not the specific details of how or where the data comes from. This keeps the business logic pure and independent.
3. Tracing the Flow: A Deeper Dive
Now, let's explore the specifics of how data and calls move through these layers. The following video from the well-respected Reso Coder channel provides a detailed, practical explanation using a "Number Trivia" app as an example.
Flutter TDD Clean Architecture Course [1] – Explanation & Project Structure
This video walks through the architecture layer by layer, explaining the role of each component and how they connect. It provides a more in-depth look at the concepts we've discussed.
Watch the following segments in order: The Overall Structure: First, watch the introduction to the three layers to see the author's diagram. This reinforces the concepts we just covered. Layers explanation. The Presentation Layer: Understand how the UI layer initiates actions. Pay attention to how the BLoC's role is simply to delegate work. Presentation layer. The Domain Layer: See why this is the core of the app. Focus on the concept of Use Cases and Entities. Domain layer. The Dependency Rule in Action: This is a crucial part. Focus on how an abstract class for the Repository acts as a contract in the Domain layer. This is the key to dependency inversion. Repository contract. The Data Layer: Finally, see how the Data layer implements the repository contract and interacts with Data Sources and Models. Data layer.
To solidify your understanding, let's also look at a textual breakdown of these principles.
Clean Architecture for Flutter Developers: Structuring Your ...
This article by Christian Tsoungui provides a clear, written explanation of the layers and their responsibilities, which will complement what you've just seen in the videos.
Read the article from the beginning down to the section titled "How They Work Together". Focus on the definitions provided for the Dependency Rule and the responsibilities of each of the three layers.
4. A Step-by-Step Walkthrough
Let's synthesize everything into a single, concrete flow. Imagine a user taps a "Get Profile" button in your app.
-
Presentation Layer (UI Interaction):
- The user taps a
Buttonwidget. - The
onPressedcallback is triggered, which calls a method on your BLoC/Cubit, for example,context.read<ProfileBloc>().add(FetchProfileEvent()).
- The user taps a
-
Presentation to Domain (Initiating the Use Case):
- The
ProfileBlocreceives the event. - Inside the BLoC's event handler, it calls the
executemethod of aGetUserProfileuse case. The BLoC depends on the use case from the Domain layer. final user = await getUserProfile.execute();
- The
-
Domain Layer (Executing Business Logic):
- The
GetUserProfileuse case contains the logic for this specific feature. Its job is to orchestrate the fetching of a user profile. - To get the data, it calls a method on a repository interface (an abstract class) that it depends on.
return await userRepository.getProfile();- At this point, the Domain layer's job is done. It has requested data via an abstract contract, without any knowledge of APIs, databases, or JSON.
- The
-
Data Layer (Fulfilling the Contract):
- A class named
UserRepositoryImplin the Data layer implements theUserRepositoryinterface from the Domain layer. - The
getProfile()method inUserRepositoryImplis executed. This class might check if there's an internet connection. - If online, it calls a method on a
RemoteDataSource(e.g.,api.get('/profile')). - If offline, it might call a method on a
LocalDataSourceto retrieve cached data.
- A class named
-
Data Layer (Data Handling):
- The
RemoteDataSourceuses a package likeDioto make an HTTP request and receives raw JSON data. - This JSON is parsed into a
UserModelobject.UserModelis a Data layer class that often includesfromJsonandtoJsonmethods. It might be a subclass of the domain'sUserentity. - The
UserRepositoryImplreceives theUserModeland converts it into a pureUserentity to return to the Domain layer.
- The
-
Data Flowing Back (Data → Domain → Presentation):
- The
Userentity is returned fromuserRepository.getProfile(). - It flows back to the
GetUserProfileuse case. - The use case returns the
Userentity to theProfileBloc.
- The
-
Presentation Layer (Updating the UI):
- The
ProfileBlocreceives theUserentity. - It emits a new state,
ProfileLoadSuccess(user: user).
- A
BlocBuilderin the widget tree listens for this state change and rebuilds the UI to display the user's profile information.
- The
This entire cycle enforces the Dependency Rule while allowing data to flow cleanly across all layers of the application.
Conclusion
In this lesson, we established the foundational theory of Clean Architecture in Flutter. You now have a solid mental model for how the system works.
Key Takeaways:
- Clean Architecture separates your app into three layers: Presentation, Domain, and Data.
- The core principle is the Dependency Rule: dependencies must only point inwards, towards the central Domain layer.
- The Domain layer is independent and contains your core business logic (
UseCases,Entities). It defines contracts (Repositoryinterfaces) for data handling. - The Data layer implements these contracts, handling the "how" of data retrieval from sources like APIs or local storage.
- A user action triggers a call flow from Presentation → Domain → Data, and the result creates a data flow from Data → Domain → Presentation, which updates the UI.
In our next lesson, we will begin putting this theory into practice. You will define and implement the core shared components of the architecture, such as base UseCase and Failure classes. This will create the reusable building blocks we'll need for every feature in our application.