Skip to main content
Blog 6 min read

Flutter & Firebase : Économisez avec le Lazy Loading

Découvrez comment implémenter le lazy loading sur Flutter et Firebase pour réduire les lectures en base de données et optimiser vos coûts.

F
Fabien Chung
Flutter & Firebase : Économisez avec le Lazy Loading

Dans le cadre du développement de ma dernière application, LOVT, j'ai utilisé ma bonne vieille stack Flutter + Firebase, une combinaison puissante et parfaite pour des projets en phase de test de marché avec pour objectif d'avancer rapidement.

Pourquoi utiliser Flutter et Firebase ?

Flutter est un framework réputé pour créer des applications multiplateformes à partir d'un code source unique. Pour notre projet, nous avons utilisé Flutter Web afin de rendre l'application accessible rapidement.

Firebase, quant à lui, est un service backend extrêmement populaire auprès des développeurs, offrant une solution clé en main avec une base de données, sans nécessiter la gestion de l'infrastructure. Cela nous est particulièrement utile puisque notre application Flutter est hébergée et utilise sa base de données.

Firebase inclut un plan gratuit (le fameux "free tier") avec des quotas à respecter, comme le nombre de lectures et d'écritures dans la base de données, ainsi que la quantité de fichiers stockés et téléchargés.

