Flutter Blocage Réseau au Démarrage
Problème
Le démarrage de l'application Flutter est lent car les opérations réseau (connexions WebSocket, appels API, initialisation de services) s'exécutent séquentiellement pendant la phase d'initialisation, bloquant le rendu de la première frame. Avec plusieurs connexions relay/serveur, le pire cas de temps de démarrage devient O(n × timeout) au lieu de O(max timeout).
Contexte / Conditions Déclencheurs
- L'app prend 3+ secondes pour afficher la première frame au lancement
- Les logs de démarrage montrent des "connecting to relay X... connecting to relay Y..." séquentiels
- Les services nécessitant une authentification s'initialisent même pour les utilisateurs non authentifiés
_initializeCoreServices()ou similaire contient plusieurs opérations réseau en attente- Les providers Riverpod initialisent avidement les services dépendants du réseau dans leur méthode
build()
Solution
1. Identifier les Opérations Bloquantes
Cherchez les awaits séquentiels dans le code de démarrage :
// MAUVAIS : Séquentiel - chaque connexion bloque la suivante
for (final url in relays) {
await connectToRelay(url); // Bloque le démarrage !
}
2. Convertir Séquentiel en Parallèle
Utilisez Future.wait() pour exécuter toutes les connexions simultanément :
// BON : Parallèle - toutes les connexions s'exécutent à la fois
final results = await Future.wait(
relays.map((url) async {
final success = await connectToRelay(url);
return MapEntry(url, success);
}),
);
3. Reporter l'Initialisation des Services Non-Critiques
Déplacez les services dépendants du réseau hors du chemin critique de démarrage :
Avant (bloque le démarrage) :
Future<void> _initializeCoreServices(ProviderContainer container) async {
await container.read(authServiceProvider).initialize();
await container.read(nostrServiceProvider).initialize(); // BLOQUE pour les connexions relay !
await container.read(otherServiceProvider).initialize();
}
Après (initialisation lazy) :
Future<void> _initializeCoreServices(ProviderContainer container) async {
// NOTE : NostrService s'initialise paresseusement quand l'utilisateur s'authentifie
await container.read(authServiceProvider).initialize();
await container.read(seenVideosServiceProvider).initialize();
// NostrService NON initialisé ici - s'exécute quand l'état d'auth change
}
4. Utiliser les Dépendances Provider pour l'Init Lazy
Laissez Riverpod gérer l'initialisation paresseuse via les dépendances provider :
@riverpod
NostrClient nostrClient(NostrClientRef ref) {
final authService = ref.watch(authServiceProvider);
// Crée le client uniquement quand l'état d'auth est prêt
if (!authService.isAuthenticated) {
return NostrClient.disconnected();
}
// Initialise paresseusement quand vraiment nécessaire
final client = NostrClient(relays: authService.userRelays);
Future.microtask(() => client.initialize());
return client;
}
Vérification
- Vérifiez les logs de démarrage pour "First frame rendered in Xms" - doit être < 2000ms
- Vérifiez que les opérations réseau se produisent APRÈS le timestamp de première frame dans les logs
- Pour les utilisateurs non authentifiés, les connexions relay ne doivent PAS apparaître dans les logs de démarrage
Exemple
Amélioration de démarrage réalisée :
- Avant : Première frame à 3500ms+ (attente de 5 relays × ~700ms chacun)
- Après : Première frame à 1426ms (les connexions parallèles se font post-frame)
Motif de log montrant que la correction fonctionne :
[18:15:17.538] First frame rendered in 1426ms
[18:15:17.556] Creating NostrClient... // APRÈS la première frame !
Notes
- Ce motif s'applique à toute initialisation asynchrone, pas seulement les WebSockets
- Considérez la gestion des timeouts lors de la parallélisation - utilisez
Future.waitavec gestion d'erreurs - Pour les services critiques, utilisez un écran de chargement plutôt que de bloquer le thread principal
- Profilez avec Flutter DevTools Timeline pour identifier d'autres goulots d'étranglement au démarrage
- Rappelez-vous que
Future.wait()échoue rapidement par défaut - utilisez try/catch dans le map si vous voulez un succès partiel
Motifs Connexes
- Splash screen avec initialisation asynchrone
- Riverpod
AsyncNotifierpour l'état chargé paresseusement - Initialisation de service en arrière-plan après la première frame