Flutter Architecture Guide
Architecture: Feature-First Clean Architecture
This project follows a feature-first directory structure with Clean Architecture layers inside each feature.
lib/features/{feature}/
├── data/
│ ├── datasources/ # Remote (API) and local (cache/DB) sources
│ ├── models/ # DTOs with fromJson/toJson
│ └── repositories/ # Implements domain repository interfaces
├── domain/
│ ├── entities/ # Pure Dart domain objects (no JSON, no Flutter)
│ ├── repositories/ # Abstract interfaces
│ └── usecases/ # Single-responsibility business logic
└── presentation/
├── providers/ # Riverpod providers and notifiers
├── screens/ # Full-page widgets
└── widgets/ # Feature-local reusable widgets
Detailed patterns: see references/riverpod-patterns.md Clean Architecture detail: see references/clean-arch.md
Layer Rules (Dependency Direction)
presentation → domain ← data
presentationdepends ondomain(entities, use cases, repository interfaces)dataimplementsdomaininterfacesdomainhas zero Flutter or data-layer importspresentationnever imports fromdatadirectly
State Management: Riverpod
Preferred provider types by use case:
| Use Case | Provider Type |
|---|---|
| Synchronous computed value | Provider |
| Async data (read-only, no mutation) | FutureProvider / StreamProvider |
| Mutable async state | AsyncNotifierProvider |
| Mutable sync state | NotifierProvider |
| Single-use actions (not state) | ref.read(notifierProvider.notifier).method() |
Navigation
Use go_router for declarative routing:
- Define all routes in a single
router.dartfile - Use typed route classes when go_router codegen is available
- Pass only IDs through routes; load entities in the destination screen via providers
Error Handling
Use Result or Either type for repository return values:
// Domain layer
abstract class UserRepository {
Future<Result<User, AppError>> getUser(String id);
}
Do NOT throw exceptions across layer boundaries. Catch at the data layer, return failures.
When to Deviate
These are defaults, not laws. Deviating is fine when:
- A feature is truly trivial (single screen, no business logic) — can live in
presentation/only - Third-party package imposes a different structure
- Performance requires bypassing the domain layer for hot paths
Document deviations in a comment or ADR.