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:

  1. Presentation Layer — The UI (what users see).
  2. Domain Layer — The business logic (rules and actions).
  3. Data Layer — The part that deals with APIs, databases, etc.

Why use Clean Architecture?

  1. Keeps your code organized.
  2. Easier to add new features.
  3. Reduces bugs by keeping code isolated.
  4. 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.dart

Step 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:

  1. Presentation Layer (HomePage) → asks Domain Layer (GetWeather) for weather info.
  2. Domain Layer → asks Data Layer (WeatherRepository) to get the data.
  3. Data Layer → calls API and sends the weather info back to the Domain Layer.
  4. Domain Layer → sends the data to the Presentation Layer to display on the screen.

Benefits of Clean Architecture:

  1. Separation of concerns — Each layer has a specific responsibility.
  2. Easier to test — You can test each layer separately.
  3. Reusable code — The same domain logic can be used in different apps (like web or desktop).
  4. 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:

  1. Presentation Layer → Builds the UI.
  2. Domain Layer → Handles the business logic.
  3. Data Layer → Deals with API calls or databases.

Thank You, Hope You Enjoyed.