Flutter Firestore multiplate-forme

Objectifs

Dans cet atelier de programmation, vous allez créer une application de recommandation de restaurants multiplates-formes utilisant Flutter et Cloud Firestore.

L'application finale fonctionne sur Android, iOS et sur le Web, à partir d'un codebase Dart unique.

5e7215e72fa571b.png

Points abordés

  • Lire et écrire des données dans Cloud Firestore à partir d'une application Flutter
  • Écouter les modifications apportées aux données Cloud Firestore en temps réel
  • Utiliser Firebase Authentication et les règles de sécurité pour sécuriser des données Cloud Firestore
  • Écrire des requêtes et des transactions complexes de Cloud Firestore

Qu'attendez-vous de cet atelier de programmation ?

Je suis novice en la matière et je voudrais avoir un bon aperçu. Je connais un peu le sujet, mais j'aimerais revoir certains points. Je recherche un exemple de code à utiliser dans mon projet. Je cherche des explications sur un point spécifique.

Éléments nécessaires

Si vous ne connaissez pas bien Flutter ou Firestore, commencez par suivre l'atelier de programmation Firebase pour Flutter :

Voici les éléments à réunir pour cet atelier de programmation :

  • Un IDE ou un éditeur de texte de votre choix, tel qu'Android Studio ou VS Code configuré avec les plug-ins Dart et Flutter.
  • Le navigateur Google Chrome et une certaine connaissance des outils pour les développeurs Chrome.
  • Une version récente de Flutter (version beta ou ultérieure) avec la compatibilité Web activée. Vous allez configurer l'assistance Web au cours de cet atelier de programmation, mais pour plus d'informations, consultez la page Assistance Web pour Flutter.
  • L'outil npm, pour l'installation des outils officiels de ligne de commande firebase pour la dernière partie de cet atelier de programmation (Déployer des index, Sécuriser vos données et Déployer sur Firebase Hosting).
  • (Facultatif) Un appareil Android ou un émulateur connecté, si vous souhaitez compiler votre application pour Android
  • (Facultatif) Un Mac avec une version assez récente de XCode, si vous souhaitez compiler votre application pour iOS.

Créer un projet Firebase

  1. Dans la console Firebase, cliquez sur Ajouter un projet, puis nommez le projet Firebase FriendlyEats. Prenez note de l'ID de votre projet Firebase (ou cliquez sur l'icône Modifier pour définir l'ID de projet souhaité).
  2. Cliquez sur Créer un projet.

L'application que vous créez utilise plusieurs services Firebase disponibles sur le Web :

  • Firebase Authentication, pour identifier facilement vos utilisateurs
  • Cloud Firestore, pour enregistrer des données structurées sur le cloud et recevoir une notification instantanée en cas de mise à jour des données
  • Firebase Hosting, pour héberger et diffuser vos éléments statiques

Vous allez ensuite apprendre à configurer et activer les services à l'aide de la console Firebase.

Activer l'authentification anonyme

Bien que cet aspect ne soit pas l'objectif principal de cet atelier de programmation, il est important d'avoir une forme d'authentification dans votre application. Vous utiliserez une connexion anonyme, ce qui signifie que l'utilisateur sera automatiquement connecté sans être invité à fournir ses identifiants.

Voici comment activer la connexion anonyme :

  1. Dans la console Firebase, localisez la section Créer dans la barre de navigation de gauche.
  2. Cliquez sur Authentication, puis sur l'onglet Sign-in Method (Méthode de connexion). Vous pouvez également accéder directement à la console Firebase.
  3. Activez le fournisseur de connexion Anonyme, puis cliquez sur Enregistrer.

fee6c3ebdf904459.png

L'activation de l'option Connexion anonyme permet à l'application de connecter les utilisateurs à l'application Web sans leur demander leurs identifiants. Pour en savoir plus, consultez la documentation sur l'authentification anonyme.

Activer Cloud Firestore

L'application utilise Cloud Firestore pour enregistrer et recevoir des informations et des notes sur un restaurant.

Voici comment activer Cloud Firestore :

  1. Dans la section Créer de la console Firebase, cliquez sur Firestore Database.
  2. Cliquez sur Créer une base de données dans le volet Cloud Firestore.

57e83568e05c7710.png

  1. Sélectionnez l'option Démarrer en mode test, puis cliquez sur Activer après avoir lu la clause de non-responsabilité concernant les règles de sécurité.