Ce combo est idéal pour tester un marché, attirer de nouveaux utilisateurs et se concentrer sur le produit, avec l'avantage d'être gratuit (jusqu'à un certain seuil).

Le défi des quotas de lecture Firebase

LOVT est une application permettant aux utilisateurs de visualiser des offres et demandes de services. Elle propose une phase d'introduction pour présenter l'application, suivie d'une liste de services. Le but ici est de donner un aperçu aux non inscrits. Une fois inscrits ou connectés, les utilisateurs peuvent accéder aux détails de chaque service et contacter les personnes concernés.

Application Flutter récupérant tous les documents Firebase sans lazy loadingApplication Flutter récupérant tous les documents Firebase sans lazy loading

Suite à un lancement progressif auprès de notre communauté de premiers utilisateurs, je me suis rendu compte que le parcours d'acquisition initial allait poser problème.

En chargeant la liste de services deux fois (une fois avant la connexion et une autre fois après) on allait vite atteindre la limite de quota définie par Firebase. Pour dix services affichés, cela correspond a dix lectures en base de données. En prenant l'hypothèse que le nombre de services créés et que le nombre d'inscrits allait augmenter drastiquement, il était probable qu'on allait dépasser la limite de 50 000 lectures quotidienne.

L’optimisation avec le Lazy Loading

Pour éviter cette surcharge potentielle, je me suis dit que ce serait l'occasion parfaite pour coder du lazy loading (chargement progressif).

Le lazy loading permet de charger les données uniquement quand cela est nécessaire. Pour de gros volumes de données, cela améliore les performances et réduit l'utilisation de la mémoire en évitant de tout charger d'un coup. Dans notre cas, bien que nous n'ayons pas encore un volume important de données, je l'ai utilisé pour charger progressivement la liste des services, en prévision d'une augmentation future.

Par rapport à l'UI existante, il était suffisant de charger seulement six résultats à la fois, ce qui permettrait à l'utilisateur de voir une liste complète dès le premier affichage. Pour voir davantage de services, il lui suffirait de faire défiler vers le bas pour en charger six nouveaux.

Du point de vue code cela ressemble à ça:

Dans ma classe de repository, je construis la requête Firestore en limitant le nombre de résultats à 6.

dart29 lines
1Future<PaginatedJobPosts> getJobsAvailable( 2 {DocumentSnapshot? lastDocument, int limit = 6}) async { 3 try { 4 // Define a query with a limit 5 Query query = _firestore.collection("jobPosts").limit(limit); 6 7 if (lastDocument != null) { 8 query = query.startAfterDocument(lastDocument); 9 } 10 11 // Map the data from firestore 12 final querySnapshot = await query 13 .withConverter<JobPost>( 14 fromFirestore: (snapshot, _) => JobPost.fromMap(snapshot.data()!), 15 toFirestore: (job, _) => job.toMap(), 16 ) 17 .get(); 18 19 // Prepare the data to return 20 final jobs = querySnapshot.docs.map((doc) => doc.data()).toList(); 21 final lastDoc = querySnapshot.docs.isNotEmpty ? querySnapshot.docs.last : null; 22 23 // Return an object PaginatedJobPosts to be manipulated in the view 24 return PaginatedJobPosts(jobs, lastDoc); 25 } catch (e) { 26 debugPrint("Error fetching jobs: $e"); 27 return PaginatedJobPosts([], null); 28 } 29}

Puis, dans la vue, je présente les résultats de la requête et je gère le chargement de données supplémentaires.

dart31 lines
1// NotificationListener to handle scroll down event 2return NotificationListener<ScrollNotification>( 3 onNotification: (ScrollNotification scrollInfo) { 4 // Check if it is the bottom of the list 5 if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && 6 !isLoadingMore) { 7 _loadMoreJobs(); // Request more results from firebase 8 } 9 return false; 10 }, 11 // Load the list view 12 child: ListView.builder( 13 itemCount: _allJobs.length + (_hasMoreData ? 1 : 0), 14 itemBuilder: (context, index) { 15 if (index == _allJobs.length) { 16 return const Center(child: CircularProgressIndicator()); 17 } 18 return Container( 19 margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), 20 child: JobPostCard( 21 jobPost: _allJobs[index], 22 onTap: () => context.goNamed( 23 AppRoute.jobDetail.name, 24 pathParameters: {'id': index.toString()}, 25 extra: _allJobs[index], 26 ), 27 ), 28 ); 29 }, 30 ), 31);
dart19 lines
1Future<void> _loadMoreJobs() async { 2 // Manage loader 3 if (ref.read(loadingNotifierProvider) || !_hasMoreData) return; 4 ref.read(loadingNotifierProvider.notifier).setLoading(true); 5 6 // Call repository to get more results 7 final paginatedJobs = 8 await ref.read(getJobsAvailableProvider(_lastDocument).future); 9 10 if (paginatedJobs.jobs.isEmpty) { 11 _hasMoreData = false; 12 } else { 13 _allJobs.addAll(paginatedJobs.jobs); 14 _lastDocument = paginatedJobs.lastDocument; 15 } 16 17 // Manage loader 18 ref.read(loadingNotifierProvider.notifier).setLoading(false); 19}

Le détail du code ici.

En appliquant cette limite de chargement, le nombre de lectures en base de données a diminué de façon significative. En effet, Firebase comptabilise uniquement les éléments retournés dans la requête, ce qui signifie que si vous chargez 6 résultats, cela ne comptera que pour 6 lectures, beaucoup moins que si vous chargiez l'ensemble des données en une seule fois.

Résultats : Réduction des lectures Firebase

Est-ce que l'implémentation du lazy loading a réellement porté ses fruits ?

Grâce à une campagne TikTok très efficace, nous avons connu un pic d'acquisitions important, ce qui nous a permis de tester et de valider l'efficacité de cette optimisation.

Alors, quelles ont été les performances avant et après l'implémentation du lazy loading ?

Avant l'optimisation : Avec environ 200 inscriptions, nous étions pas loin des 43 000 lectures en base de données.

Après l'optimisation : Avec près de 650 inscriptions, le pic de lectures est descendu aux alentours des 37 000. Cela signifie que nous avons triplé le nombre d'inscriptions tout en réduisant le quota de lectures !

Les avantages du Lazy Loading dans Flutter

  • - Réduction des coûts : Nous sommes restés dans le plan gratuit de Firebase.

  • - Amélioration des performances : Temps de chargement plus rapides pour les utilisateurs.

  • - Économie de mémoire : En évitant de charger toute la liste des services d'un coup.

  • - Évolutivité : L'application peut gérer plus d'utilisateurs sans augmenter les coûts.

En somme, le lazy loading s’est révélé très efficace pour notre application. Non seulement il nous a permis de rester dans le free tier de Firebase, mais il a également offert une meilleure marge de manœuvre en termes de scalabilité.

Pour une startup comme LOVT, aux ressources limitées, chaque moyen d'optimiser et d'économiser compte. Le trio Flutter, Firebase et lazy loading est validé de mon côté. Qu'en pensez-vous ?