Clean Architecture & SDD
Scalable mobile applications separate concerns by implementing Clean Architecture combined with Schema-Driven Design (SDD). Clean Architecture isolates core business rules from external frameworks, UI code, and database drivers.
1. Clean Architecture Layers in Flutter
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRESENTATION LAYER β
β - Flutter Widgets, Pages, Components β
β - State Management (BLoCs, Cubits, ViewModels) β
βββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β (depends on Domain)
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DOMAIN LAYER β βββ PURE DART!
β - Entities & Value Objects β (Zero Flutter dependencies)
β - Use Cases / Interactors β
β - Repository Abstract Interfaces β
βββββββββββββββββββββββββββββ²βββββββββββββββββββββββββββββ
β (implements Interfaces)
βββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββ
β DATA LAYER β
β - Data Models (fromJson / toJson) β
β - Remote Data Sources (Dio, REST, GraphQL) β
β - Local Data Sources (SQLite, Hive, SecureStorage) β
β - Repository Concrete Implementations β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ1. Domain Layer (Pure Business Logic)
- Entities: Core business objects containing business logic rules.
- Use Cases (Interactors): Single-responsibility business actions (e.g.
LoginUserUseCase,CalculateCartTotalUseCase). - Repository Interfaces: Abstract contracts defining required data operations without knowing how or where data is stored.
- Rule: Must be pure Dart with 0 dependencies on Flutter UI (
package:flutter) or external data frameworks!
2. Data Layer (Infrastructure & Persistence)
- Data Models: DTOs (Data Transfer Objects) handling JSON serialization and schema mapping. Converts raw JSON to Domain Entities (
model.toEntity()). - Data Sources: Low-level HTTP clients (
Dio) or database engines (sqflite,Hive). - Repository Implementations: Implements the abstract repository interfaces from the Domain layer, orchestrating local vs remote caching strategies.
3. Presentation Layer (UI & State)
- Widgets: UI elements rendering state.
- State Managers: BLoC/Notifier converting Use Case execution into UI state states.
2. Dependency Inversion Principle (DIP) & GetIt
Under DIP, high-level business modules (Domain Use Cases) do NOT depend on low-level infrastructure modules (Data Sources). Both depend on abstractions (Repository Interfaces).
Service Location with GetIt
import 'package:get_it/get_it.dart';
final sl = GetIt.instance;
void setupDependencyInjection() {
// Data Sources
sl.registerLazySingleton<UserRemoteDataSource>(() => UserRemoteDataSourceImpl(sl()));
// Repositories (Bind Abstract Contract -> Concrete Implementation)
sl.registerLazySingleton<UserRepository>(() => UserRepositoryImpl(remoteDataSource: sl()));
// Use Cases
sl.registerLazySingleton(() => GetUserProfileUseCase(sl()));
// BLoCs / ViewModels
sl.registerFactory(() => ProfileBloc(getUserProfileUseCase: sl()));
}3. Schema-Driven Design (SDD) / Server-Driven UI
Schema-Driven Design (SDD) delegates UI layout and component structure to backend-supplied JSON schemas.
SDD Mechanics in Flutter
- Backend sends a JSON payload describing UI component hierarchies (e.g.
type: "carousel",children: [...]). - Flutter application parses schema into a dynamic registry of widget builders.
- Benefit: Enables instant UI updates, layout experiments, and A/B testing without submitting new app builds to the App Store or Google Play!
Widget buildWidgetFromSchema(Map<String, dynamic> json) {
switch (json['type']) {
case 'text':
return Text(json['content'] ?? '');
case 'button':
return ElevatedButton(
onPressed: () => handleAction(json['action']),
child: Text(json['label']),
);
case 'container':
return Container(
padding: EdgeInsets.all(json['padding']?.toDouble() ?? 0.0),
child: buildWidgetFromSchema(json['child']),
);
default:
return SizedBox.shrink();
}
}4. Trade-offs & Production Considerations
- Architecture Overhead for Small Apps: Clean Architecture creates many small files (Entities, Models, Repositories, Interfaces, Use Cases). For simple CRUD apps, this abstraction overhead can slow velocity. Use Clean Architecture for enterprise codebases with multiple developers.
- Mapping Overhead: Converting
JSON Map -> Data Model -> Domain Entity -> UI ViewModelcreates object transformation overhead. Ensure mapping extension functions (model.toEntity()) are lightweight.