Le mode test vous permet d'écrire librement dans la base de données pendant le développement. Vous améliorerez la sécurité de votre base de données dans une étape ultérieure de cet atelier de programmation.

daef1061fc25acc7.png

Clonez le dépôt GitHub à partir de la ligne de commande :

git clone https://github.com/FirebaseExtended/codelab-friendlyeats-flutter.git friendlyeats-flutter

L'exemple de code doit être cloné dans le répertoire 📁friendlyeats-flutter. À partir de maintenant, assurez-vous d'exécuter des commandes à partir du répertoire ci-dessous :

cd friendlyeats-flutter

Importer l'application de départ

Ouvrez ou importez le répertoire 📁friendlyeats-flutter dans l'IDE de votre choix. Ce répertoire contient le code de départ de l'atelier de programmation, qui correspond à une application de recommandation de restaurants non fonctionnelle.

Vous la rendrez opérationnelle au cours de cet atelier de programmation et allez rapidement modifier le code de ce répertoire.

Localiser les fichiers à modifier

Même si le point d'entrée habituel d'une application Flutter est son fichier lib/main.dart, cet atelier de programmation vous fait travailler en priorité sur les aspects relatifs aux données.

Recherchez les fichiers suivants dans le projet :

  • lib/src/model/data.dart : fichier principal que vous modifiez pendant cet atelier de programmation. Il contient toute la logique de lecture et d'écriture des données à partir de Firestore.
  • web/index.html : fichier chargé par le navigateur pour démarrer votre application. Modifiez ce fichier pour installer et initialiser les bibliothèques Firebase pour le Web.

CLI Firebase

L'interface de ligne de commande (CLI) de Firebase vous permet de déployer votre application Web et votre configuration sur Firebase directement à partir des fichiers de votre projet.

  1. Installez la CLI en exécutant la commande npm suivante :
npm -g install firebase-tools
  1. Exécutez la commande suivante pour vérifier que la CLI a été installée correctement :
firebase --version

Assurez-vous que la version de la CLI Firebase est la version 7.4.0 ou ultérieure.

  1. Autorisez la CLI Firebase en exécutant la commande suivante :
firebase login

Le dépôt que vous avez cloné à l'étape précédente contient déjà un fichier firebase.json avec une configuration de projet prête à l'emploi (emplacement des autres fichiers de configuration, hébergement du déploiement, etc.). Vous devez maintenant associer votre copie de travail de l'application à votre projet Firebase :

  1. Assurez-vous que votre ligne de commande accède au répertoire local de votre application.
  2. Pour associer votre application à votre projet Firebase, exécutez la commande suivante :
firebase use --add
  1. Lorsque vous y êtes invité, sélectionnez votre ID de projet, puis attribuez un alias à votre projet Firebase.

Un alias est utile si vous disposez de plusieurs environnements (production, préproduction, etc.). Cependant, pour cet atelier de programmation, utilisez simplement l'alias default.

  1. Suivez les instructions fournies dans votre ligne de commande.

Activer la compatibilité Web pour Flutter

Pour compiler votre application Flutter afin qu'elle s'exécute sur le Web, vous devez activer cette fonctionnalité (actuellement en version bêta). Pour activer la compatibilité Web, saisissez les commandes suivantes :

$ flutter channel beta
$ flutter upgrade
$ flutter config --enable-web

Dans votre IDE, sous la liste des appareils, ou depuis la ligne de commande en utilisant flutter devices, Chrome et le serveur Web doivent être répertoriés.

L'appareil Chrome lance automatiquement Chrome. Le serveur Web démarre un serveur qui héberge l'application afin que vous puissiez la charger depuis n'importe quel navigateur.

Utilisez l'appareil Chrome pendant le développement afin de pouvoir utiliser DevTools, et utilisez le serveur Web lorsque vous souhaitez effectuer des tests sur d'autres navigateurs.

Après avoir créé un projet Firebase, vous pouvez configurer une ou plusieurs applications pour qu'elles utilisent ce projet Firebase. Procédez comme suit :

  • Enregistrez l'ID propre à la plate-forme de votre application avec Firebase.
  • Générez des fichiers de configuration pour votre application.
  • Ajoutez la configuration à l'emplacement approprié dans les dossiers de votre projet.

ac27fbbadff7a3b9.png

Si vous développez votre application Flutter pour plusieurs plates-formes, vous devez enregistrer chaque plate-forme sur laquelle votre application s'exécute dans le même projet Firebase.

