What is Clean Architecture in Flutter?
Imagine building a house. If the house is built without any proper structure or plan, it will be messy and hard to maintain. Clean Architecture is like a well-organized blueprint for your Flutter app that keeps everything structured, reusable, and easy to maintain.
In Clean Architecture, you split your app into three layers:
- Presentation Layer — The UI (what users see).
- Domain Layer — The business logic (rules and actions).
- Data Layer — The part that deals with APIs, databases, etc.
Why use Clean Architecture?
- Keeps your code organized.
- Easier to add new features.
- Reduces bugs by keeping code isolated.
- Reusable code across different apps.
Three Layers of Clean Architecture
Let's break it down with a real-life example of a "Get Weather Info" app.
1. Presentation Layer (UI)
- This is where you build your screens and widgets.
- It gets data from the domain layer and displays it to the user.
2. Domain Layer (Business Logic)
- This is where you write the core logic (like calculations or rules).
- It doesn't know about the UI or how data is fetched.
3. Data Layer (API/Database)
- This layer handles data fetching from APIs, databases, or local storage.
- It provides data to the domain layer.
Folder Structure for Clean Architecture
Here's how your project should be organized:
lib/
│
├── presentation/ <-- UI (screens and widgets)
│ └── home_page.dart
│
├── domain/ <-- Business logic (use cases, entities)
│ └── usecases/
│ └── get_weather.dart
│ └── entities/
│ └── weather.dart
│
└── data/ <-- Data layer (repositories, API calls)
└── repositories/
└── weather_repository.dart
└── datasources/
└── weather_api.dartStep 1: Presentation Layer (Home Page UI)
Here's how you build a simple UI to show the weather info.
home_page.dart (Presentation Layer)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../domain/usecases/get_weather.dart';
final weatherProvider = FutureProvider<String>((ref) async {
final getWeather = GetWeather();
return await getWeather();
});
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final weatherAsyncValue = ref.watch(weatherProvider);
return Scaffold(
appBar: AppBar(title: Text('Weather App')),
body: Center(
child: weatherAsyncValue.when(
data: (weather) => Text('Weather: $weather'),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
),
),
);
}
}Step 2: Domain Layer (Use Case)
The Domain Layer contains the business logic.
get_weather.dart (Use Case)
class GetWeather {
Future<String> call() async {
// Here, we'll call the data layer to get the weather info.
final weatherRepository = WeatherRepository();
return await weatherRepository.getWeather();
}
}Step 3: Data Layer (API Call)
The Data Layer handles the API call or data fetching.
🧑💻 weather_repository.dart (Repository)
import '../datasources/weather_api.dart';
class WeatherRepository {
final WeatherApi _weatherApi = WeatherApi();
Future<String> getWeather() async {
return await _weatherApi.fetchWeather();
}
}weather_api.dart (Data Source)
class WeatherApi {
Future<String> fetchWeather() async {
// Simulate an API call
await Future.delayed(Duration(seconds: 2));
return 'Sunny, 25°C';
}
}What's Happening in Each Layer?
Presentation Layer : Builds the UI and shows data to users.
Domain Layer : Handles the business logic.
Data Layer : Fetches data from an API or database.
How the Layers Work Together:
- Presentation Layer (HomePage) → asks Domain Layer (GetWeather) for weather info.
- Domain Layer → asks Data Layer (WeatherRepository) to get the data.
- Data Layer → calls API and sends the weather info back to the Domain Layer.
- Domain Layer → sends the data to the Presentation Layer to display on the screen.
Benefits of Clean Architecture:
- Separation of concerns — Each layer has a specific responsibility.
- Easier to test — You can test each layer separately.
- Reusable code — The same domain logic can be used in different apps (like web or desktop).
- Scalable — Adding new features is easier.
Advanced Tip: Use Dependency Injection
To avoid creating instances manually (WeatherRepository()), use dependency injection with a package like Riverpod or get_it.
Quick Summary:
- Presentation Layer → Builds the UI.
- Domain Layer → Handles the business logic.
- Data Layer → Deals with API calls or databases.
Thank You, Hope You Enjoyed.