As Flutter applications grow, managing code becomes challenging. A project that starts with a few screens can quickly become difficult to maintain when you add authentication, APIs, databases, state management, and multiple developers.
This is where Clean Architecture helps.
In this tutorial, we will learn:
- What Clean Architecture means
- Why Flutter projects need it
- The recommended folder structure
- How Presentation, Domain, and Data layers work together
- How to organize a production Flutter application
What is Clean Architecture?
Clean Architecture is a software design pattern that separates an application into independent layers.
The main goal is:
Business logic should not depend on UI, frameworks, or external services.
Instead of mixing everything together:
Screen → API → Database
we separate responsibilities:
Presentation Layer
↓
Domain Layer
↓
Data Layer
Each layer has a specific role.
Why Use Clean Architecture in Flutter?
A simple Flutter project often starts like this:
lib/
├── screens/
├── widgets/
├── models/
└── services/
This works for small apps, but problems appear as the application grows:
- UI code contains business logic
- API changes require many modifications
- Testing becomes difficult
- Code becomes hard to reuse
- Features become tightly connected
Clean Architecture solves these problems by creating clear boundaries.
Clean Architecture Layers in Flutter
A production Flutter application usually contains three main layers:
lib/
├── presentation/
├── domain/
└── data/
Let's understand each layer.
1. Presentation Layer
The presentation layer is responsible for everything related to the user interface.
It contains:
- Screens
- Widgets
- State management
- UI events
- User interactions
Example structure:
presentation/
├── pages/
│ ├── login_page.dart
│ └── home_page.dart
│
├── widgets/
│ └── custom_button.dart
│
└── bloc/
├── auth_bloc.dart
├── auth_event.dart
└── auth_state.dart
The UI should not directly call APIs.
Avoid:
class LoginPage {
Future<void> login(){
api.login();
}
}
The UI should communicate with the domain layer instead.
2. Domain Layer
The domain layer contains the application's business rules.
This is the core of your application.
It should not depend on:
- Flutter
- Firebase
- REST APIs
- Databases
It contains:
- Entities
- Use Cases
- Repository interfaces
Structure:
domain/
├── entities/
│ └── user.dart
│
├── repositories/
│ └── auth_repository.dart
│
└── usecases/
└── login_user.dart
Entities
Entities represent business objects.
Example:
class User {
final String id;
final String email;
User({
required this.id,
required this.email,
});
}
The entity only contains business data.
No API code.
No Firebase code.
No JSON conversion.
Use Cases
A use case represents a user action.
Examples:
- Login user
- Get profile
- Update settings
- Upload image
Example:
class LoginUser {
final AuthRepository repository;
LoginUser(this.repository);
Future<User> call(
String email,
String password
){
return repository.login(
email,
password
);
}
}
The use case does not know where the data comes from.
It only communicates with the repository.
3. Data Layer
The data layer handles external sources.
Examples:
- REST API
- Firebase
- SQLite
- Local storage
Structure:
data/
├── models/
│ └── user_model.dart
│
├── repositories/
│ └── auth_repository_impl.dart
│
└── datasources/
├── remote/
│ └── auth_api.dart
│
└── local/
└── storage.dart
Repository Pattern
Repositories create a connection between the domain and data layers.
The flow looks like this:
Use Case
↓
Repository Interface
↓
Repository Implementation
↓
API / Database
Repository Interface
Located in the domain layer:
abstract class AuthRepository {
Future<User> login(
String email,
String password
);
}
The domain layer only defines what should happen.
Repository Implementation
Located in the data layer:
class AuthRepositoryImpl
implements AuthRepository {
final AuthRemoteDataSource remote;
AuthRepositoryImpl(
this.remote
);
@override
Future<User> login(
String email,
String password
) async {
return await remote.login(
email,
password
);
}
}
The data layer decides how data is fetched.
Production Flutter Folder Structure
For large applications, feature-based architecture works best.
Example:
lib/
├── core/
│
│ ├── constants/
│ ├── errors/
│ ├── network/
│ └── utils/
│
├── features/
│
│ ├── authentication/
│ │
│ │ ├── data/
│ │ │
│ │ ├── domain/
│ │ │
│ │ └── presentation/
│ │
│ │
│ ├── profile/
│ │
│ └── home/
│
└── main.dart
Each feature owns its:
- Screens
- Business logic
- API integration
- Models
This makes the project easier to scale.
Dependency Rule
The most important Clean Architecture rule:
Presentation
|
↓
Domain
|
↓
Data
The domain layer should never import Flutter packages.
Avoid:
import 'package:flutter/material.dart';
inside your domain folder.
Your business logic should remain independent.
State Management with Clean Architecture
Clean Architecture works well with:
- BLoC
- Cubit
- Riverpod
- Provider
Example flow:
Login Button
↓
AuthBloc
↓
LoginUser UseCase
↓
AuthRepository
↓
Firebase API
Each component has a clear responsibility.
Dependency Injection
Large applications usually use dependency injection.
Popular packages:
- get_it
- injectable
Example:
final getIt = GetIt.instance;
void setupDependencies(){
getIt.registerLazySingleton(
() => AuthRepositoryImpl()
);
}
Dependency injection makes code easier to test and maintain.
Benefits of Clean Architecture
Using Clean Architecture gives you:
✅ Better code organization
✅ Easier testing
✅ Independent business logic
✅ Easier feature development
✅ Better teamwork
✅ Easier maintenance
When Should You Use Clean Architecture?
Use it for:
✅ Enterprise applications
✅ Production apps
✅ Apps with multiple developers
✅ Long-term projects
Avoid over-engineering:
❌ Small demo apps
❌ Simple prototypes
❌ One-screen applications
Conclusion
Clean Architecture provides a scalable structure for Flutter applications.
The main principles are:
- Separate UI from business logic
- Keep domain independent
- Use repositories as boundaries
- Organize code by features
- Keep dependencies pointing inward
A clean architecture setup may require more initial effort, but it saves significant time when your Flutter application grows.
Recommended Dev.to Tags
#flutter
#dart
#mobile
#architecture
#cleanarchitecture



Top comments (0)