Cet atelier de programmation porte avant tout sur la plate-forme Web, car iOS et Android sont abordés dans l'atelier de programmation Firebase pour Flutter. Accédez à cet atelier de programmation si vous souhaitez ajouter la compatibilité avec Android ou iOS à votre application FriendlyEats.

Dans votre application Flutter, il existe un fichier web/index.html spécial qui sert de point d'entrée à votre application lorsqu'elle s'exécute sur le Web. Vous modifiez ce point d'entrée à l'aide d'une configuration spécifique pour votre projet, afin que votre application Web puisse se connecter au backend Firebase.

Configurer pour le Web

  1. Dans la console Firebase, sélectionnez Vue d'ensemble du projet dans la barre de navigation de gauche, puis cliquez sur le bouton Web sous Lancez-vous en ajoutant Firebase à votre application. La boîte de dialogue suivante doit s'afficher :

f76ed55f71f15953.png

  1. Donnez un nom à votre application. Il s'agit de la valeur utilisée dans la console Firestore pour identifier la version Web de votre application.
  2. Cliquez sur Enregistrer l'application.
  3. Une fois que vous avez enregistré votre application, l'étape Ajouter le SDK Firebase vous fournit du code à coller dans le fichier web/index.html de votre application Flutter. Voici à quoi doit ressembler le résultat de cette opération :

web/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>friendlyeats</title>

  <!-- The core Firebase JS SDK is always required and must be listed first -->
  <script src="https://www.gstatic.com/firebasejs/7.5.1/firebase-app.js"></script>

  <!-- TODO: Add SDKs for Firebase products that you want to use
      https://firebase.google.com/docs/web/setup#available-libraries -->

  <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
      apiKey: "YoUr_RaNdOm_API_kEy",
      authDomain: "your-project-name.firebaseapp.com",
      databaseURL: "https://your-project-name.firebaseio.com",
      projectId: "your-project-name",
      storageBucket: "your-project-name.appspot.com",
      messagingSenderId: "012345678901",
      appId: "1:109876543210:web:r4nd0mH3xH45h"
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
  </script>

</head>
<body>
  <script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
  1. Notez que le code que vous venez de coller comporte un élément TODO. Vous allez corriger cela immédiatement. Comme l'atelier de programmation utilise Firebase Authentication et Firestore, ajoutez les balises de script pour ces produits :

web/index.html

  ...
  <!-- TODO: Add SDKs for Firebase products that you want to use
      https://firebase.google.com/docs/web/setup#available-libraries -->

  <script src="https://www.gstatic.com/firebasejs/7.5.1/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.5.1/firebase-firestore.js"></script>

  <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
      ...
  1. Enregistrez votre fichier web/index.html, puis cliquez sur Accéder à la console dans la boîte de dialogue Ajouter Firebase à votre application Web.
  2. Votre application Flutter est prête à être associée à Firebase.

d43a19dbf1248134.pngVous avez trouvé une pièce du puzzle !

La plupart des modifications de code requises pour activer la compatibilité Firebase sont déjà activées dans le projet sur lequel vous travaillez. Toutefois, afin d'ajouter la compatibilité avec les plates-formes mobiles, vous devez utiliser une procédure semblable à celle que vous venez de suivre pour le Web :

  • Enregistrez la plate-forme souhaitée sur le projet Firebase.
  • Téléchargez le fichier de configuration correspondant à la plate-forme, puis ajoutez-le au code.

Dans le répertoire de premier niveau de votre application Flutter, vous trouverez des sous-répertoires appelés ios et android. Ces répertoires contiennent les fichiers de configuration propres aux plates-formes iOS et Android.

Configurer iOS

  1. Dans la console Firebase, sélectionnez Vue d'ensemble du projet dans la barre de navigation de gauche, puis cliquez sur le bouton iOS sous Lancez-vous en ajoutant Firebase à votre application.

La boîte de dialogue suivante doit s'afficher :

c42139f18fb9a2ee.png

  1. La valeur importante à indiquer est l'ID de groupe iOS. Pour obtenir l'ID du groupe, vous devez effectuer les trois opérations suivantes.
  1. Dans l'outil de ligne de commande, accédez au répertoire de premier niveau de votre application Flutter.
  2. Exécutez la commande open ios/Runner.xcworkspace pour ouvrir Xcode.
  1. Dans Xcode, cliquez sur le premier niveau.Runner dans le volet de gauche pour afficher General dans le volet de droite, comme illustré. Copiez la valeur de l'identifiant de groupe.

