Mobile App Development
How to Implement Clean Architecture in Flutter (Beginner Step-by-Step Guide)
If you want to write clean, scalable, and professional Flutter code, understanding Clean Architecture is essential.In this guide, we will focus only on the core concept of Clean Architecture:
- β No State Management
- β No Dependency Injection
- β Only core architecture (Domain + Data + Presentation)
π§ What is Clean Architecture?
Clean Architecture is a design approach where an application is divided into layers to achieve:
- Well-organized code
- Easier maintenance and updates
- Better testability
- Scalability for large applications
π The Golden Rule
- Dependencies always point inward
- Inner layers are independent and do not know about outer layers
Presentation β Domain β Data
π§± The 3 Core Layers
1. Domain Layer (Core Logic)
- The brain of the application
- Contains business rules
- Written in pure Dart (no Flutter, no APIs)
2. Data Layer (Implementation)
- Handles API calls or local database
- Implements contracts defined in the Domain layer
3. Presentation Layer (UI)
- Displays UI
- Handles user interactions
π Recommended Folder Structure
lib/
βββ features/
β βββ user/
β βββ domain/
β βββ data/
β βββ presentation/
π Step-by-Step Implementation (API Example)
Letβs walk through a simple example:
π Fetch User from API
π§ Step 1: Domain Layer (Start Here)
π lib/features/user/domain/
β 1.1 Entity (First File)
π entities/user.dart
class User {
final int id;
final String name;
User({required this.id, required this.name});
}
β Represents core data
β Has no dependency on external layers
β 1.2 Repository (Abstract)
π repositories/user_repository.dart
abstract class UserRepository {
Future<User> getUser();
}
β Defines what data is needed
β Does not include implementation
β 1.3 Use Case
π usecases/get_user.dart
class GetUser {
final UserRepository repository;
GetUser(this.repository);
Future<User> call() {
return repository.getUser();
}
}
β Represents a business action
β This is what the UI will call
πΎ Step 2: Data Layer
π lib/features/user/data/
β 2.1 Model
π models/user_model.dart
import '../../domain/entities/user.dart';
class UserModel extends User {
UserModel({required int id, required String name})
: super(id: id, name: name);
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'],
name: json['name'],
);
}
}
β Maps API response to your app structure
β 2.2 Data Source (API Call)
π datasources/user_remote_data_source.dart
class UserRemoteDataSource {
Future<UserModel> getUser() async {
// Fake API call
await Future.delayed(Duration(seconds: 1));
return UserModel(id: 1, name: "Ali");
}
}
β Contains actual API logic
β 2.3 Repository Implementation
π repositories/user_repository_impl.dart
class UserRepositoryImpl implements UserRepository {
final UserRemoteDataSource remoteDataSource;
UserRepositoryImpl(this.remoteDataSource);
@override
Future<User> getUser() async {
return await remoteDataSource.getUser();
}
}
β Implements the Domain contract
π¨ Step 3: Presentation Layer (Simple UI)
π lib/features/user/presentation/
β Simple UI Page (Without State Management)
π pages/user_page.dart
class UserPage extends StatefulWidget {
@override
_UserPageState createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
String text = "Press Button";
void fetchUser() async {
final dataSource = UserRemoteDataSource();
final repository = UserRepositoryImpl(dataSource);
final usecase = GetUser(repository);
final user = await usecase();
setState(() {
text = user.name;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: fetchUser,
child: Text(text),
),
),
);
}
}
β Direct and simple connection
β Easy for beginners (no Bloc, no Provider)
π Data Flow Explained
UI β UseCase β Repository β DataSource β API
API β DataSource β Repository β UseCase β UI
β‘ Recommended Development Order
- Entity
- Repository (abstract)
- Use Case
- Model
- Data Source
- Repository Implementation
- UI
β Common Mistakes to Avoid
- Calling APIs directly from UI β
- Mixing Models and Entities β
- Importing Flutter into Domain layer β
π― Final Summary
- Domain β Defines business logic
- Data β Implements logic
- Presentation β Displays results
π βAlways start with the Domain β never with the UI.β
π Whatβs Next?
- State Management (Bloc, Riverpod)
- Dependency Injection (e.g., get_it)
Clean Architecture is the foundation everything else builds on.
π Explore More Blogs
If you found this guide helpful, explore more mobile app development articles on our blog.