Folder structure: feature-first, layer-first and the hybrid
Folder structure is not architecture, but it is the visible part of it — the team looks at it every day. There are three options.
Layer-first — layers at the top level: lib/domain, lib/data, lib/presentation, with features inside each layer.
- Good: very simple in small and mid-size projects; the layer rule is right there in the tree.
- Bad: changing one feature means walking through three separate folders; in a 30-screen app the
presentationfolder becomes enormous.
Feature-first — features at the top level: lib/features/orders/{domain,data,presentation}, each feature carrying its own layers.
- Good: one folder is enough to work on a feature; deleting a feature or extracting it into a package is easy.
- Bad: shared code becomes a problem. Which feature does
AuthRepositorybelong to? When there is no answer,core/turns into the place where everything is dumped.
Hybrid — the official Flutter guide's choice: UI split by feature, data split by layer. The reasoning is simple and convincing: a view and a view model belong to one feature, while repositories and services do not — several features use them.
lib/
├── ui/ ← UI qatı: FEATURE-lərə görə
│ ├── core/
│ │ ├── ui/ ← paylaşılan widget-lər
│ │ └── themes/
│ └── <feature_adı>/
│ ├── view_models/
│ └── widgets/
├── domain/ ← modellər (entity)
│ └── models/
├── data/ ← data qatı: QAT üzrə
│ ├── repositories/
│ ├── services/
│ └── model/ ← API modelləri (DTO)
├── config/
├── routing/
├── utils/
├── main.dart
├── main_development.dart ← flavor-lar ayrı giriş nöqtələri
└── main_staging.dart
Diqqət: `/widgets` adlı qovluq yoxdur — paylaşılan widget-lər `ui/core/ui`
altındadır. Rəsmi tövsiyə budur: qovluq adı da qatı desin.The structure of the official case study (Compass) app — a real example of the hybrid approach.
| Criterion | Layer-first | Feature-first | Hybrid (official) |
|---|---|---|---|
| Working on one feature | Walking through three folders | One folder | One folder for the UI, shared data |
| A shared repository | Natural — `data/` is shared | Awkward — `core/` bloats | Natural — data is split by layer |
| Navigating 30+ screens | Heavy: an enormous `presentation/` | Comfortable | Comfortable |
| Extracting a feature into a package | Hard | Easy | Medium — the UI is easy, the data stays shared |
| Merge conflict risk | High: everyone in the same folders | Low | Low to medium |
| When to choose it | < 10 screens, single developer | A large project heading for modules | The default for most projects |
Beyond folders, two rules keep the structure readable.
1. Let the name state the layer. The official recommendation: a class name should announce its architectural role — HomeViewModel, UserRepository, ProductApiClient. Avoid names that collide with Flutter SDK types (Router, Route, State). And avoid catch-all suffixes — Manager, Helper, Util — they hide the layer and turn into the class where everything gets dumped.
2. Let `test/` mirror `lib/`. lib/data/repositories/order_repository_remote.dart → test/data/repositories/order_repository_remote_test.dart. Then "does this class have a test?" is answered at a glance.
A short note on barrel files (a file of exports such as orders.dart): in a small project they shorten imports, but a barrel across a layer boundary hides the dependency rule — whoever writes import 'package:app/domain.dart' cannot see what they pulled in. Avoiding barrels at layer boundaries is the safer default.
Practice. Write your project's folder structure into an ARCHITECTURE.md: a tree diagram, one line describing each folder, and a note on what does not go there. Then place five of your existing files into that tree.
Done means: the file is in the repo, the tree clearly shows domain, data and the UI part, and at least one folder carries a "X does not go here, because …" line. This ends up being the document you reach for most often in code review.
📚 Sources and documentation
- Case study: structure overviewofficialdocs.flutter.dev
The source of the tree above and the reasoning behind "UI by feature, data by layer".
- Architecture recommendations: namingofficialdocs.flutter.dev
Standardised naming conventions and the recommendation to use `ui/core/` instead of `/widgets`.
- The Compass app source (GitHub)officialgithub.com
Walking the structure in real code — seeing which file sits in which folder is the fastest way to learn it.
- Dart: package layout conventionsofficialdart.dev
The official meaning of `lib/`, `lib/src/`, `test/` and `tool/` — needed once you start splitting into modules.