9733e26be329f329.png

  1. Revenez à la boîte de dialogue Firebase, collez l'identifiant de groupe copié dans le champ ID du groupe iOS, puis cliquez sur Enregistrer l'application.
  1. Dans Firebase, suivez les instructions pour télécharger le fichier de configuration GoogleService-Info.plist.
  2. Revenez à Xcode. Notez que l'élément Runner possède un sous-dossier également nommé Runner (illustré dans l'image précédente).
  3. Faites glisser le fichier GoogleService-Info.plist (que vous venez de télécharger) dans ce sous-dossier Runner.
  4. Dans la boîte de dialogue qui apparaît dans Xcode, cliquez sur Finish (Terminer).
  5. Revenez à la console Firebase. À l'étape de configuration, cliquez sur Suivant, ignorez les étapes restantes, puis revenez à la page principale de la console Firebase.

La configuration de votre application Flutter pour iOS est terminée.

Configurer Android

  1. Dans la console Firebase, sélectionnez Vue d'ensemble du projet dans la barre de navigation de gauche, puis cliquez sur le bouton Android sous Lancez-vous en ajoutant Firebase à votre application.

La boîte de dialogue suivante doit s'afficher : 8254fc299e82f528.png

  1. La valeur importante à indiquer est le nom du package Android. Effectuez les deux opérations suivantes pour l'obtenir :
  1. Dans le répertoire de votre application Flutter, ouvrez le fichier android/app/src/main/AndroidManifest.xml.
  2. Dans l'élément manifest, recherchez la valeur de chaîne de l'attribut package. Cette valeur correspond au nom du package Android (du type com.yourcompany.yourproject). Copiez cette valeur.
  3. Dans la boîte de dialogue Firebase, collez le nom du package copié dans le champ Nom du package Android.
  4. Vous n'avez pas besoin du certificat de signature de débogage SHA-1 pour cet atelier de programmation. Laissez ce champ vide.
  5. Cliquez sur Enregistrer l'application.
  6. Dans Firebase, suivez les instructions pour télécharger le fichier de configuration google-services.json.
  7. Accédez au répertoire de votre application Flutter et déplacez le fichier google-services.json (que vous venez de télécharger) dans le répertoire android/app.
  8. Revenez dans la console Firebase, ignorez les étapes restantes, puis accédez de nouveau à la page principale de la console Firebase.
  9. Toute la configuration Gradle est déjà activée. Si votre application est toujours en cours d'exécution, fermez-la et générez-la de nouveau pour permettre à Gradle d'installer des dépendances.

La configuration de votre application Flutter pour Android est à présent terminée.

Vous êtes prêt à travailler sur votre application. Commencez par exécuter l'application en local. Vous pouvez désormais exécuter l'application sur toutes les plates-formes que vous avez configurées (et pour lesquelles un appareil et un émulateur sont disponibles).

Découvrez quels appareils sont disponibles à l'aide de la commande suivante :

flutter devices

Selon les appareils disponibles, le résultat de la commande précédente peut se présenter ainsi :

3 connected devices:

Android SDK built for x86 • emulator-5554 • android-x86    • Android 7.1.1 (API 25) (emulator)
Chrome                    • chrome        • web-javascript • Google Chrome 79.0.3945.130
Web Server                • web-server    • web-javascript • Flutter Tools

Nous allons poursuivre cet atelier de programmation en utilisant l'appareil chrome.

  1. Exécutez la commande CLI Flutter suivante :
flutter run -d chrome
  1. Flutter commence par générer votre application pour le Web, puis ouvre automatiquement une fenêtre Chrome avec l'application en cours d'exécution.

Votre copie de FriendlyEats doit maintenant être associée à votre projet Firebase.

L'application se connecte automatiquement à votre projet Firebase, puis vous connecte automatiquement en tant qu'utilisateur anonyme.

c45806a2ac9300d9.png

Dans cette section, vous allez écrire des données dans Cloud Firestore afin de renseigner l'UI de l'application. Il est possible d'effectuer cette opération manuellement à l'aide de la console Firebase, mais vous le ferez dans l'application pour voir comment se déroule un processus basique d'écriture Cloud Firestore.

Modèle de données

Les données Firestore sont divisées en collections, documents, champs et sous-collections. Chaque restaurant est stocké sous forme de document dans une collection de premier niveau appelée restaurants.

92f8dc2c769d2d6c.png

Vous stockez ensuite chaque avis dans une sous-collection appelée ratings dans chaque document restaurant.

a00d9eb006ddd6c0.png

Ajouter des restaurants à Firestore

