Knowledge
guide/May 18, 2026·9 min read

Structuring a Flutter app with Clean Architecture

Every Flutter app starts tidy. The mess shows up around month three, when business logic has leaked into widgets and nobody's sure where to add the next feature. Here's the structure I reach for, and the parts I happily skip.

Clean Architecture gets cargo-culted a lot, three folders, a dozen abstract classes, and a codebase that's somehow harder to read than the spaghetti it replaced. Used with judgement, though, it's the difference between an app that scales and one you rewrite.

Three layers, one rule

The whole idea fits in one sentence: dependencies point inwards. The UI knows about the domain; the domain knows about nothing.

  • Presentation, widgets and state (Bloc, Riverpod, whatever). Dumb on purpose.
  • Domain, entities and use-cases. Pure Dart, no Flutter, no packages.
  • Data, repositories, API clients, local DB. Implements interfaces the domain defines.

A folder layout that survives contact

I organise by feature first, then by layer, so everything for one feature lives together:

lib/
  features/
    auth/
      data/        // dtos, repositories impl
      domain/      // entities, repository interface, use-cases
      presentation/// pages, widgets, bloc
  core/            // shared utils, errors, di

The domain is the point

A use-case is just a class with a call method. It reads like a sentence and never imports Flutter:

domain/usecases/sign_in.dart

class SignIn {
  final AuthRepository repo;
  SignIn(this.repo);

  Future<User> call(Credentials c) => repo.signIn(c);
}
tip If a use-case is a one-line pass-through forever, inline it. Don't add a class to satisfy a diagram, add it when there's logic to hold.

What I skip

I don't make a mapper for every model, I don't wrap every primitive in a value object, and I don't add a use-case when a repository call is genuinely all that happens. Architecture is a budget, spend it where the app is actually complex.

The test isn't "does it match the diagram?" It's "can a new teammate add a feature without asking me where things go?"

The payoff

When the domain is pure Dart, your most important logic is testable in milliseconds with no widget tree and no mocks of Flutter itself. That's the real reason to do any of this.

References

  1. Robert C. Martin, Clean Architecture — the original dependency-rule framing.
  2. bloc / Riverpod — state management for the presentation layer.
Written by Shankar KakumaniKnowledge