Flutter Interview Handbook

Clean Architecture & SDD

100%

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

  1. Backend sends a JSON payload describing UI component hierarchies (e.g. type: "carousel", children: [...]).
  2. Flutter application parses schema into a dynamic registry of widget builders.
  3. 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 ViewModel creates object transformation overhead. Ensure mapping extension functions (model.toEntity()) are lightweight.