Le principal objet de modèle dans l'application est un restaurant. Ensuite, écrivez du code pour ajouter un document "restaurant" à la collection restaurants.

  1. Ouvrez lib/src/model/data.dart.
  2. Recherchez la fonction addRestaurant.
  3. Remplacez toute la fonction par le code suivant :

lib/src/model/data.dart

Future<void> addRestaurant(Restaurant restaurant) {
  final restaurants = FirebaseFirestore.instance.collection('restaurants');
  return restaurants.add({
    'avgRating': restaurant.avgRating,
    'category': restaurant.category,
    'city': restaurant.city,
    'name': restaurant.name,
    'numRatings': restaurant.numRatings,
    'photo': restaurant.photo,
    'price': restaurant.price,
  });
}

Le code précédent ajoute un document à la collection restaurants.

Ceci s'effectue en obtenant une référence à une collection Cloud Firestore restaurants, puis en ajoutant (adding) les données.

Les données du document proviennent d'un objet Restaurant, qui doit être converti en objet Map pour le plug-in Firestore.

Ajouter des restaurants

  1. Générez à nouveau et actualisez votre application Flutter (Maj+R dans la fenêtre de terminal qui exécute votre application).
  2. Cliquez sur ADD SOME (AJOUTER DES ÉLÉMENTS).

L'application génère automatiquement un ensemble aléatoire d'objets restaurants et appelle votre fonction addRestaurant. Toutefois, les données ne s'afficheront pas dans votre application Web, car vous devez toujours mettre en œuvre la récupération des données (la section suivante de l'atelier de programmation y est consacrée).

Si vous accédez à l'onglet Développer > Firestore Database > Cloud Firestore dans la console Firebase, les nouveaux documents devraient s'afficher dans la collection restaurants.

f06898b9d6dd4881.png

Félicitations ! Vous venez d'écrire des données dans Cloud Firestore à partir d'une application Web.

Dans la section suivante, vous allez apprendre à récupérer des données dans Cloud Firestore pour les afficher dans votre application.

Dans cette section, vous découvrirez comment récupérer des données depuis Cloud Firestore pour les afficher dans votre application. Les deux étapes clés sont la création d'une requête et l'écoute de son flux Stream d'instantanés. Cet écouteur est informé de toutes les données existantes correspondant à la requête et actualisé en temps réel.

Commencez par créer la requête qui diffuse la liste de restaurants par défaut et non filtrée.

  1. Revenez au fichier lib/src/model/data.dart.
  2. Recherchez la fonction loadAllRestaurants.
  3. Remplacez toute la fonction par le code suivant :

lib/src/model/data.dart

Stream<QuerySnapshot> loadAllRestaurants() {
  return FirebaseFirestore.instance
      .collection('restaurants')
      .orderBy('avgRating', descending: true)
      .limit(50)
      .snapshots();
}

Le code précédent crée une requête qui récupère jusqu'à 50 restaurants dans la collection de premier niveau nommée restaurants, classés en fonction de leur note moyenne (égale à zéro pour tous actuellement).

Vous devez maintenant transformer chaque instantané QuerySnapshot renvoyé par le flux Stream en données Restaurant affichables.

Voici comment extraire des informations Restaurant d'un élément QuerySnapshot de la collection restaurants :

  1. Revenez au fichier lib/src/model/data.dart.
  2. Recherchez la fonction getRestaurantsFromQuery.
  3. Remplacez toute la fonction par le code suivant :

lib/src/model/data.dart

List<Restaurant> getRestaurantsFromQuery(QuerySnapshot snapshot) {
  return snapshot.docs.map((DocumentSnapshot doc) {
    return Restaurant.fromSnapshot(doc);
  }).toList();
}

La méthode getRestaurantsFromQuery est appelée à chaque apparition d'un nouvel instantané QuerySnapshot correspondant à la requête Query que vous avez créé précédemment. Les instantanés QuerySnapshots sont le mécanisme utilisé par Firestore pour avertir votre application en temps réel des modifications apportées à une requête Query.

Cette méthode convertit simplement tous les documents contenus dans l'instantané snapshot en objets Restaurant qui peuvent être utilisés ailleurs dans votre application Flutter.

Maintenant que vous avez mis en œuvre les deux méthodes, générez à nouveau, puis rechargez votre application et vérifiez que les restaurants affichés précédemment dans la console Firebase s'affichent bien dans l'application. Si vous n'avez pas fait d'erreur dans cette section, votre application lit et écrit désormais des données avec Cloud Firestore.

