A production-ready Flutter starter with Clean Architecture, BLoC, and functional error handling. Small enough to read in one sitting, structured enough to scale to a 100-screen app.
Distilled from patterns I've used on large production Flutter apps (movie booking, global eSIM, e-commerce) — rewritten from scratch as a clean, reusable template.
presentation domain data
┌─────────────────────┐ ┌───────────────┐ ┌──────────────────────┐
│ Page → Bloc │→│ UseCase │→│ Repository impl │
│ (states, events) │ │ Repository │ │ ├─ RemoteSource(Dio)│
│ │ │ (interface) │ │ └─ Model(fromJson) │
└─────────────────────┘ └───────────────┘ └──────────────────────┘
Flutter pure Dart packages/IO
The dependency rule: arrows point inward. domain imports nothing from
Flutter, Dio, or JSON — which is why every layer tests in isolation.
| Concern | Choice | Why |
|---|---|---|
| State management | flutter_bloc (sealed events/states) | Exhaustive switch in UI — a new state won't compile until handled |
| Errors | dartz Either<Failure, T> |
Failures are values; exceptions die at the repository boundary |
| DI | get_it composition root | One file (core/di/injector.dart) wires interfaces to impls |
| Networking | Dio with env-driven base URL | --dart-define=API_BASE_URL=..., no hardcoded URLs |
| Theming | Material 3 ColorScheme.fromSeed |
One seed color; light & dark stay in sync |
| Testing | bloc_test + mocktail | Bloc tests run in milliseconds with a mocked use case |
lib/
├── main.dart # tiny: init DI, runApp
├── core/
│ ├── di/injector.dart # composition root (get_it)
│ ├── error/failures.dart # sealed Failure values
│ ├── network/dio_client.dart # Dio factory + interceptor hook point
│ └── theme/app_theme.dart # M3 light/dark from one seed
└── features/users/ # copy this folder to add a feature
├── domain/ entities · repository interface · usecases
├── data/ models(fromJson) · remote source · repository impl
└── presentation/ bloc · pages · widgets
- Page dispatches
UsersRequested - Bloc calls
GetUsers()use case - Use case calls
UserRepository(interface) UserRepositoryImplcalls Dio source, catches exceptions →Either- Bloc folds
EitherintoUsersLoaded/UsersError - UI
switches over sealed states — no if-soup, no missed cases
git clone https://github.com/rai-ms/flutter-base my_app && cd my_app
flutter pub get
flutter run --dart-define=API_BASE_URL=https://your-api.com
flutter test # bloc tests, millisecondsCopy lib/features/users → rename → register its pieces in
core/di/injector.dart. Each feature is self-contained; deleting its folder
removes it completely.
MIT — use it for anything.