Flutter Clean Architecture Explained with Folder Structure & Code Example (Android Studio Guide 2026)
April 7, 2026
🚀 Flutter Clean Architecture: Complete Guide with Folder Structure & Code Example
If you’re a mobile developer aiming to build scalable, maintainable, and testable Flutter apps, then Clean Architecture is a must-learn concept. Especially with a mobile development experience, mastering this will elevate your code quality and system design.
This guide covers:
- Clean Architecture basics in Flutter
- Recommended folder structure (Android Studio)
- Real-world coding example
- Best practices
📌 What is Clean Architecture?
Clean Architecture is a design pattern that separates your app into independent layers, ensuring:
- Easy testing
- Scalability
- Maintainability
- Clear separation of concerns
🧩 Core Layers
Presentation Layer → Domain Layer → Data Layer
🧱 Flutter Clean Architecture Layers Explained
1. Presentation Layer (UI + State Management)
- UI (Widgets)
- State management (Bloc / Provider / Riverpod)
👉 Responsible for:
- Rendering UI
- Handling user interaction
2. Domain Layer (Business Logic – Core)
- Entities
- Use Cases
- Repository Interfaces
👉 Pure Dart (NO Flutter dependency)
3. Data Layer (API / DB)
- Models
- Repository Implementation
- Data sources (API / Local DB)
📁 Recommended Folder Structure (Android Studio)
lib/
│
├── core/
│ ├── error/
│ ├── network/
│ ├── utils/
│
├── features/
│ └── user/
│ ├── data/
│ │ ├── datasource/
│ │ │ ├── user_remote_data_source.dart
│ │ │ └── user_local_data_source.dart
│ │ │
│ │ ├── models/
│ │ │ └── user_model.dart
│ │ │
│ │ └── repositories/
│ │ └── user_repository_impl.dart
│ │
│ ├── domain/
│ │ ├── entities/
│ │ │ └── user.dart
│ │ │
│ │ ├── repositories/
│ │ │ └── user_repository.dart
│ │ │
│ │ └── usecases/
│ │ └── get_user.dart
│ │
│ ├── presentation/
│ │ ├── bloc/
│ │ │ ├── user_bloc.dart
│ │ │ ├── user_event.dart
│ │ │ └── user_state.dart
│ │ │
│ │ └── pages/
│ │ └── user_page.dart
│
└── main.dart
💡 Real Example: Fetch User Data
Let’s implement a simple Get User API flow
🔹 1. Domain Layer
Entity
class User {
final int id;
final String name; User({required this.id, required this.name});
}
Repository Interface
abstract class UserRepository {
Future<User> getUser();
}
Use Case
class GetUser {
final UserRepository repository; GetUser(this.repository); Future<User> call() async {
return await repository.getUser();
}
}
🔹 2. Data Layer
Model
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'],
);
}
}
Remote Data Source
class UserRemoteDataSource {
Future<UserModel> getUser() async {
// Mock API call
await Future.delayed(Duration(seconds: 1)); return UserModel(id: 1, name: "John Doe");
}
}
Repository Implementation
import '../../domain/entities/user.dart';
import '../../domain/repositories/user_repository.dart';
import '../datasource/user_remote_data_source.dart';class UserRepositoryImpl implements UserRepository {
final UserRemoteDataSource remoteDataSource; UserRepositoryImpl(this.remoteDataSource); @override
Future<User> getUser() async {
return await remoteDataSource.getUser();
}
}
🔹 3. Presentation Layer (Using BLoC)
Event
abstract class UserEvent {}class GetUserEvent extends UserEvent {}
State
abstract class UserState {}class UserInitial extends UserState {}class UserLoading extends UserState {}class UserLoaded extends UserState {
final String name; UserLoaded(this.name);
}class UserError extends UserState {}
Bloc
import 'package:flutter_bloc/flutter_bloc.dart';class UserBloc extends Bloc<UserEvent, UserState> {
final GetUser getUser; UserBloc(this.getUser) : super(UserInitial()) {
on<GetUserEvent>((event, emit) async {
emit(UserLoading());
try {
final user = await getUser();
emit(UserLoaded(user.name));
} catch (e) {
emit(UserError());
}
});
}
}
UI Page
class UserPage extends StatelessWidget {
final UserBloc bloc; UserPage(this.bloc); @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("User Page")),
body: Center(
child: BlocBuilder<UserBloc, UserState>(
bloc: bloc,
builder: (context, state) {
if (state is UserLoading) {
return CircularProgressIndicator();
} else if (state is UserLoaded) {
return Text("User: ${state.name}");
} else {
return ElevatedButton(
onPressed: () {
bloc.add(GetUserEvent());
},
child: Text("Load User"),
);
}
},
),
),
);
}
}
🔧 Dependency Injection (Optional but Recommended)
Use packages like:
- get_it
- injectable
Example:
final getIt = GetIt.instance;void setup() {
getIt.registerLazySingleton(() => UserRemoteDataSource());
getIt.registerLazySingleton<UserRepository>(
() => UserRepositoryImpl(getIt()));
getIt.registerLazySingleton(() => GetUser(getIt()));
}
🎯 Key Benefits of Clean Architecture in Flutter
✔ Scalable for large apps
✔ Easy to test (unit testing per layer)
✔ Independent layers
✔ Replace API / DB without affecting UI
✔ Industry standard (used in enterprise apps)
⚠️ Common Mistakes to Avoid
❌ Mixing UI with business logic
❌ Direct API calls from UI
❌ Skipping domain layer
❌ Tight coupling between layers
🏁 Conclusion
Flutter Clean Architecture is essential if you:
- Want long-term maintainable apps
- Build enterprise-grade applications