À mesure que votre liste de restaurants change, cet écouteur s'actualise automatiquement. Essayez d'accéder à la console Firebase afin de supprimer manuellement un restaurant ou d'en modifier le nom. Ces modifications s'affichent immédiatement sur votre site.

edd9adbafa5bd539.png

Jusqu'à présent, vous avez appris à utiliser la fonction onSnapshot pour obtenir les actualisations en temps réel. Cependant, cette approche ne convient pas à toutes les situations. Il peut parfois être judicieux de ne récupérer les données qu'une seule fois.

Il vous faut une méthode qui charge un restaurant spécifique à partir de son ID lorsque les utilisateurs cliquent sur un restaurant donné dans l'application.

  1. Revenez au fichier lib/src/model/data.dart.
  2. Recherchez la fonction getRestaurant.
  3. Remplacez toute la fonction par le code suivant :

lib/src/model/data.dart

Future<Restaurant> getRestaurant(String restaurantId) {
  return FirebaseFirestore.instance
      .collection('restaurants')
      .doc(restaurantId)
      .get()
      .then((DocumentSnapshot doc) => Restaurant.fromSnapshot(doc));
}

Le code utilise get() pour récupérer un élément Future<DocumentSnapshot> contenant les informations du restaurant que vous avez demandé. Il vous suffit d'acheminer cet élément via then() vers une fonction qui convertit l'instantané DocumentSnapshot en objet Restaurant dès qu'il est disponible.

Après avoir mis en œuvre cette méthode, vous pouvez afficher la page d'informations de chaque restaurant.

  1. Actualisez votre application en appuyant sur Maj+R dans le terminal où flutter est exécuté.
  2. Cliquez sur un restaurant de la liste pour afficher la page d'informations le concernant :

f8ca540dda5540a9.png

Ensuite, utilisez le code nécessaire pour ajouter les notes aux restaurants à l'aide de transactions.

Dans cette section, vous donnez aux utilisateurs la possibilité d'envoyer des avis sur des restaurants. Jusqu'à présent, toutes vos écritures étaient indépendantes et relativement simples. En cas d'erreur, il vous suffisait de demander à l'utilisateur de réessayer l'écriture, à moins que l'application ne le fasse automatiquement.

De nombreux utilisateurs souhaiteront ajouter une note à un restaurant. Vous devez donc coordonner plusieurs opérations de lecture et d'écriture. Tout d'abord, l'avis doit être envoyé, puis les valeurs count de la note du restaurant et la moyenne average rating doivent être actualisées. Si l'une des deux opérations échoue, mais pas l'autre, votre application se retrouvera dans un état incohérent. Les données d'une partie de la base de données ne correspondront plus à celles d'une autre partie.

Heureusement, Cloud Firestore propose une fonctionnalité de transaction qui vous permet d'effectuer plusieurs opérations de lecture et d'écriture en une seule opération indépendante afin de garantir la cohérence de vos données.

  1. Revenez au fichier lib/src/model/data.dart.
  2. Recherchez la fonction addReview.
  3. Remplacez toute la fonction par le code suivant :

lib/src/model/data.dart

Future<void> addReview({String restaurantId, Review review}) {
  final restaurant =
      FirebaseFirestore.instance.collection('restaurants').doc(restaurantId);
  final newReview = restaurant.collection('ratings').doc();

  return FirebaseFirestore.instance.runTransaction((Transaction transaction) {
    return transaction
        .get(restaurant)
        .then((DocumentSnapshot doc) => Restaurant.fromSnapshot(doc))
        .then((Restaurant fresh) {
      final newRatings = fresh.numRatings + 1;
      final newAverage =
          ((fresh.numRatings * fresh.avgRating) + review.rating) / newRatings;

      transaction.update(restaurant, {
        'numRatings': newRatings,
        'avgRating': newAverage,
      });

      transaction.set(newReview, {
        'rating': review.rating,
        'text': review.text,
        'userName': review.userName,
        'timestamp': review.timestamp ?? FieldValue.serverTimestamp(),
        'userId': review.userId,
      });
    });
  });
}

La fonction précédente déclenche une transaction qui commence par récupérer une version fresh de l'objet Restaurant représentée par restaurantId.

Ensuite, mettez à jour les valeurs numériques de avgRating et numRatings dans la référence du document restaurant.

En parallèle, ajoutez le nouveau review via la référence du document newReview dans la sous-collection ratings du restaurant.

