Coding Studio

Learn & Grow together.

Flutter Clean Architecture Explained with Folder Structure & Code Example (Android Studio Guide 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

Leave a Reply

Your email address will not be published. Required fields are marked *