21 KiB
QUEUE-APP-API.md
Système de File d'Attente pour les Requêtes API Hors Ligne
🚀 État actuel de l'implémentation (29/08/2025)
✅ Phases complétées
- Phase 1 : Infrastructure de base - COMPLÉTÉ
- Phase 2 : Modification ApiService - COMPLÉTÉ
- Phase 3 : Intégration ConnectivityService - COMPLÉTÉ
- Phase 5 : Alertes utilisateur - COMPLÉTÉ
- Phase 6 : Adaptation des Repositories CRUD - COMPLÉTÉ (tous les 8 repositories)
⚠️ Décisions d'architecture importantes
- Pas d'IDs temporaires : La base de données MariaDB utilise des
int unsigned, donc pas de valeurs négatives possibles - Stratégie sans stockage local pour CREATE :
- CREATE offline → Requête en queue, pas de stockage Hive, message "Création en attente"
- UPDATE offline → Modification locale avec
isSynced: false+ requête en queue - DELETE offline → Suppression locale immédiate + requête en queue
- Pas d'expiration automatique : Les requêtes persistent indéfiniment jusqu'à leur envoi
- Ordre FIFO strict : Basé sur
createdAtpour garantir la cohérence
📦 Repositories adaptés (8/8)
| Repository | CREATE offline | UPDATE offline | DELETE offline | Notes |
|---|---|---|---|---|
| PassageRepository | ✅ Queue + Dialog + Pas de stockage | ✅ Stockage local + Flag | ✅ Suppression immédiate | Priorité haute terrain |
| ClientRepository | ✅ Queue + Dialog + Pas de stockage | ✅ Stockage local | ✅ Suppression immédiate | Adhérents terrain |
| MembreRepository | ✅ Queue + Dialog + Retourne null | ✅ Stockage local | ✅ Suppression immédiate | Gestion équipe |
| SectorRepository | ✅ Queue + Dialog + Status "queued" | ✅ Stockage local | ✅ Suppression + passages | Gère aussi les passages |
| UserRepository | N/A | ✅ Stockage avec isSynced=false | N/A | Profil utilisateur connecté uniquement |
| AmicaleRepository | ✅ Queue + Dialog + Pas de stockage | ✅ Stockage local | ✅ Suppression immédiate | Organisation |
| OperationRepository | ✅ Queue + Dialog + Pas de stockage | ✅ Stockage avec isSynced=false | ✅ Suppression + données liées | Campagnes |
📊 Réponses API en mode offline
Toutes les requêtes stockées en pending retournent :
{
"queued": true
}
🔄 Prochain travail
- Implémenter la gestion des conflits de synchronisation
- Ajouter la limite de queue (1000 requêtes max)
- Créer le système d'export des données en attente
- Ajouter un dialog de confirmation lors de la déconnexion avec données en attente
- Tester le système complet en mode avion
📋 Vue d'ensemble
Objectif
Créer un système robuste de gestion des requêtes API en mode hors ligne pour permettre à l'application de fonctionner sans connexion Internet et de synchroniser automatiquement les données dès que la connexion est rétablie.
Problématique actuelle
- Les requêtes API échouent immédiatement sans connexion
- Aucun mécanisme de file d'attente pour les requêtes en attente
- Les messages du chat sont perdus si envoyés hors ligne
- Les modifications CRUD ne sont pas persistées localement pour envoi ultérieur
Solution proposée
Modifier ApiService pour qu'il gère automatiquement la file d'attente : stockage dans Hive si hors ligne, envoi direct si en ligne, et traitement automatique de la queue au retour de connexion.
🏗️ Architecture
Flux de données
Application (UI/Repository)
↓
ApiService (modifié)
↓
[Si en ligne] → Envoi direct → API
↓
[Si hors ligne] → Stockage → Hive Box "pending_requests"
ConnectivityService (écoute en permanence)
↓
[Connexion rétablie] → Notifie ApiService
↓
ApiService.processPendingRequests() → API
Services impliqués
-
ApiService (à modifier)
- Vérifie la connectivité avant chaque requête
- Si online : envoi direct à l'API
- Si offline : stockage dans Hive Box
- Méthode
processPendingRequests()pour vider la queue - Retourne une réponse "pending" avec tempId si offline
-
ConnectivityService (à enrichir)
- Détecte les changements de connexion
- Appelle
ApiService.processPendingRequests()au retour - Vérifie périodiquement s'il y a des requêtes en attente
-
Hive Boxes (nouvelles)
pending_requests: File d'attente des requêtes (JAMAIS supprimée si non vide)temp_entities: Entités temporaires (messages temp_, etc.)
📊 Analyse des requêtes actuelles
1. Chat Service
- POST
/chat/rooms- Créer une conversation - POST
/chat/rooms/{roomId}/messages- Envoyer un message - GET
/chat/rooms- Obtenir les conversations - GET
/chat/rooms/{roomId}/messages- Obtenir les messages - DELETE
/chat/rooms/{roomId}- Supprimer une conversation
2. User Repository
- GET
/users- Liste des utilisateurs - GET
/users/{id}- Détail utilisateur - POST
/users- Créer utilisateur - PUT
/users/{id}- Modifier utilisateur - DELETE
/users/{id}- Supprimer utilisateur
3. Amicale Repository
- GET
/entites- Liste des amicales - PUT
/entites/{id}- Modifier amicale - POST
/entites/{id}/logo- Upload logo
4. Operations/Passages
- GET
/operations- Liste des opérations - POST
/passages- Créer un passage - PUT
/passages/{id}- Modifier un passage - GET
/operations/{id}/export/excel- Export Excel
5. Authentification
- POST
/login- Connexion - POST
/logout- Déconnexion
💾 Modèle de données pour la file d'attente
PendingRequest Model
@HiveType(typeId: 100)
class PendingRequest extends HiveObject {
@HiveField(0)
final String id; // UUID unique
@HiveField(1)
final String method; // POST, GET, PUT, DELETE
@HiveField(2)
final String path; // /chat/rooms/xxx/messages
@HiveField(3)
final Map<String, dynamic>? data; // Body de la requête
@HiveField(4)
final Map<String, dynamic>? queryParams; // Query parameters
@HiveField(5)
final DateTime createdAt; // Timestamp de création (ORDRE DE TRAITEMENT)
@HiveField(6)
final String? tempId; // ID temporaire associé (ex: temp_xxx)
@HiveField(7)
final String context; // chat, user, operation, etc.
@HiveField(8)
final int retryCount; // Nombre de tentatives
@HiveField(9)
final String? errorMessage; // Dernière erreur
@HiveField(10)
final Map<String, dynamic>? metadata; // Infos additionnelles
}
⚠️ IMPORTANT : Les requêtes DOIVENT être traitées dans l'ordre chronologique strict (FIFO - First In First Out) basé sur createdAt pour garantir la cohérence des données.
🎯 Cas d'usage spécifiques
1. Chat - Envoi de message hors ligne
Workflow actuel:
- Utilisateur tape un message
- Tentative d'envoi direct à l'API
- Si pas de connexion → Erreur
Nouveau workflow:
- Utilisateur tape un message
- Création d'un message temporaire avec ID
temp_xxx - Sauvegarde immédiate dans Hive (affichage instantané)
- Si en ligne → Envoi API → Remplacement par message réel
- Si hors ligne → Ajout à la queue → Message affiché en italique/grisé
- Quand connexion revenue → Traitement de la queue → Mise à jour avec ID réel
Indicateurs visuels:
- Message en attente : Texte italique + icône horloge
- Message en cours d'envoi : Spinner
- Message envoyé : Texte normal
- Message en erreur : Texte rouge + icône erreur + option retry
2. CRUD Utilisateurs/Amicales
Création hors ligne:
- Génération d'un ID temporaire
temp_user_xxx - Sauvegarde locale dans Hive
- Affichage avec badge "En attente de synchronisation"
- Synchronisation automatique au retour de connexion
Modification hors ligne:
- Sauvegarde des modifications locales
- Marquage de l'entité comme "modified_offline"
- Affichage d'un indicateur de synchronisation en attente
- Gestion des conflits si modifié par ailleurs
Suppression hors ligne:
- Marquage local comme "deleted"
- Masquage de l'UI
- Envoi de la suppression au retour de connexion
3. Passages terrain
Enregistrement de passage hors ligne:
- Sauvegarde locale complète (coordonnées GPS, timestamp, etc.)
- Queue avec priorité HAUTE
- Synchronisation prioritaire au retour de connexion
- Conservation des données même après envoi (backup)
🔄 Gestion de la synchronisation
Ordre de traitement
FIFO strict (First In First Out) : Les requêtes sont TOUJOURS traitées dans l'ordre chronologique de leur création (createdAt), sans exception. Ceci garantit :
- Les messages de chat arrivent dans le bon ordre
- Une création précède toujours sa modification
- Une modification précède toujours sa suppression
- La cohérence des données est maintenue
Stratégie de retry
- 1ère tentative : Immédiate
- 2ème tentative : Après 30 secondes
- 3ème tentative : Après 2 minutes
- 4ème+ tentative : Toutes les 5 minutes
Abandon après 24hMODIFIÉ: Pas d'expiration automatique - requêtes persistent indéfiniment
Gestion des conflits
- Messages chat : Pas de conflit (append-only)
- CRUD : Dernière modification gagne + log des conflits
- Passages : Pas de conflit (chaque passage est unique)
🛠️ Plan d'implémentation
Phase 1 : Infrastructure (Jour 1) ✅ COMPLÉTÉ
- Créer le modèle
PendingRequest - Créer la Hive Box
pending_requestsavec protection - Enrichir
ConnectivityServiceavec gestion de queue - Ajouter les indicateurs UI d'alerte
Phase 2 : Modification ApiService (Jour 1-2) ✅ COMPLÉTÉ
- Ajouter vérification connectivité dans chaque méthode
- Implémenter le stockage dans Hive si offline
- Créer méthode
processPendingRequests() - Gérer les réponses "pending" avec tempId
Phase 3 : Traitement de la queue (Jour 2) ✅ COMPLÉTÉ
- Implémenter le processeur de queue FIFO
- Garantir l'ordre chronologique strict
- Implémenter les retry avec backoff
- Gérer les erreurs et abandons
Phase 4 : Chat (Jour 3)
- Adapter
ChatServicepour messages temporaires - Implémenter l'UI pour messages en attente
- Gérer le remplacement temp_ → ID réel
- Tester envoi/réception hors ligne
Phase 5 : CRUD (Jour 4) ✅ COMPLÉTÉ
- Adapter PassageRepository (COMPLÉTÉ)
- Adapter les autres repositories (Client, Membre, Sector, User, Amicale, Operation) - TOUS COMPLÉTÉS
Gérer les IDs temporairesMODIFIÉ: Pas d'IDs temporaires (contrainte DB unsigned int)- Implémenter les indicateurs UI
- Gérer les conflits de synchronisation
Phase 6 : Tests et optimisation (Jour 5)
- Tests unitaires du QueueService
- Tests d'intégration
- Tests de scénarios hors ligne/en ligne
- Optimisation des performances
🎨 UI/UX Guidelines
⚠️ ALERTES UTILISATEUR OBLIGATOIRES
Mode hors ligne détecté
- Banner permanent en haut : Fond orange avec texte "⚠️ Mode hors ligne - X modifications en attente"
- Badge rouge sur l'app bar : Nombre de requêtes non synchronisées
- Vibration/Son : Alerte immédiate lors de la perte de connexion
Tentative de déconnexion avec données en attente
- Dialog bloquant :
⚠️ ATTENTION Vous avez X modifications non synchronisées. Si vous vous déconnectez maintenant, ces données seront PERDUES : - X messages non envoyés - X modifications d'utilisateurs - X passages terrain [Annuler] [Se reconnecter d'abord] [Forcer déconnexion*] * Les données non synchronisées seront perdues
Fermeture de l'app avec données en attente
- Notification système : "GeoSector a des données non synchronisées"
- Dialog de confirmation si tentative de fermeture
Indicateurs visuels globaux
- Badge sur l'app bar : Nombre de requêtes en attente (rouge pulsant)
- Snackbar : Notification quand connexion perdue/retrouvée
- Pull to refresh : Force la synchronisation manuelle
- Bouton "Synchroniser" : Visible uniquement si données en attente
États des éléments
- En attente : Opacity 0.7 + icône horloge + texte italique
- En cours de sync : Shimmer effect ou spinner
- Erreur de sync : Bordure rouge + icône erreur
- Synchronisé : État normal
Messages utilisateur
- "⚠️ Pas de connexion - X actions en attente de synchronisation"
- "⚠️ Reconnectez-vous pour envoyer vos modifications"
- "✅ Connexion rétablie - Synchronisation en cours..."
- "✅ Toutes les modifications ont été synchronisées"
- "❌ Erreur de synchronisation - Toucher pour réessayer"
🔐 Considérations de sécurité
⚠️ PROTECTION CRITIQUE DE LA HIVE BOX
SPLASH PAGE - ATTENTION :
- La
splash_page.dartqui réinitialise les boxes au démarrage DOIT :- Vérifier si
pending_requestsbox existe et n'est pas vide - Si elle contient des données → NE PAS LA SUPPRIMER
- Afficher un avertissement à l'utilisateur
- Déclencher immédiatement le traitement de la queue si connexion disponible
- Vérifier si
// Dans splash_page.dart
if (await Hive.boxExists(AppKeys.pendingRequestsBoxName)) {
final box = await Hive.openBox<PendingRequest>(AppKeys.pendingRequestsBoxName);
if (box.isNotEmpty) {
print('⚠️ ${box.length} requêtes en attente trouvées');
// NE PAS SUPPRIMER LA BOX
// Notifier l'utilisateur
// Déclencher le traitement si online
}
}
Dans app_keys.dart :
static const String pendingRequestsBoxName = 'pending_requests';
static const String tempEntitiesBoxName = 'temp_entities';
Autres considérations
- Persistance sécurisée : Chiffrer les données sensibles dans Hive
- Expiration des tokens : Gérer le renouvellement des sessions
- Limite de queue : Maximum 1000 requêtes en attente
- Nettoyage : Purger les requêtes expirées (>24h) SEULEMENT si confirmé par l'utilisateur
- Validation : Revalider les données avant envoi
- Sauvegarde : Export possible des requêtes en attente avant déconnexion forcée
📈 Monitoring et debugging
Logs à implémenter
// Format des logs
[ApiService] No connection - Request queued: POST /chat/rooms/xxx/messages
[ApiService] Queue size: 5 pending requests
[ConnectivityService] Connection restored - Processing queue...
[ApiService] Processing pending request 1/5: POST /chat/rooms/xxx/messages
[ApiService] Success: temp_xxx → real_id_xxx
[ApiService] Retry 2/5 for request xxx
[ApiService] WARNING: Request xxx pending for >12h
[ApiService] ALERT: User attempting logout with 5 pending requests
Métriques à suivre
- Taille de la queue
- Temps moyen de traitement
- Taux de succès/échec
- Nombre de retry par requête
- Durée moyenne hors ligne
🚀 Bénéfices attendus
- Expérience utilisateur : Application utilisable sans connexion
- Fiabilité : Aucune perte de données
- Performance : Réponse immédiate (optimistic UI)
- Productivité : Travail continu même en zone blanche
- Résilience : Gestion automatique des problèmes réseau
⚠️ Points d'attention
- Taille de la queue : Limiter pour éviter saturation mémoire
- Ordre des requêtes : Respecter les dépendances
- Idempotence : S'assurer que rejouer une requête est safe
- Conflits : Stratégie claire de résolution
- Battery : Optimiser pour ne pas drainer la batterie
📝 Planning détaillé d'implémentation
Phase 1 : Infrastructure de base (Jour 1) ✅ COMPLÉTÉ
- Créer les constantes dans
app_keys.dart(pendingRequestsBoxName, tempEntitiesBoxName) - Créer le modèle
PendingRequestavec annotations Hive - Enregistrer l'adapter PendingRequest dans
HiveAdapters - Protéger la box
pending_requestsdansHiveService - Ajouter
pending_requestsdansHiveService._boxConfigs
Phase 2 : Modification ApiService (Jour 1-2) ✅ COMPLÉTÉ
- Créer QueueManager dans ApiService pour gérer la file
- Modifier les méthodes ApiService (get, post, put, delete) pour vérifier connectivité
- Implémenter
processPendingRequests()dans ApiService - Créer le système de retry avec backoff exponentiel
Phase 3 : Intégration ConnectivityService (Jour 2) ✅ COMPLÉTÉ
- Enrichir ApiService avec listener de connectivité pour déclencher la synchronisation au retour de connexion
Phase 4 : Chat (Proof of Concept) (Jour 3)
- Adapter ChatService pour messages temporaires (temp_xxx)
- Créer UI pour messages en attente (italique, icône horloge)
- Implémenter le remplacement temp_id vers real_id
Phase 5 : Alertes utilisateur (Jour 3) ✅ COMPLÉTÉ
- Badge de connexion avec indicateur et animation clignotante
- Dialog d'information pour création en attente (PassageRepository)
- Intégration du compteur de requêtes pendantes dans ConnectivityIndicator
- Widget PendingRequestsCounter pour affichage détaillé
Phase 6 : Adaptation des Repositories CRUD (Jour 4-5) ✅ COMPLÉTÉ
Priorité HAUTE (critique terrain) ✅
- Adapter PassageRepository pour CRUD hors ligne avec priorité haute
- Adapter ClientRepository pour création d'adhérents sur le terrain
Priorité MOYENNE ✅
- Adapter UserRepository pour CRUD hors ligne (profil utilisateur connecté uniquement)
- Adapter MembreRepository pour gestion de l'équipe
- Adapter SectorRepository pour assignation de secteurs
Priorité BASSE ✅
- Adapter AmicaleRepository pour modifications moins fréquentes
- Adapter OperationRepository pour gestion des campagnes
Phase 7 : Gestion avancée (Jour 5) ✅
- Créer système de gestion des conflits
- Détection automatique des erreurs 409 (Conflict)
- Marquage des requêtes en conflit avec
hasConflict: true - Méthodes de résolution :
resolveConflictByDeletion()etresolveConflictByRetry() - Gestion des erreurs permanentes (4xx sauf 409) avec suppression automatique
- Limite de 5 tentatives pour les erreurs temporaires
- Implémenter limite de queue (max 1000 requêtes)
- Vérification avant ajout avec exception si limite atteinte
- Message d'erreur clair pour l'utilisateur
- Créer système d'export des données en attente
- Export JSON complet avec
exportPendingRequestsToJson() - Import avec fusion et détection de doublons
importPendingRequestsFromJson() - Statistiques détaillées avec
getPendingRequestsStats()
- Export JSON complet avec
Phase 8 : Monitoring et debug (Jour 6)
- Ajouter logs détaillés pour monitoring
- Implémenter métriques de performance
Phase 9 : Tests (Jour 6-7)
- Créer tests unitaires pour QueueService
- Créer tests d'intégration mode offline/online
- Tester scénarios réels (mode avion, perte connexion)
Phase 10 : Documentation (Jour 7)
- Documenter l'utilisation pour les développeurs
- Créer guide utilisateur pour le mode hors ligne
📊 Estimation et priorités
Durée totale estimée : 5-7 jours
MVP minimal (3 jours)
- Phases 1-3 : Infrastructure + ApiService + ConnectivityService
- Phase 4 : Chat comme proof of concept
- Phase 5 : Alertes utilisateur de base
Version complète (7 jours)
- Toutes les phases incluant tous les repositories
- Tests complets
- Documentation
Ordre de priorité des repositories
- 🔴 Critique : PassageRepository, ClientRepository
- 🟠 Important : UserRepository, MembreRepository, SectorRepository
- 🟡 Normal : AmicaleRepository, OperationRepository
🔧 Utilisation des fonctionnalités avancées
Gestion des conflits
// Récupérer les requêtes en conflit
final conflicts = ApiService.instance.getConflictedRequests();
final conflictCount = ApiService.instance.getConflictedRequestsCount();
// Résoudre un conflit
await ApiService.instance.resolveConflictByDeletion(requestId); // Supprimer
await ApiService.instance.resolveConflictByRetry(requestId); // Réessayer
Export/Import des données
// Exporter les requêtes en attente
final jsonData = ApiService.instance.exportPendingRequestsToJson();
// Sauvegarder dans un fichier ou partager...
// Importer des requêtes
final imported = await ApiService.instance.importPendingRequestsFromJson(jsonString);
print('$imported requêtes importées');
// Obtenir des statistiques
final stats = ApiService.instance.getPendingRequestsStats();
print('Total: ${stats['total']}, Conflits: ${stats['conflicted']}');
Limite de queue
La limite de 1000 requêtes est automatiquement vérifiée. Si atteinte :
- Une
ApiExceptionest levée avec un message explicite - L'utilisateur doit attendre la synchronisation avant de nouvelles opérations
Guide de test en mode avion
- Activer le mode avion sur l'appareil
- Effectuer des opérations (créations, modifications, suppressions)
- Vérifier le compteur de requêtes en attente dans l'interface
- Désactiver le mode avion
- Observer la synchronisation automatique (badge devient vert)
- Vérifier les données synchronisées sur le serveur
Document créé le 25/08/2025 - Dernière mise à jour : 29/08/2025 - Système complet avec gestion des conflits et export