Voici comment tester le code que vous venez d'ajouter :

  1. Actualisez votre application en appuyant sur Maj+R dans le terminal où flutter est exécuté.
  2. Accédez à la page d'informations de n'importe quel restaurant.
  3. Ajoutez des avis. Voici deux méthodes pour ce faire :
  • En l'absence d'avis, cliquez sur le bouton ADD SOME (AJOUTER DES ÉLÉMENTS) dans la liste vide.
  • Cliquez sur le bouton d'action flottant +, puis saisissez votre propre avis.

Actuellement, l'application affiche la liste des restaurants, mais l'utilisateur ne peut pas la filtrer en fonction de ses besoins. Dans cette section, vous utilisez les fonctionnalités avancées de requête avancée de Cloud Firestore pour activer le filtrage.

Voici un exemple de requête basique permettant de récupérer tous les restaurants de la catégorie Dim Sum :

Query filteredCollection = FirebaseFirestore.instance
        .collection('restaurants')
        .where('category', isEqualTo: 'Dim Sum');

Comme son nom l'indique, la méthode where() limite les téléchargements déclenchés par la requête aux membres de la collection dont les champs correspondent aux restrictions que vous avez définies. Dans ce cas, il ne télécharge que les restaurants dont l'attribut category est égal à Dim Sum.

Vous pouvez également trier les données obtenues :

Query filteredAndSortedCollection = FirebaseFirestore.instance
        .collection('restaurants')
        .where('category', isEqualTo: 'Dim Sum')
        .orderBy('price', descending: true);

La méthode orderBy() fait en sorte que la requête trie les restaurants de catégorie Dim Sum en fonction de leur attribut de coût, price, par ordre décroissant.

Dans l'application, l'utilisateur peut combiner plusieurs filtres pour créer des requêtes spécifiques, telles que Pizza à San Francisco ou Fruits de mer à Los Angeles, par ordre de popularité.

Vous créez une méthode qui génère une requête filtrant les restaurants en fonction de plusieurs critères sélectionnés par les utilisateurs.

  1. Revenez au fichier lib/src/model/data.dart.
  2. Recherchez la fonction loadFilteredRestaurants.
  3. Remplacez toute la fonction par le code suivant :

lib/src/model/data.dart

Stream<QuerySnapshot> loadFilteredRestaurants(Filter filter) {
  Query collection = FirebaseFirestore.instance.collection('restaurants');
  if (filter.category != null) {
    collection = collection.where('category', isEqualTo: filter.category);
  }
  if (filter.city != null) {
    collection = collection.where('city', isEqualTo: filter.city);
  }
  if (filter.price != null) {
    collection = collection.where('price', isEqualTo: filter.price);
  }
  return collection
      .orderBy(filter.sort ?? 'avgRating', descending: true)
      .limit(50)
      .snapshots();
}

Le code précédent ajoute plusieurs filtres where et une seule clause orderBy afin de générer une requête complexe en fonction des entrées utilisateur. Désormais, la requête ne renvoie que les restaurants qui correspondent aux besoins de l'utilisateur.

Actualisez votre application dans votre navigateur en appuyant sur les touches Maj+R du terminal où flutter est exécuté.

Essayez de filtrer les résultats par prix, par ville et par catégorie. Au cours de la phase de test, les erreurs qui s'afficheront dans la console JavaScript de votre navigateur se présentent ainsi :

The query requires an index. You can create it here: https://console.firebase.google.com/project/.../database/firestore/indexes?create_index=...

Ces erreurs sont dues au fait que Cloud Firestore nécessite une indexation préalable pour la plupart des requêtes complexes. Le fait d'exiger une indexation sur les requêtes permet à Cloud Firestore à'être rapide à grande échelle.

L'ouverture du lien à partir du message d'erreur entraîne l'ouverture automatique de l'interface utilisateur de création d'index dans la console Firebase avec les paramètres corrects renseignés.

Dans la section suivante, vous allez écrire et déployer, dans la CLI Firebase, tous les index requis pour cette application en une fois.

Si vous ne souhaitez pas explorer toutes les options de l'application et suivre chaque lien de création d'index, vous pouvez facilement déployer plusieurs index à la fois à l'aide de la CLI Firebase.

  1. À la racine du projet de votre application, recherchez le fichier firestore.indexes.json.

Ce fichier décrit tous les index nécessaires pour toutes les combinaisons possibles de filtres.

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. Déployez ces index à l'aide de la commande suivante :
firebase deploy --only firestore:indexes

