---
# System prepended metadata

title: What is the best structure of a Flutter professional project?
tags: [Clean Architecture, Flutter]

---


# What is the best structure of a Flutter professional project?
![flutter-logo](https://hackmd.io/_uploads/BJGk58tn-g.png)

Choosing the right folder structure is like picking a blueprint for a building—the right one depends on how many "floors" (features) your app will have. This guide covers five proven architectures, from simple prototypes to enterprise‑grade monoliths, and explains when to use each.

---

## Architecture Comparison at a Glance

| Architecture | Navigation Difficulty | Best Use Case |
| :--- | :--- | :--- |
| **MVC / MVVM** | Easy | Single‑purpose apps, prototypes |
| **Layer‑First** | Moderate | 1–2 developer projects |
| **Feature‑First** | Low (everything is grouped) | **Production‑grade client apps** |
| **Clean / DDD** | High (many files) | Banking, healthcare, long‑term support |
| **Modular** | Very High | Large teams (10+ developers) |

---

## 1. MVC / MVVM – The "Essential" Structure

The most basic separation of concerns. Perfect for small utilities or rapid prototypes where you just need to keep logic out of the UI.

```
lib/
├── models/             <-- Data classes (JSON serialization)
├── views/              <-- All UI screens and widgets
├── view_models/        <-- ChangeNotifiers or simple controllers
├── services/           <-- API or Database calls
└── main.dart
```

---

## 2. Layer‑First – The "Classic" Structure

Organised by **what the file is** (models, logic, screens). This is a refined version of a typical beginner setup and works well for mid‑sized projects.

```
lib/
├── data/
│   ├── models/         <-- user_model.dart
│   ├── repositories/   <-- auth_repository.dart
│   └── data_sources/   <-- api_service.dart
├── logic/              <-- auth_bloc.dart, theme_cubit.dart
├── presentation/
│   ├── screens/        <-- login_screen.dart, home_screen.dart
│   └── widgets/        <-- custom_button.dart
├── core/               <-- constants, theme, utilities
└── main.dart
```

**When to upgrade:** Once you have 10+ screens, jumping between top‑level folders becomes tedious. That's the signal to move to Feature‑First.

---

## 3. Feature‑First – The "Senior" Choice (Recommended)

Organised by **what the user does**. Each feature is a self‑contained mini‑app. This is the most common structure in high‑performing Flutter teams.

```
lib/
├── core/               <-- Shared logic, theme, and network clients
└── features/
    ├── authentication/
    │   ├── data/       <-- Auth models & API services
    │   ├── logic/      <-- Auth Bloc/Cubit
    │   └── ui/         <-- Login & Signup screens
    ├── onboarding/
    │   ├── data/
    │   ├── logic/
    │   └── ui/
    └── specialist/     <-- (Repeat structure for each feature)
```

**Why Feature‑First wins:**
- **Isolation** – Work on one feature without touching others.
- **Scalability** – Duplicate the folder template for new features.
- **Searchability** – All files related to "Onboarding" live together.

---

## 4. Clean Architecture / DDD – The "Enterprise" Vault

The most rigid and scalable pattern. It uses a strict three‑layer separation **inside every feature** to make business logic 100% independent of Flutter.

```
lib/
└── features/
    └── profile/
        ├── domain/        <-- THE HEART: Entities, Use Cases, Repo Interfaces
        ├── data/          <-- THE BODY: Models, Repo Impl, Data Sources
        └── presentation/  <-- THE FACE: Blocs, Pages, Widgets
```

> **The Secret of Clean Architecture:** The `domain` layer has **zero** imports from `data` or Flutter itself. It contains only pure Dart code. This makes it immortal—you could theoretically move the domain layer to a web server or desktop app, and the logic wouldn't change.

---

## 5. Modular Architecture – The "Super‑App" Logic

For massive apps (like Uber or Amazon), the project is split into completely independent packages, each with its own `pubspec.yaml`.

```
lib/ (The Main App "Shell")
└── main.dart

packages/ (Local folders or separate Git repos)
├── auth_module/        <-- Has its own pubspec.yaml
├── payment_module/     <-- Has its own pubspec.yaml
├── chat_module/        <-- Has its own pubspec.yaml
└── core_ui_library/    <-- Shared design system (buttons, colors)
```

This allows different teams to work on separate modules without merge conflicts and enables true code reuse across multiple apps.

---

## 🧭 Which One Should You Choose?

| Your Situation | Recommended Architecture |
| :--- | :--- |
| Hackathon / Prototype | MVC / MVVM |
| Solo developer, <10 screens | Layer‑First |
| Solo or small team, >10 screens | **Feature‑First** ⭐ |
| Regulated industry (banking, health) | Clean / DDD |
| Multiple teams, multiple apps | Modular |

> **Senior Tip:** Regardless of the architecture you choose, always keep a `core/` folder for shared resources like `AppColors`, `AppStrings`, and your `DioClient`. It prevents **code duplication**, the silent killer of all apps.

---

## 🔄 From Layer‑First to Feature‑First – A Practical Upgrade

If you're currently using a Layer‑First structure and want to evolve it to Feature‑First, here's a step‑by‑step mapping:

### Upgraded Folder Structure (Production‑Ready)

```
lib/
├── core/                     <-- Replaces 'constant'
│   ├── constants/            <-- fonts.dart, colors.dart, api_endpoints.dart
│   ├── errors/               <-- failures.dart, exceptions.dart
│   ├── network/              <-- dio_client.dart, http_interceptor.dart
│   ├── theme/                <-- app_theme.dart
│   └── utils/                <-- formatters.dart, validators.dart
│
├── data/                     
│   ├── data_sources/         <-- Remote and local providers
│   │   ├── remote/           
│   │   └── local/            
│   ├── models/               
│   └── repositories/         
│
├── business_logic/           
│   └── blocs/                <-- Grouped by feature
│       ├── auth/             
│       └── onboarding/       
│
├── presentation/             
│   ├── screens/              
│   │   ├── onboarding/       
│   │   ├── specialist/       
│   │   └── user/             
│   └── widgets/              <-- Only **shared** widgets live here
│
└── main.dart
```

### Key Changes Explained

| Change | Why |
|--------|-----|
| `constant` → `core` | Central hub for network clients, theme, errors, and utilities. |
| `data_sources/` | Separates remote API calls from repository logic. |
| `blocs/` sub‑folders | Each major feature gets its own folder, preventing navigation nightmares. |
| `widgets/` (plural) | Only **global** reusable widgets belong here; feature‑specific widgets stay inside their screen folder. |

---

## 📦 Handling Offline Caching (The Professional Way)

Offline caching turns your repository into the **Single Source of Truth**. The repository decides whether to return cached data or fetch fresh data from the network.

### Caching Strategy: **Cache‑First, Then‑Network**

1. **Check connectivity** (e.g., `internet_connection_checker`).
2. **Local storage** – Use **Hive** (fast, NoSQL) or **Sqflite** (relational).
3. **Repository logic**:
   - **Online** → fetch from API → save to cache → return data.
   - **Offline** → fetch from cache → return data.
   - **Offline & no cache** → return `CacheFailure`.

### Example Repository Implementation

```dart
class OnboardingRepositoryImpl implements OnboardingRepository {
  final RemoteDataSource remote;
  final LocalDataSource local;
  final NetworkInfo network;

  OnboardingRepositoryImpl({
    required this.remote,
    required this.local,
    required this.network,
  });

  @override
  Future<Either<Failure, List<OnboardingModel>>> getPages() async {
    if (await network.isConnected) {
      try {
        final remoteData = await remote.getOnboardingData();
        local.cacheOnboardingData(remoteData); // Save for offline use
        return Right(remoteData);
      } on ServerException {
        return Left(ServerFailure());
      }
    } else {
      try {
        final localData = await local.getLastOnboardingData();
        return Right(localData);
      } on CacheException {
        return Left(CacheFailure());
      }
    }
  }
}
```

### Benefits of This Approach

- The UI / Bloc only calls `getPages()`. It never knows where the data comes from.
- Swapping from REST to GraphQL, or Hive to SQLite, only requires changes inside the data layer.
- Offline mode works seamlessly for the user.

---

## 📚 Recommended Packages for a Professional Flutter Project

| Purpose | Package(s) |
|---------|------------|
| State Management | `flutter_bloc`, `cubit` |
| Dependency Injection | `get_it` + `injectable` |
| Networking | `dio` + `dio_cache_interceptor` |
| Local Storage | `hive` or `isar` (fast NoSQL), `sqflite` (SQL) |
| Functional Error Handling | `dartz` (`Either`, `Option`) |
| Connectivity | `internet_connection_checker` |
| Code Generation | `json_serializable`, `freezed` |

---


# ✍️ Author

**Samer Alaa Abu Zaina**  
Computer Engineering  | Flutter Developer  

GitHub:  
https://github.com/SamerZaina