A production-ready Flutter starter kit implementing Clean Architecture with Riverpod, GoRouter, Dio, and best-in-class patterns for scalable cross-platform development. Built for teams who need architecture that scales beyond a prototype, enforces type-safety, and makes testing a first-class citizen.
The project strictly follows the Dependency Rule: Source code dependencies must point inward. The Domain layer knows nothing of Flutter, Dio, or Riverpod.
graph TD
UI[Presentation / UI] -->|Watches| Provider[Riverpod Provider]
Provider -->|Calls| UseCase[Domain UseCase]
UseCase -->|Calls| RepoInterface[Domain Repository Interface]
RepoInterface -.->|Implemented by| RepoImpl[Data Repository Impl]
RepoImpl -->|Calls| Datasource[Data Datasource]
Datasource -->|Uses| ApiClient[Dio ApiClient]
Datasource -->|Uses| Storage[SecureStorage/Cache]
##📁 Folder Structure
lib/
├── core/ # Shared infrastructure & utilities
│ ├── config/ # Environment & app configuration
│ ├── di/ # Dependency injection (Riverpod composition root)
│ ├── error/ # Failure hierarchy & error mapping
│ ├── network/ # Dio client, interceptors, API response models
│ ├── router/ # GoRouter configuration & route guards
│ ├── storage/ # Local & secure storage abstractions
│ ├── theme/ # Material 3 theme, colors, typography, spacing
│ ├── utils/ # Validators, loggers, extensions
│ └── widgets/ # Reusable UI components (AppButton, AppLoader, etc.)
├── features/ # Feature-first modules
│ └── auth/ # Authentication feature
│ ├── data/ # DTOs, datasources, repository implementations
│ ├── domain/ # Entities, repository interfaces, use cases
│ └── presentation/ # Riverpod providers, pages, widgets
└── main.dart # App entry point with flavor support
- Flutter 3.22+ / Dart 3.4+
- Android Studio / VS Code
git clone https://github.com/amjadlabdev/flutter_architecture_kit.git
cd flutter_architecture_kit
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs # Generate Freezed/Riverpod code
flutter run# Development (default)
flutter run --dart-define=APP_ENV=development
# Staging
flutter run --dart-define=APP_ENV=staging
# Production
flutter run --dart-define=APP_ENV=productionFollow this exact sequence to maintain the Dependency Rule:
-
Domain Layer (
features/{feature}/domain/):- Define
entities/(Pure Dart classes with Equatable). - Define
repositories/{feature}_repository.dart(Abstract class withEither<Failure, T>returns). - Define
usecases/(ImplementUseCase<Type, Params>interface).
- Define
-
Data Layer (
features/{feature}/data/):- Define
models/(Freezed DTOs withfromJson,toJson, andtoEntity). - Define
datasources/(Remote: ApiClient calls, Local: Storage calls). - Define
repositories/{feature}_repository_impl.dart(Implements Domain abstract class, maps Models to Entities).
- Define
-
Presentation Layer (
features/{feature}/presentation/):- Define
providers/(Riverpod AsyncNotifiers mapping UseCase results to State). - Define
pages/(Scaffold layouts). - Define
widgets/(Stateful/Stateless leaf components).
- Define
-
DI (
core/di/injection.dart):- Wire Datasources → Repository Implementation → Repository Interface.
-
Routing (
core/router/):- Add route constant to
app_routes.dart. - Add
GoRoutetoapp_router.dart.
- Add route constant to
##🧠 State Management: Why Riverpod?
Riverpod was chosen over BLoC and Provider for production apps because:
- Compile-time safety: Providers are declared globally, preventing
ProviderNotFoundException. - Testability: Providers can be overridden in
ProviderContainerwithout masking. - AsyncNotifier: Native support for
AsyncValue, eliminating loading/error boilerplate. - Code Generation:
@riverpodremoves the verboseNotifierProviderboilerplate.
Provider Hierarchy:
Provider: For dependency injection (Repositories, UseCases).NotifierProvider/@riverpod: For complex synchronous state.AsyncNotifierProvider/@riverpod: For async state (API calls) with built-in loading/error/copyWith.
We use dartz's Either<Failure, T> instead of try/catch at the Domain layer.
Why? Exceptions are invisible in function signatures. You don't know a function can fail until it crashes at runtime. Either forces the caller to handle both Left (failure) and Right (success) at compile time.
Flow:
ApiClientthrowsDioException.Repositorycatches it and maps it toLeft(NetworkFailure()).UseCasepasses theEitherup.Providerfolds theEitherintoAuthErrororAuthAuthenticatedstate.- UI handles
AuthErrorwith a SnackBar.
Declarative routing tied to Riverpod state prevents manual Navigator.push bugs.
- Auth Guard: Implemented via
redirectcallback readingauthProvider. - Deep Links: Configured in
GoRouterfor universal links. - Named Routes: Enforced via
AppRoutesconstants to prevent string-typo crashes.
Run tests:
flutter testMocking Strategy:
- Domain: Mock
Repositoriesto testUseCasesindependently. - Presentation: Override
ProvidersinProviderContainerto test state transitions. - Data: Mock
ApiClientto test mapping logic without HTTP calls.
- Fork the repository.
- Create a feature branch (
git checkout -b feature/amazing-feature). - Commit using Conventional Commits (
feat: add user profile). - Ensure
flutter analyzepasses with zero warnings. - Open a Pull Request.
Amjad Ali — Senior Software Engineer & Solution Architect
- 💻 GitHub
- ✉️ amjadalibrw1@gmail.com
Built with ❤️ and Clean Architecture principles.