Flutter / Firebase architecture — the layou-dev-kit

A bad import doesn't compile.

An architecture held up by good intentions doesn't hold. This one is split into packages whose dependencies are checked by the Dart compiler. If presentation imports data, it isn't a warning, a lint, or a review comment: it's a red error that blocks the build.

The 5 packages, and what each is allowed to see

The cost of no architecture

What breaks an app is almost never the tech

The 18-month refactor

The app works, it grows, and each new feature costs more than the last. The moment you should accelerate is the moment you rewrite.

The dev who can't leave

One person knows why that file exists. The knowledge lives in a head, not in the structure.

The review that argues style

You debate indentation and naming while a Firestore call slips into a widget.

What it returns

Four benefits, and the mechanism behind each

An architecture promise with no mechanism is a slogan. For each one, here's what actually enforces it.

Scalability

Add people without slowing the team down

The monorepo is split by layer, not by screen. Two devs on two features don't collide: they don't touch the same packages.

  • A feature is a vertical slice across 4 separate packages
  • Package splitting keeps builds incremental — you don't recompile the whole app
  • A new dev reads 5 folders, not 300 files — the structure tells them where their code goes
  • Naming conventions are fixed: the file to create is deduced, not debated

Maintainability

The blast radius of a change is known upfront

When a business rule moves, it moves inside domain. Nothing else compiles differently. That's what a held architecture buys: knowing what you're not breaking.

  • domain is pure: testable with no emulator, no Firebase, no widget
  • Repos are interfaces — swap in a fake with one line of DI
  • Replacing Firestore with something else only touches data
  • Max 50 lines per function, 200 per class, 1 public class per file

Evolvability

The MVP and the V3 are the same codebase

Clean Architecture from day 1, even for an MVP. No throwaway code, so no rewrite exactly when the app finally starts working.

  • Errors are typed: sealed class Failure + exhaustive switch — adding a failure case becomes a compile error everywhere it's missing
  • Either on writes: the failure path lives in the signature, not in a forgotten try/catch
  • Every external SDK is wrapped before it reaches app code
  • Migrations happen layer by layer, not in a big bang

Security

Few chokepoints, so little to audit

An architecture doesn't secure an app — it decides how many places you must go through. That number is what makes an audit feasible.

  • The client calls no backend function: callables are banned. It writes a status: pending document, a trigger processes it. One entry surface to govern instead of a fleet of HTTP endpoints
  • Every write goes through a mandatory usecase — one place to validate, not twelve write paths
  • Backend secrets live in Secret Manager, never in a production .env. Client-side, envied injects them at compile time
  • No raw Firebase exception reaches the UI: it's translated into a typed Failure at the data boundary

The rule

What the compiler refuses

Each package declares its dependencies. This table isn't a team convention pinned to a wall: it's the real state of the pubspec.yaml files. A red cell is a broken build.

What the compiler refuses
Package… core domain data presentation app
core ·
domain ·
data ·
presentation ·
app ·
  • can import
  • won't compile
// ✅ OK — presentation → domain
import 'package:myapp_domain/auth/entities/app_user.dart';

// ❌ COMPILATION ERROR — data qui fuite dans presentation
import 'package:myapp_data/auth/datasources/auth_remote_datasource.dart';

Tooling

Discipline, delegated to the machine

The rest runs through `custom_lint`. These are rules nobody has to defend in review — they fail on their own.

  • molecule_no_riverpod error
    A molecule receives everything through its constructor. That's what makes it testable with zero mocks.
  • connector_must_be_consumer error
    The connector is the only place Riverpod touches the widget tree. Anywhere else is a leak.
  • max_function_lines · 50 warning
    A longer function hides a decision nobody named.
  • max_class_lines · 200 warning
    Beyond that, the class does two things. Split it before it hardens.
  • one_public_class_per_file warning
    The file path becomes the project index — you find without searching.

And there's no escape hatch: disabling a rule with // ignore_for_file is forbidden by the kit. You refactor instead.

Before / after

The same shortcut, in both worlds

Without the dev-kit With
Forbidden import It compiles (but it's broken) Compilation error
Oversized widget Nobody notices Lint error at 200 lines
Molecule using Riverpod It works Lint error
Testing a widget Mock hell Zero mocks
New AI agent Must re-read every convention The compiler guides it

Why it matters in 2026

A constraining architecture is one an agent can follow

An AI agent writes fast, and it cheats: a datasource imported into a widget, three classes in one file, a setState “because it's simpler.” The worst part is that it compiles — and quietly rots the codebase. What stops it isn't a longer prompt: it's a project where the shortcut doesn't compile. The agent gets the build error, understands the constraint, and corrects itself with no human in the loop. The compiler is the reviewer that never sleeps and never loses its context.

I don't need to trust the AI. The architecture itself is the guardrail.
See the full delivery loop →

Honestly

What it costs, and what it doesn't do

It isn't free

Budget 2-3 days of Melos + package scaffolding on the first project, plus about a day to write the lint rules with their tests. After that it carries over from project to project.

On a throwaway MVP, it's overkill

A hackathon, a prototype you bin in two weeks: the 4 layers cost more than they return. It pays off when the code has to outlive its author.

It isn't a security framework

The dev-kit contains no Firestore rules, no encryption policy, no GDPR compliance. It reduces the number of chokepoints — the audit and the rules remain work in their own right.

In detail

The interactive explorer

The dev-kit's 9 views: packages, layers, data flow, atomic design, Riverpod injection, errors, naming, UI, services. Keyboard navigation (1-9, Esc), or Auto Tour. The explorer's interface is in French.

Open fullscreen ↗

Your app is starting to weigh

Existing codebase or from-scratch start: we look at what holding this architecture at your place would cost, and what it would spare you.