Après quelques minutes, vos index deviennent opérationnels et les messages d'erreur disparaissent. Si vous essayez d'utiliser les index avant qu'ils ne soient prêts, vous risquez de rencontrer des erreurs de ce type :

The query requires an index. That index is currently building and cannot be used yet. See its status here:
https://console.firebase.google.com/project/.../database/firestore/indexes?create_index=...

Au début de cet atelier de programmation, vous avez défini les règles de sécurité de votre application pour ouvrir la base de données à toute lecture ou écriture. Dans une application réelle, il vous faudrait définir des règles bien plus précises pour éviter les accès aux données ou les modifications indésirables.

.

  1. Dans la section Créer de la console Firebase, cliquez sur Firestore Database.
  2. Cliquez sur l'onglet Rules (Règles) dans la section Cloud Firestore (ou accédez directement à la console Firebase).
  3. Remplacez les valeurs par défaut par les règles suivantes, puis cliquez sur Publier.

firestore.rules

service cloud.firestore {
  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo)
    //   - Validate updates
    //   - Deletes are not allowed
    match /restaurants/{restaurantId} {
      allow read, create: if request.auth != null;
      allow update: if request.auth != null
                    && request.resource.data.name == resource.data.name
      allow delete: if false;

      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
        allow update, delete: if false;
      }
    }
  }
}

Ces règles limitent l'accès afin de s'assurer que les clients n'apportent que des modifications sûres, par exemple :

  • Les modifications apportées à un document "restaurant" ne peuvent modifier que les notes, pas le nom, ni aucune autre donnée immuable.
  • La création de notes n'est possible que si l'ID utilisateur correspond à l'utilisateur connecté, ce qui évite tout spoofing.

Au lieu d'utiliser la console Firebase, vous pouvez utiliser la CLI Firebase pour déployer des règles dans votre projet Firebase. Le fichier firestore.rules de votre répertoire de travail contient déjà les règles ci-dessus. Pour déployer ces règles depuis votre système de fichiers local (au lieu d'utiliser la console Firebase), exécutez la commande suivante :

firebase deploy --only firestore:rules

Commande "flutter build web"

Jusqu'à présent, vous n'avez utilisé que les versions de débogage de votre application Flutter. Ces builds sont un peu plus lents, car ils contiennent des informations supplémentaires pour faciliter le débogage.

Avant de déployer votre application, vous devez créer une version de production. Flutter vous permet de créer des applications pour la production avec l'outil build :

flutter build web

Cette opération place tous les éléments de production dans le répertoire build/web du projet.

Votre application est maintenant prête à être déployée sur Firebase.

Commande "firebase deploy"

Votre projet est préconfiguré pour créer et déployer les éléments générés par Flutter (consultez le fichier firebase.json à la racine du projet).

Déployez une nouvelle version de votre application avec Firebase à l'aide des commandes suivantes :

firebase init hosting
firebase deploy --only hosting

La procédure ci-dessus ne devrait prendre que quelques secondes. Elle nettoie tous les builds précédents (flutter clean), génère à nouveau votre application (flutter build web), puis déploie les nouveaux éléments (le contenu de build/web) dans Firebase Hosting.

Le message de confirmation contient une URL d'hébergement où votre application publiée est disponible sur Internet.

Félicitations !

Dans cet atelier de programmation, vous avez appris à connecter votre application Web Flutter à Firebase à l'aide des plug-ins Firebase Authentication et Firestore, à effectuer des lectures et des écritures de base et avancées avec Cloud Firestore, et à sécuriser l'accès aux données à l'aide de règles de sécurité.

Vous trouverez la solution complète dans la branche done (terminé) du dépôt.

Pour en savoir plus sur Dart et Flutter, consultez leur site officiel :

Pour en savoir plus sur Cloud Firestore, consultez les ressources suivantes :

Cet atelier de programmation est un bon point de départ pour découvrir d'autres fonctionnalités de Firestore (et Firebase). Voici quelques exercices supplémentaires pour approfondir vos compétences :

  • Utilisez les écritures par lots dans la méthode addRestaurantsBatch pour ajouter tous les restaurants et avis dans une seule requête afin de n'actualiser l'UI de votre application qu'une seule fois.
  • Modifiez le widget RestaurantAppBar pour mettre à jour la note d'un restaurant en temps réel lorsque les utilisateurs y ajoutent des avis.
  • Activez google_sign_in dans firebase_auth afin de récupérer le prénom réel de l'utilisateur qui publie un avis, car tous les utilisateurs actuels sont anonymes.