Effective Layered Architecture in Large Flutter Apps

Md. Al - AminMd. Al - Amin
4 min read

When your Flutter app grows beyond a few screens and features, your codebase starts to feel like a jungle. You find business logic inside widgets, API calls scattered across UI files, and trying to test anything becomes a nightmare. Sound familiar?

This is where Layered Architecture comes in and not just for the sake of organization, but for maintainability, scalability, and testability.

Let’s break this down from core principles to real-world implementation with no fluff.

What is Layered Architecture?

At its core, layered architecture separates your app’s responsibilities into distinct layers. Each layer focuses on a specific role and communicates only with its adjacent layers.

Here’s the classic 3+1 layered structure in a Flutter context:

  • Presentation Layer

  • Application Layer (Use Cases)

  • Domain Layer (Business Rules)

  • Data Layer (Repositories, APIs, DB)

Why Layered Architecture Matters in Large Apps

Here’s what layered architecture solves:

  • Bloated widgets: Business logic is moved out into use cases.

  • Hard-to-test code: Each layer becomes independently testable.

  • Tightly coupled APIs: Use repositories and abstractions.

  • Onboarding new developers: Clear folder boundaries make it easier.

  • Reusability issues: Business logic is reusable across screens and features.

Breaking Down the Layers (With Dart Examples)

1. Presentation Layer

This is your UI layer. It includes widgets, BLoC, Cubits, or Riverpod providers. This layer should never contain business logic or direct data access.

Responsibilities:

  • Show UI

  • React to state changes

  • Call use cases not services, not APIs

Example:

class ProfileScreen extends StatelessWidget {
  final GetUserProfileUseCase useCase;

  const ProfileScreen(this.useCase);

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: useCase(),
      builder: (context, snapshot) {
        // show UI
      },
    );
  }
}

2. Application Layer (Use Cases)

This layer represents actions in your app like LoginUser, GetPosts, UpdateCart.

Responsibilities:

  • Encapsulate one app-level action

  • Use domain models and repositories

  • Contain no UI or infrastructure code

Example:

class GetUserProfileUseCase {
  final UserRepository repo;

  GetUserProfileUseCase(this.repo);

  Future<User> call() async {
    return await repo.getUserProfile();
  }
}

3. Domain Layer

This is the core logic of your app where your entities and rules live.

Responsibilities:

  • Define pure business rules

  • Be completely Flutter-independent

  • No external libraries, no platform dependencies

Example:

class User {
  final String id;
  final String name;

  User({required this.id, required this.name});

  bool isValid() => name.isNotEmpty;
}

4. Data Layer

This layer communicates with the outside world APIs, databases, local storage.

Responsibilities:

  • Implement repository interfaces

  • Handle API or DB calls

  • Convert between DTOs and domain models

Example:

class UserRepositoryImpl implements UserRepository {
  final RemoteDataSource remote;

  @override
  Future<User> getUserProfile() async {
    final dto = await remote.fetchUser();
    return User(id: dto.id, name: dto.name);
  }
}

Tools to Support Layered Architecture

Here are some tools and libraries that make working with this structure easier:

  • State Management: BLoC, Cubit, Riverpod

  • Dependency Injection: get_it, injectable

  • Code Generation: freezed, json_serializable

  • Testing: mocktail, bloc_test, test

Real-World Folder Structure

Organizing by feature first, then by layer within each feature is recommended.

Example structure for a profile feature:

/lib
  /features
    /profile
      /presentation
        profile_screen.dart
        profile_cubit.dart
      /application
        get_user_profile_usecase.dart
      /domain
        entities/user.dart
        repositories/user_repository.dart
      /data
        models/user_dto.dart
        repositories/user_repository_impl.dart

Each feature is modular, self-contained, and easier to navigate or test.

Best Practices

  • Use interfaces in the domain layer and implement them in the data layer.

  • Keep presentation logic (UI) separate from business logic (use cases).

  • Make your domain layer pure Dart with no dependencies.

  • Avoid injecting services directly into widgets.

  • Prefer dependency injection over global service locators.

Real-World Use Case: E-Commerce App

In a large shopping app, you might have features like cart, profile, search, checkout. Each of these would:

  • Have its own use cases like AddToCartUseCase, GetCartItemsUseCase

  • Use Cubits/Blocs only for managing state

  • Keep data fetching in data layer via repositories

This structure scales well across multiple teams and codebases.

Common Pitfalls to Avoid

  • Mixing UI code with business logic (e.g., calling APIs inside widgets)

  • Skipping domain layer to move faster (you’ll slow down later)

  • Overusing abstractions when not needed

  • Injecting concrete classes everywhere instead of interfaces

Final Thoughts

Layered architecture isn’t about creating folders for the sake of it. It’s about controlling the direction of dependencies, limiting complexity, and empowering collaboration.

It helps you:

  • Scale your app confidently

  • Keep testing fast and easy

  • Prevent spaghetti code

  • Make onboarding developers smooth

If you’re planning to build a large Flutter app that can grow without chaos this architecture is not optional. It’s essential.

0
Subscribe to my newsletter

Read articles from Md. Al - Amin directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Md. Al - Amin
Md. Al - Amin

Experienced Android Developer with a demonstrated history of working for the IT industry. Skilled in JAVA, Dart, Flutter, and Teamwork. Strong Application Development professional with a Bachelor's degree focused in Computer Science & Engineering from Daffodil International University-DIU.