Most “Flutter architecture” advice is either a 40-file boilerplate template or a shrug. After a few years of shipping production apps, here’s the version I’d give my past self.
Start with layers, not folders
The folders don’t matter. The dependency direction does. Keep three layers and let dependencies point inward only:
- Presentation — widgets, state, navigation.
- Domain — entities and use-cases. No Flutter imports. Ever.
- Data — repositories, APIs, local storage.
A use-case is just a function. Resist the urge to make it a 200-line class:
class GetActiveRides { GetActiveRides(this._repo); final RideRepository _repo;
Future<List<Ride>> call() => _repo.activeRides();}Keep state boring
Whatever state management you use — Riverpod, Bloc, signals — the rule is the same: state is derived, effects are explicit. If you can’t tell where a side effect happens by reading the file, the abstraction is wrong.
final activeRidesProvider = FutureProvider.autoDispose((ref) { return ref.watch(getActiveRidesProvider)();});The smallest abstractions that pay off
- A
Result<T>type instead of throwing across layers. - A single
AppExceptionhierarchy mapped at the data boundary. - One place that turns API models into domain entities.
That’s most of it. Architecture is the set of decisions that are expensive to change later — spend your discipline there, and stay loose everywhere else.