# Architecture UnionFlow Mobile

Document de référence de l'architecture logicielle de l'application UnionFlow Mobile.

**Version**: 1.0.0
**Date**: 2026-03-15
**Pattern**: Clean Architecture + BLoC

---

## Table des Matières

1. [Vue d'Ensemble](#vue-densemble)
2. [Clean Architecture](#clean-architecture)
3. [BLoC Pattern](#bloc-pattern)
4. [Structure des Dossiers](#structure-des-dossiers)
5. [Couches Applicatives](#couches-applicatives)
6. [Flux de Données](#flux-de-données)
7. [Injection de Dépendances](#injection-de-dépendances)
8. [Gestion d'État](#gestion-détat)
9. [Communication Réseau](#communication-réseau)
10. [Cache Strategy](#cache-strategy)
11. [Authentification](#authentification)
12. [WebSocket Temps Réel](#websocket-temps-réel)
13. [Bonnes Pratiques](#bonnes-pratiques)

---

## Vue d'Ensemble

UnionFlow Mobile est construit selon les principes de **Clean Architecture** de Robert C. Martin, couplée au pattern **BLoC (Business Logic Component)** pour la gestion d'état.

### Objectifs Architecturaux

✅ **Séparation des préoccupations** - Chaque couche a une responsabilité unique
✅ **Testabilité** - Code découplé et facilement testable
✅ **Maintenabilité** - Structure claire et cohérente
✅ **Scalabilité** - Ajout facile de nouvelles fonctionnalités
✅ **Indépendance du framework** - Business logic indépendante de Flutter

---

## Clean Architecture

### Les 3 Couches Principales

```
┌─────────────────────────────────────────────────────┐
│                  PRESENTATION                       │  ← UI, BLoC, Pages, Widgets
│  (Flutter Widgets, BLoC, Events, States)            │
├─────────────────────────────────────────────────────┤
│                    DOMAIN                           │  ← Business Logic
│     (Entities, Use Cases, Repository Interfaces)    │
├─────────────────────────────────────────────────────┤
│                     DATA                            │  ← Data Sources
│  (Models, Repositories Impl, API, Cache, DB)        │
└─────────────────────────────────────────────────────┘
```

### Règles de Dépendance

**Règle d'or**: Les dépendances pointent toujours **vers l'intérieur**.

- ✅ Presentation → Domain → Data
- ❌ Data → Domain (INTERDIT)
- ❌ Domain → Presentation (INTERDIT)

**Exemple**:
```dart
// ✅ BON: Presentation dépend de Domain
class DashboardBloc {
  final GetDashboardData useCase; // Use case du Domain
}

// ✅ BON: Data implémente interface du Domain
class DashboardRepositoryImpl implements DashboardRepository {
  // Repository du Domain
}

// ❌ MAUVAIS: Domain dépend de Data
class GetDashboardData {
  final DashboardRemoteDataSource dataSource; // Data layer - INTERDIT
}
```

---

## BLoC Pattern

### Principe

**BLoC** = Business Logic Component

Le BLoC est un pattern de gestion d'état qui:
- Sépare la logique métier de l'UI
- Utilise des **Events** (entrées) et des **States** (sorties)
- Communication via **Streams** (reactive programming)

### Schéma de Flux

```
┌─────────┐        Event         ┌──────┐        State        ┌────────┐
│  Widget │ ──────────────────> │ BLoC │ ──────────────────> │ Widget │
└─────────┘                      └──────┘                      └────────┘
     │                              │
     │                              ├─> Use Cases (Domain)
     │                              ├─> Repository (Domain)
     │                              └─> Data Sources (Data)
     │
     └─────────────────────────────────────────────────────────┘
                     (BlocBuilder reconstruit l'UI)
```

### Composants d'un BLoC

#### 1. Events (Entrées)

```dart
// features/dashboard/presentation/bloc/dashboard_event.dart
abstract class DashboardEvent extends Equatable {}

class LoadDashboardData extends DashboardEvent {
  @override
  List<Object?> get props => [];
}

class RefreshDashboard extends DashboardEvent {
  @override
  List<Object?> get props => [];
}
```

#### 2. States (Sorties)

```dart
// features/dashboard/presentation/bloc/dashboard_state.dart
abstract class DashboardState extends Equatable {}

class DashboardInitial extends DashboardState {
  @override
  List<Object?> get props => [];
}

class DashboardLoading extends DashboardState {
  @override
  List<Object?> get props => [];
}

class DashboardLoaded extends DashboardState {
  final DashboardEntity dashboard;

  DashboardLoaded(this.dashboard);

  @override
  List<Object?> get props => [dashboard];
}

class DashboardError extends DashboardState {
  final String message;

  DashboardError(this.message);

  @override
  List<Object?> get props => [message];
}
```

#### 3. BLoC (Logique)

```dart
// features/dashboard/presentation/bloc/dashboard_bloc.dart
class DashboardBloc extends Bloc<DashboardEvent, DashboardState> {
  final GetDashboardData getDashboardData;

  DashboardBloc({required this.getDashboardData}) : super(DashboardInitial()) {
    on<LoadDashboardData>(_onLoadDashboard);
    on<RefreshDashboard>(_onRefreshDashboard);
  }

  Future<void> _onLoadDashboard(
    LoadDashboardData event,
    Emitter<DashboardState> emit,
  ) async {
    emit(DashboardLoading());

    final result = await getDashboardData();

    result.fold(
      (failure) => emit(DashboardError(failure.message)),
      (dashboard) => emit(DashboardLoaded(dashboard)),
    );
  }

  Future<void> _onRefreshDashboard(
    RefreshDashboard event,
    Emitter<DashboardState> emit,
  ) async {
    // Similar logic with cache invalidation
  }
}
```

#### 4. UI (Consommation)

```dart
// features/dashboard/presentation/pages/dashboard_page.dart
class DashboardPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => getIt<DashboardBloc>()..add(LoadDashboardData()),
      child: BlocBuilder<DashboardBloc, DashboardState>(
        builder: (context, state) {
          if (state is DashboardLoading) {
            return CircularProgressIndicator();
          } else if (state is DashboardLoaded) {
            return _buildDashboard(state.dashboard);
          } else if (state is DashboardError) {
            return ErrorWidget(state.message);
          }
          return SizedBox.shrink();
        },
      ),
    );
  }
}
```

---

## Structure des Dossiers

```
lib/
├── core/                           # Code transversal
│   ├── cache/
│   │   ├── cache_service.dart      # Service cache TTL
│   │   └── cached_datasource_decorator.dart
│   ├── config/
│   │   └── environment.dart        # Configuration ENV
│   ├── constants/
│   │   └── app_constants.dart      # Constantes app
│   ├── di/
│   │   ├── injection_container.dart # Setup DI
│   │   ├── injection.dart          # @module annotations
│   │   └── register_module.dart
│   ├── navigation/
│   │   ├── app_router.dart         # go_router config
│   │   └── main_navigation_layout.dart
│   ├── network/
│   │   ├── api_client.dart         # Dio setup
│   │   └── network_info.dart       # Connectivity
│   ├── storage/
│   │   └── dashboard_cache_manager.dart
│   └── utils/
│       └── logger.dart             # AppLogger central
│
├── features/                       # Modules métier
│   ├── authentication/
│   │   ├── data/
│   │   │   ├── datasources/
│   │   │   │   ├── keycloak_auth_service.dart
│   │   │   │   ├── keycloak_webview_auth_service.dart
│   │   │   │   └── permission_engine.dart
│   │   │   ├── models/
│   │   │   │   ├── user.dart
│   │   │   │   └── user_role.dart
│   │   │   └── repositories/
│   │   │       └── auth_repository_impl.dart
│   │   ├── domain/
│   │   │   ├── entities/
│   │   │   │   └── user_entity.dart
│   │   │   ├── repositories/
│   │   │   │   └── auth_repository.dart
│   │   │   └── usecases/
│   │   │       ├── login_user.dart
│   │   │       ├── logout_user.dart
│   │   │       └── get_current_user.dart
│   │   └── presentation/
│   │       ├── bloc/
│   │       │   ├── auth_bloc.dart
│   │       │   ├── auth_event.dart
│   │       │   └── auth_state.dart
│   │       └── pages/
│   │           └── login_page.dart
│   │
│   ├── dashboard/                  # Feature Dashboard
│   │   ├── data/
│   │   │   ├── datasources/
│   │   │   │   └── dashboard_remote_datasource.dart
│   │   │   ├── models/
│   │   │   │   ├── dashboard_stats_model.dart
│   │   │   │   └── dashboard_stats_model.g.dart
│   │   │   ├── repositories/
│   │   │   │   └── dashboard_repository_impl.dart
│   │   │   └── services/
│   │   │       ├── dashboard_offline_service.dart
│   │   │       └── dashboard_performance_monitor.dart
│   │   ├── domain/
│   │   │   ├── entities/
│   │   │   │   └── dashboard_entity.dart
│   │   │   ├── repositories/
│   │   │   │   └── dashboard_repository.dart
│   │   │   └── usecases/
│   │   │       └── get_dashboard_data.dart
│   │   └── presentation/
│   │       ├── bloc/
│   │       │   ├── dashboard_bloc.dart
│   │       │   ├── dashboard_event.dart
│   │       │   └── dashboard_state.dart
│   │       ├── pages/
│   │       │   ├── connected_dashboard_page.dart
│   │       │   └── role_dashboards/
│   │       │       ├── super_admin_dashboard.dart
│   │       │       ├── org_admin_dashboard.dart
│   │       │       ├── active_member_dashboard.dart
│   │       │       └── ...
│   │       └── widgets/
│   │           ├── dashboard_stat_card.dart
│   │           └── dashboard_chart.dart
│   │
│   ├── members/                    # Feature Membres
│   ├── contributions/              # Feature Cotisations
│   ├── events/                     # Feature Événements
│   ├── solidarity/                 # Feature Solidarité
│   ├── organizations/              # Feature Organisations
│   ├── notifications/              # Feature Notifications
│   ├── profile/                    # Feature Profil
│   ├── reports/                    # Feature Rapports
│   └── admin/                      # Feature Administration
│
├── shared/                         # Composants UI partagés
│   ├── design_system/
│   │   ├── components/
│   │   │   ├── buttons/
│   │   │   │   ├── uf_primary_button.dart
│   │   │   │   └── uf_secondary_button.dart
│   │   │   ├── cards/
│   │   │   │   ├── uf_card.dart
│   │   │   │   ├── uf_stat_card.dart
│   │   │   │   └── uf_metric_card.dart
│   │   │   ├── inputs/
│   │   │   └── uf_app_bar.dart
│   │   └── theme/
│   │       └── app_theme_sophisticated.dart
│   └── widgets/
│       ├── confirmation_dialog.dart
│       └── file_upload_widget.dart
│
└── main.dart                       # Point d'entrée
```

---

## Couches Applicatives

### 1. Presentation Layer (UI)

**Responsabilités**:
- Afficher l'interface utilisateur
- Écouter les interactions utilisateur
- Envoyer des **Events** au BLoC
- Observer les **States** du BLoC
- Déclencher la navigation

**Fichiers**:
- `bloc/` - Gestion d'état (Events, States, BLoC)
- `pages/` - Écrans de l'app
- `widgets/` - Composants UI réutilisables

**Règles**:
- ❌ Pas de logique métier dans les widgets
- ❌ Pas d'appels directs aux repositories ou data sources
- ✅ Communication uniquement via BLoC
- ✅ Widgets stateless autant que possible

### 2. Domain Layer (Business Logic)

**Responsabilités**:
- Définir les **Entities** (objets métier purs)
- Définir les **Use Cases** (actions métier)
- Définir les **Repository Interfaces** (contrats)

**Fichiers**:
- `entities/` - Objets métier purs (sans annotation)
- `usecases/` - Actions métier atomiques
- `repositories/` - Interfaces (abstractions)

**Règles**:
- ✅ Code 100% Dart pur (pas de dépendance Flutter)
- ✅ Facilement testable unitairement
- ✅ Indépendant des frameworks externes
- ❌ Pas de dépendance vers Data ou Presentation

**Exemple Use Case**:
```dart
// domain/usecases/get_dashboard_data.dart
class GetDashboardData {
  final DashboardRepository repository;

  GetDashboardData(this.repository);

  Future<Either<Failure, DashboardEntity>> call() async {
    return await repository.getDashboardData();
  }
}
```

### 3. Data Layer (Data Sources)

**Responsabilités**:
- Implémenter les **Repository Interfaces** du Domain
- Gérer les sources de données (API, Cache, DB)
- Mapper les **Models** vers les **Entities**

**Fichiers**:
- `datasources/` - Services API, Cache, DB
- `models/` - DTOs avec serialization JSON
- `repositories/` - Implémentations des interfaces

**Règles**:
- ✅ Utilise `json_annotation` pour serialization
- ✅ Gère les erreurs réseau et parsing
- ✅ Implémente le cache strategy
- ❌ Pas d'exposition des Models hors de Data layer

**Exemple Repository**:
```dart
// data/repositories/dashboard_repository_impl.dart
class DashboardRepositoryImpl implements DashboardRepository {
  final DashboardRemoteDataSource remoteDataSource;
  final CacheService cacheService;

  DashboardRepositoryImpl({
    required this.remoteDataSource,
    required this.cacheService,
  });

  @override
  Future<Either<Failure, DashboardEntity>> getDashboardData() async {
    try {
      // 1. Check cache
      final cached = cacheService.get<DashboardStatsModel>('dashboard_stats');
      if (cached != null) {
        return Right(cached.toEntity());
      }

      // 2. Fetch from API
      final model = await remoteDataSource.getDashboardStats();

      // 3. Cache result
      await cacheService.set('dashboard_stats', model);

      // 4. Map to Entity
      return Right(model.toEntity());
    } on DioException catch (e) {
      return Left(NetworkFailure(e.message ?? 'Network error'));
    } catch (e) {
      return Left(UnknownFailure(e.toString()));
    }
  }
}
```

---

## Flux de Données

### Flux Complet (Exemple Dashboard)

```
1. USER ACTION
   ↓
2. Widget.onPressed → context.read<DashboardBloc>().add(LoadDashboardData())
   ↓
3. DashboardBloc reçoit l'event
   ↓
4. Bloc appelle Use Case: getDashboardData()
   ↓
5. Use Case appelle Repository: repository.getDashboardData()
   ↓
6. Repository vérifie le cache (CacheService)
   ├─> Cache HIT: retourne Entity depuis cache
   └─> Cache MISS: appelle RemoteDataSource
       ↓
7. RemoteDataSource fait l'appel API (Dio)
   ↓
8. API retourne JSON
   ↓
9. JSON désérialisé en Model (@JsonSerializable)
   ↓
10. Model mappé en Entity (model.toEntity())
   ↓
11. Repository met en cache (cacheService.set)
   ↓
12. Repository retourne Either<Failure, Entity>
   ↓
13. Use Case retourne le résultat au Bloc
   ↓
14. Bloc émet un nouveau State: DashboardLoaded(entity)
   ↓
15. BlocBuilder reconstruit l'UI avec les nouvelles données
```

---

## Injection de Dépendances

### Get It + Injectable

```dart
// core/di/injection_container.dart
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'injection_container.config.dart';

final getIt = GetIt.instance;

@InjectableInit()
Future<void> configureDependencies() async {
  await getIt.init();
}
```

### Annotations

```dart
// @singleton - Instance unique partagée
@singleton
class CacheService { ... }

// @lazySingleton - Instance unique créée à la demande
@lazySingleton
class KeycloakAuthService { ... }

// @injectable - Nouvelle instance à chaque injection
@injectable
class DashboardBloc {
  final GetDashboardData getDashboardData;

  DashboardBloc({required this.getDashboardData});
}
```

### Enregistrement Manuel

```dart
// core/di/register_module.dart
@module
abstract class RegisterModule {
  @lazySingleton
  Dio get dio => Dio(
    BaseOptions(
      baseUrl: AppConfig.apiBaseUrl,
      connectTimeout: const Duration(seconds: 15),
      receiveTimeout: const Duration(seconds: 15),
    ),
  );

  @lazySingleton
  FlutterSecureStorage get secureStorage => const FlutterSecureStorage(
    aOptions: AndroidOptions(encryptedSharedPreferences: true),
    iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock_this_device),
  );

  @preResolve
  Future<SharedPreferences> get sharedPreferences => SharedPreferences.getInstance();
}
```

---

## Gestion d'État

### BLoC Lifecycle

```
┌─────────────┐
│   Initial   │  ← État initial
└──────┬──────┘
       │
       ├──> Event reçu
       │
┌──────▼──────┐
│   Loading   │  ← Chargement en cours
└──────┬──────┘
       │
       ├──> Succès
       │
┌──────▼──────┐
│   Loaded    │  ← Données chargées
└──────┬──────┘
       │
       ├──> Erreur
       │
┌──────▼──────┐
│    Error    │  ← Erreur survenue
└─────────────┘
```

### BlocListener vs BlocBuilder

**BlocBuilder** - Reconstruction UI:
```dart
BlocBuilder<DashboardBloc, DashboardState>(
  builder: (context, state) {
    if (state is DashboardLoaded) {
      return DashboardView(data: state.dashboard);
    }
    return LoadingWidget();
  },
)
```

**BlocListener** - Side effects (navigation, snackbar, etc.):
```dart
BlocListener<AuthBloc, AuthState>(
  listener: (context, state) {
    if (state is AuthSuccess) {
      context.go('/dashboard');
    } else if (state is AuthError) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.message)),
      );
    }
  },
  child: LoginForm(),
)
```

**BlocConsumer** - Combinaison des deux:
```dart
BlocConsumer<DashboardBloc, DashboardState>(
  listener: (context, state) {
    // Side effects
  },
  builder: (context, state) {
    // UI rebuild
  },
)
```

---

## Communication Réseau

### Dio Client

**Configuration** (`core/network/api_client.dart`):

```dart
final dio = Dio(
  BaseOptions(
    baseUrl: AppConfig.apiBaseUrl,
    connectTimeout: const Duration(seconds: 15),
    receiveTimeout: const Duration(seconds: 15),
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
  ),
);

// Interceptor pour ajouter le token JWT
dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) async {
    final token = await getIt<KeycloakAuthService>().getValidToken();
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    return handler.next(options);
  },
));

// Interceptor pour gérer les erreurs 401 (token expiré)
dio.interceptors.add(InterceptorsWrapper(
  onError: (error, handler) async {
    if (error.response?.statusCode == 401) {
      final newToken = await getIt<KeycloakAuthService>().refreshToken();
      if (newToken != null) {
        // Retry request with new token
        error.requestOptions.headers['Authorization'] = 'Bearer $newToken';
        return handler.resolve(await dio.fetch(error.requestOptions));
      }
    }
    return handler.next(error);
  },
));
```

### Error Handling

```dart
try {
  final response = await dio.get('/api/dashboard/stats');
  return DashboardStatsModel.fromJson(response.data);
} on DioException catch (e) {
  if (e.type == DioExceptionType.connectionTimeout) {
    throw NetworkException('Connection timeout');
  } else if (e.type == DioExceptionType.receiveTimeout) {
    throw NetworkException('Receive timeout');
  } else if (e.response?.statusCode == 400) {
    throw ValidationException(e.response?.data['message'] ?? 'Validation error');
  } else if (e.response?.statusCode == 500) {
    throw ServerException('Server error');
  } else {
    throw NetworkException('Unknown network error');
  }
}
```

---

## Cache Strategy

### Multi-Level Cache (L1 + L2)

**L1 Cache** - Mémoire (Instant):
```dart
static final Map<String, dynamic> _memoryCache = {};
```

**L2 Cache** - Disque (Persist après redémarrage):
```dart
final SharedPreferences _prefs;
```

### Cache Service avec TTL

**TTL configurables** (`core/cache/cache_service.dart`):

```dart
static const Map<String, int> _cacheTTL = {
  'dashboard_stats': 300,      // 5 minutes
  'parametres_lcb_ft': 1800,   // 30 minutes
  'user_profile': 600,         // 10 minutes
  'organisations': 3600,       // 1 heure
  'notifications_count': 60,   // 1 minute
};
```

### Cache-Aside Pattern

**Decorator** (`core/cache/cached_datasource_decorator.dart`):

```dart
final stats = await decorator.withCache(
  cacheKey: 'dashboard_stats_${userId}',
  fetchFunction: () => api.getDashboardStats(),
);
```

**Flux**:
1. Vérifier le cache
2. Si HIT: retourner valeur du cache
3. Si MISS: appeler l'API
4. Mettre en cache le résultat
5. Retourner le résultat

---

## Authentification

### OAuth 2.0 / OIDC avec Keycloak

**Flux**:

```
1. User clique "Se connecter"
   ↓
2. KeycloakAuthService.login(username, password)
   ↓
3. POST /realms/unionflow/protocol/openid-connect/token
   ↓
4. Keycloak retourne: access_token, refresh_token, id_token
   ↓
5. Tokens stockés dans FlutterSecureStorage (chiffrés)
   ↓
6. JWT décodé pour extraire user info + roles
   ↓
7. User Entity créé avec primaryRole (RBAC)
   ↓
8. AuthBloc émet AuthSuccess(user)
   ↓
9. Navigation vers Dashboard
```

### Refresh Token Automatique

**Verrouillage global** pour éviter appels concurrents:

```dart
static Future<String?>? _refreshFuture;

Future<String?> refreshToken() async {
  if (_refreshFuture != null) {
    return await _refreshFuture;
  }

  _refreshFuture = _performRefresh();
  try {
    return await _refreshFuture;
  } finally {
    _refreshFuture = null;
  }
}
```

**Auto-refresh** dans `getCurrentUser()`:

```dart
if (JwtDecoder.isExpired(token)) {
  token = await refreshToken();
  if (token == null) return null;
}
```

---

## WebSocket Temps Réel

### Architecture Event-Driven

**Backend** (Kafka + WebSocket):
- Kafka Topics: `unionflow.dashboard.stats`, `unionflow.notifications.user`, etc.
- Consommateur Kafka émet events sur WebSocket (`/ws/dashboard`)

**Mobile** (WebSocket Client):

```dart
@singleton
class WebSocketService {
  late WebSocketChannel _channel;
  final _eventController = StreamController<dynamic>.broadcast();

  Stream<dynamic> get eventStream => _eventController.stream;

  void connect() {
    _channel = WebSocketChannel.connect(
      Uri.parse(AppConfig.wsDashboardUrl),
    );

    _channel.stream.listen((message) {
      final event = jsonDecode(message);
      _eventController.add(event);
    });
  }

  void disconnect() {
    _channel.sink.close();
    _eventController.close();
  }
}
```

**Intégration BLoC**:

```dart
class DashboardBloc extends Bloc<DashboardEvent, DashboardState> {
  final WebSocketService webSocketService;
  StreamSubscription? _wsSubscription;

  DashboardBloc({required this.webSocketService}) : super(DashboardInitial()) {
    _wsSubscription = webSocketService.eventStream.listen((event) {
      if (event['type'] == 'DASHBOARD_STATS_UPDATE') {
        add(RefreshDashboardFromWebSocket(event['data']));
      }
    });
  }

  @override
  Future<void> close() {
    _wsSubscription?.cancel();
    return super.close();
  }
}
```

---

## Bonnes Pratiques

### 1. Naming Conventions

**Fichiers**:
- snake_case: `dashboard_bloc.dart`, `user_repository.dart`
- Suffixes: `_bloc.dart`, `_event.dart`, `_state.dart`, `_page.dart`, `_widget.dart`

**Classes**:
- PascalCase: `DashboardBloc`, `DashboardEvent`, `DashboardState`
- Suffixes: `Bloc`, `Event`, `State`, `Page`, `Widget`, `Repository`, `UseCase`

**Variables/Fonctions**:
- camelCase: `getDashboardData`, `currentUser`, `isLoading`

### 2. Code Organization

**Un fichier = Une responsabilité**:
- ✅ `dashboard_bloc.dart` contient seulement DashboardBloc
- ✅ `dashboard_event.dart` contient tous les events
- ✅ `dashboard_state.dart` contient tous les states

**Grouping**:
- Regrouper par feature (vertical slicing)
- Pas de dossiers `utils/` fourre-tout
- Chaque feature = mini-application autonome

### 3. Testing

**Tests Unitaires** (Domain):
```dart
test('GetDashboardData should return DashboardEntity on success', () async {
  when(mockRepository.getDashboardData()).thenAnswer(
    (_) async => Right(tDashboardEntity),
  );

  final result = await useCase();

  expect(result, Right(tDashboardEntity));
  verify(mockRepository.getDashboardData());
});
```

**Tests BLoC**:
```dart
blocTest<DashboardBloc, DashboardState>(
  'emits [Loading, Loaded] when LoadDashboardData succeeds',
  build: () => DashboardBloc(getDashboardData: mockGetDashboardData),
  act: (bloc) => bloc.add(LoadDashboardData()),
  expect: () => [
    DashboardLoading(),
    DashboardLoaded(tDashboard),
  ],
);
```

### 4. Error Handling

**Utiliser Either<L, R>** (package `dartz`):
```dart
Future<Either<Failure, Entity>> someMethod() async {
  try {
    final result = await api.call();
    return Right(result);
  } catch (e) {
    return Left(ServerFailure());
  }
}
```

**Failure Hierarchy**:
```dart
abstract class Failure extends Equatable {}

class ServerFailure extends Failure {}
class NetworkFailure extends Failure {}
class CacheFailure extends Failure {}
```

### 5. Performance

✅ **Const constructors** partout:
```dart
const Text('Hello'),
const SizedBox(height: 16),
const EdgeInsets.all(8),
```

✅ **ListView.builder** pour listes longues:
```dart
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ItemWidget(items[index]),
)
```

✅ **Debounce** pour recherche:
```dart
Timer? _debounce;

void _onSearchChanged(String query) {
  _debounce?.cancel();
  _debounce = Timer(const Duration(milliseconds: 300), () {
    // Call API
  });
}
```

---

## Conclusion

L'architecture UnionFlow Mobile est:

✅ **Modulaire** - Ajout facile de nouvelles features
✅ **Testable** - Code découplé et injectable
✅ **Maintenable** - Structure claire et cohérente
✅ **Scalable** - Supporte la croissance de l'application
✅ **Performante** - Cache multi-niveaux, lazy loading
✅ **Sécurisée** - Authentification robuste, storage chiffré

**Documentation associée**:
- [README.md](../README.md) - Guide d'installation
- [SECURITE_PRODUCTION.md](SECURITE_PRODUCTION.md) - Mesures de sécurité
- [OPTIMISATIONS_PERFORMANCE.md](OPTIMISATIONS_PERFORMANCE.md) - Optimisations
- [CONTRIBUTING.md](../CONTRIBUTING.md) - Guide de contribution

---

*Document maintenu par l'équipe UnionFlow Development Team*
