Apprenez à connaître Firebase pour Flutter

1. Avant de commencer

Dans cet atelier de programmation, vous apprendrez certaines bases de Firebase pour créer des applications mobiles Flutter pour Android et iOS.

Conditions préalables

Ce que vous apprendrez

  • Comment créer une application de discussion RSVP et livre d'or pour événements sur Android, iOS, le Web et macOS avec Flutter.
  • Comment authentifier les utilisateurs avec l'authentification Firebase et synchroniser les données avec Firestore.

L'écran d'accueil de l'application sur Android

L'écran d'accueil de l'application sur iOS

Ce dont vous aurez besoin

L'un des appareils suivants :

  • Un appareil physique Android ou iOS connecté à votre ordinateur et configuré en mode développeur.
  • Le simulateur iOS (Nécessite les outils Xcode ).
  • L'émulateur Android (nécessite une configuration dans Android Studio ).

Vous avez également besoin des éléments suivants :

  • Un navigateur de votre choix, tel que Google Chrome.
  • Un IDE ou un éditeur de texte de votre choix configuré avec les plugins Dart et Flutter, comme Android Studio ou Visual Studio Code .
  • La dernière version stable de Flutter ou beta si vous aimez vivre à la limite.
  • Un compte Google pour la création et la gestion de votre projet Firebase.
  • La CLI Firebase s'est connectée à votre compte Google.

2. Obtenez l'exemple de code

Téléchargez la version initiale de votre projet depuis GitHub :

  1. Depuis la ligne de commande, clonez le dépôt GitHub dans le répertoire flutter-codelabs :
git clone https://github.com/flutter/codelabs.git flutter-codelabs

Le répertoire flutter-codelabs contient le code d'une collection d'ateliers de programmation. Le code de cet atelier de programmation se trouve dans le répertoire flutter-codelabs/firebase-get-to-know-flutter . Le répertoire contient une série d'instantanés qui montrent à quoi devrait ressembler votre projet à la fin de chaque étape. Par exemple, vous êtes à la deuxième étape.

  1. Recherchez les fichiers correspondants pour la deuxième étape :
cd flutter-codelabs/firebase-get-to-know-flutter/step_02

Si vous souhaitez avancer ou voir à quoi devrait ressembler quelque chose après une étape, regardez dans le répertoire nommé d'après l'étape qui vous intéresse.

Importer l'application de démarrage

  • Ouvrez ou importez le répertoire flutter-codelabs/firebase-get-to-know-flutter/step_02 dans votre IDE préféré. Ce répertoire contient le code de démarrage de l'atelier de programmation, qui consiste en une application de rencontre Flutter non encore fonctionnelle.

Localisez les fichiers qui nécessitent du travail

Le code de cette application est réparti sur plusieurs répertoires. Cette répartition des fonctionnalités facilite le travail car elle regroupe le code par fonctionnalité.

  • Recherchez les fichiers suivants :
    • lib/main.dart : Ce fichier contient le point d'entrée principal et le widget de l'application.
    • lib/home_page.dart : Ce fichier contient le widget de la page d'accueil.
    • lib/src/widgets.dart : Ce fichier contient une poignée de widgets pour aider à standardiser le style de l'application. Ils composent l'écran de l'application de démarrage.
    • lib/src/authentication.dart : ce fichier contient une implémentation partielle de l'authentification avec un ensemble de widgets pour créer une expérience utilisateur de connexion pour l'authentification par courrier électronique Firebase. Ces widgets pour le flux d'authentification ne sont pas encore utilisés dans l'application de démarrage, mais vous les ajouterez bientôt.

Vous ajoutez des fichiers supplémentaires si nécessaire pour créer le reste de l'application.

Consultez le fichier lib/main.dart

Cette application tire parti du package google_fonts pour faire de Roboto la police par défaut dans toute l'application. Vous pouvez explorer fonts.google.com et utiliser les polices que vous y découvrez dans différentes parties de l'application.

Vous utilisez les widgets d'assistance du fichier lib/src/widgets.dart sous la forme de Header , Paragraph et IconAndDetail . Ces widgets éliminent le code dupliqué pour réduire l'encombrement dans la mise en page décrite dans HomePage . Cela permet également une apparence et une sensation cohérentes.

Voici à quoi ressemble votre application sur Android, iOS, le Web et macOS :

L'écran d'accueil de l'application sur Android

L'écran d'accueil de l'application sur iOS

L'écran d'accueil de l'application sur le Web

L'écran d'accueil de l'application sur macOS

3. Créez et configurez un projet Firebase

L'affichage des informations sur l'événement est idéal pour vos invités, mais il n'est pas très utile pour quiconque à lui seul. Vous devez ajouter des fonctionnalités dynamiques à l'application. Pour ce faire, vous devez connecter Firebase à votre application. Pour démarrer avec Firebase, vous devez créer et configurer un projet Firebase.

Créer un projet Firebase

  1. Connectez-vous à Firebase .
  2. Dans la console, cliquez sur Ajouter un projet ou Créer un projet .
  3. Dans le champ Nom du projet , saisissez Firebase-Flutter-Codelab , puis cliquez sur Continuer .

4395e4e67c08043a.png

  1. Cliquez sur les options de création de projet. Si vous y êtes invité, acceptez les conditions de Firebase, mais ignorez la configuration de Google Analytics, car vous ne l'utiliserez pas pour cette application.

b7138cde5f2c7b61.png

Pour en savoir plus sur les projets Firebase, consultez Comprendre les projets Firebase .

L'application utilise les produits Firebase suivants, disponibles pour les applications Web :

  • Authentification : permet aux utilisateurs de se connecter à votre application.
  • Firestore : enregistre les données structurées sur le cloud et reçoit des notifications instantanées lorsque les données changent.
  • Règles de sécurité Firebase : sécurise votre base de données.

Certains de ces produits nécessitent une configuration spéciale ou vous devez les activer dans la console Firebase.

Activer l'authentification de connexion par e-mail

  1. Dans le volet de présentation du projet de la console Firebase, développez le menu Créer .
  2. Cliquez sur Authentification > Commencer > Méthode de connexion > E-mail/Mot de passe > Activer > Enregistrer .

58e3e3e23c2f16a4.png

Activer Firestore

L'application Web utilise Firestore pour enregistrer les messages de discussion et recevoir de nouveaux messages de discussion.

Activer Firestore :

  • Dans le menu Créer , cliquez sur Base de données Firestore > Créer une base de données .

99e8429832d23fa3.png

  1. Sélectionnez Démarrer en mode test , puis lisez la clause de non-responsabilité concernant les règles de sécurité. Le mode test garantit que vous pouvez écrire librement dans la base de données pendant le développement.

6be00e26c72ea032.png

  1. Cliquez sur Suivant , puis sélectionnez l'emplacement de votre base de données. Vous pouvez utiliser la valeur par défaut. Vous ne pourrez pas modifier l'emplacement ultérieurement.

278656eefcfb0216.png

  1. Cliquez sur Activer .

4. Configurer Firebase

Pour utiliser Firebase avec Flutter, vous devez effectuer les tâches suivantes pour configurer le projet Flutter afin d'utiliser correctement les bibliothèques FlutterFire :

  1. Ajoutez les dépendances FlutterFire à votre projet.
  2. Enregistrez la plateforme souhaitée sur le projet Firebase.
  3. Téléchargez le fichier de configuration spécifique à la plateforme, puis ajoutez-le au code.

Dans le répertoire de niveau supérieur de votre application Flutter, se trouvent les sous-répertoires android , ios , macos et web , qui contiennent respectivement les fichiers de configuration spécifiques à la plate-forme pour iOS et Android.

Configurer les dépendances

Vous devez ajouter les bibliothèques FlutterFire pour les deux produits Firebase que vous utilisez dans cette application : Authentification et Firestore.

  • Depuis la ligne de commande, ajoutez les dépendances suivantes :
$ flutter pub add firebase_core

Le package firebase_core est le code commun requis pour tous les plugins Firebase Flutter.

$ flutter pub add firebase_auth

Le package firebase_auth permet l'intégration avec l'authentification.

$ flutter pub add cloud_firestore

Le package cloud_firestore permet d'accéder au stockage de données Firestore.

$ flutter pub add provider

Le package firebase_ui_auth fournit un ensemble de widgets et d'utilitaires pour augmenter la vitesse des développeurs avec les flux d'authentification.

$ flutter pub add firebase_ui_auth

Vous avez ajouté les packages requis, mais vous devez également configurer les projets iOS, Android, macOS et Web Runner pour utiliser Firebase de manière appropriée. Vous utilisez également le package provider qui permet de séparer la logique métier de la logique d’affichage.

Installez la CLI FlutterFire

La CLI FlutterFire dépend de la CLI Firebase sous-jacente.

  1. Si vous ne l'avez pas déjà fait, installez la CLI Firebase sur votre ordinateur.
  2. Installez la CLI FlutterFire :
$ dart pub global activate flutterfire_cli

Une fois installée, la commande flutterfire est disponible dans le monde entier.

Configurez vos applications

La CLI extrait les informations de votre projet Firebase et des applications de projet sélectionnées pour générer toute la configuration pour une plate-forme spécifique.

À la racine de votre application, exécutez la commande configure :

$ flutterfire configure

La commande de configuration vous guide à travers les processus suivants :

  1. Sélectionnez un projet Firebase basé sur le fichier .firebaserc ou depuis la console Firebase.
  2. Déterminez les plates-formes de configuration, telles qu'Android, iOS, macOS et Web.
  3. Identifiez les applications Firebase à partir desquelles extraire la configuration. Par défaut, la CLI tente de faire correspondre automatiquement les applications Firebase en fonction de la configuration actuelle de votre projet.
  4. Générez un fichier firebase_options.dart dans votre projet.

Configurer macOS

Flutter sur macOS crée des applications entièrement en bac à sable. Comme cette application s'intègre au réseau pour communiquer avec les serveurs Firebase, vous devez configurer votre application avec les privilèges client réseau.

macos/Runner/DebugProfile.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

macos/Runner/Release.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

Pour plus d'informations, consultez Prise en charge de Flutter sur le bureau .

5. Ajouter la fonctionnalité RSVP

Maintenant que vous avez ajouté Firebase à l'application, vous pouvez créer un bouton RSVP qui enregistre les personnes avec Authentification . Pour Android natif, iOS natif et Web, il existe des packages FirebaseUI Auth prédéfinis, mais vous devez créer cette fonctionnalité pour Flutter.

Le projet que vous avez récupéré précédemment comprenait un ensemble de widgets qui implémentent l'interface utilisateur pour la majeure partie du flux d'authentification. Vous implémentez la logique métier pour intégrer l’authentification à l’application.

Ajoutez une logique métier avec le package Provider

Utilisez le package provider pour rendre un objet d'état d'application centralisé disponible dans l'arborescence des widgets Flutter de l'application :

  1. Créez un nouveau fichier nommé app_state.dart avec le contenu suivant :

lib/app_state.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

class ApplicationState extends ChangeNotifier {
  ApplicationState() {
    init();
  }

  bool _loggedIn = false;
  bool get loggedIn => _loggedIn;

  Future<void> init() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform);

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
      } else {
        _loggedIn = false;
      }
      notifyListeners();
    });
  }
}

Les instructions import introduisent Firebase Core et Auth, extraient le package provider qui rend l'objet d'état de l'application disponible dans l'arborescence des widgets et incluent les widgets d'authentification du package firebase_ui_auth .

Cet objet d’état d’application ApplicationState a une responsabilité principale pour cette étape, qui consiste à alerter l’arborescence des widgets qu’il y a eu une mise à jour vers un état authentifié.

Vous utilisez uniquement un fournisseur pour communiquer l'état de connexion d'un utilisateur à l'application. Pour permettre à un utilisateur de se connecter, vous utilisez les interfaces utilisateur fournies par le package firebase_ui_auth , ce qui constitue un excellent moyen d'amorcer rapidement les écrans de connexion dans vos applications.

Intégrer le flux d'authentification

  1. Modifiez les importations en haut du fichier lib/main.dart :

lib/main.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';               // new
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';                 // new

import 'app_state.dart';                                 // new
import 'home_page.dart';
  1. Connectez l'état de l'application à l'initialisation de l'application, puis ajoutez le flux d'authentification à HomePage :

lib/main.dart

void main() {
  // Modify from here...
  WidgetsFlutterBinding.ensureInitialized();

  runApp(ChangeNotifierProvider(
    create: (context) => ApplicationState(),
    builder: ((context, child) => const App()),
  ));
  // ...to here.
}

La modification de la fonction main() rend le package fournisseur responsable de l'instanciation de l'objet d'état de l'application avec le widget ChangeNotifierProvider . Vous utilisez cette classe provider spécifique car l'objet d'état de l'application étend la classe ChangeNotifier , qui permet au package provider de savoir quand réafficher les widgets dépendants.

  1. Mettez à jour votre application pour gérer la navigation vers les différents écrans fournis par FirebaseUI, en créant une configuration GoRouter :

lib/main.dart

// Add GoRouter configuration outside the App class
final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
      routes: [
        GoRoute(
          path: 'sign-in',
          builder: (context, state) {
            return SignInScreen(
              actions: [
                ForgotPasswordAction(((context, email) {
                  final uri = Uri(
                    path: '/sign-in/forgot-password',
                    queryParameters: <String, String?>{
                      'email': email,
                    },
                  );
                  context.push(uri.toString());
                })),
                AuthStateChangeAction(((context, state) {
                  final user = switch (state) {
                    SignedIn state => state.user,
                    UserCreated state => state.credential.user,
                    _ => null
                  };
                  if (user == null) {
                    return;
                  }
                  if (state is UserCreated) {
                    user.updateDisplayName(user.email!.split('@')[0]);
                  }
                  if (!user.emailVerified) {
                    user.sendEmailVerification();
                    const snackBar = SnackBar(
                        content: Text(
                            'Please check your email to verify your email address'));
                    ScaffoldMessenger.of(context).showSnackBar(snackBar);
                  }
                  context.pushReplacement('/');
                })),
              ],
            );
          },
          routes: [
            GoRoute(
              path: 'forgot-password',
              builder: (context, state) {
                final arguments = state.uri.queryParameters;
                return ForgotPasswordScreen(
                  email: arguments['email'],
                  headerMaxExtent: 200,
                );
              },
            ),
          ],
        ),
        GoRoute(
          path: 'profile',
          builder: (context, state) {
            return ProfileScreen(
              providers: const [],
              actions: [
                SignedOutAction((context) {
                  context.pushReplacement('/');
                }),
              ],
            );
          },
        ),
      ],
    ),
  ],
);
// end of GoRouter configuration

// Change MaterialApp to MaterialApp.router and add the routerConfig
class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Firebase Meetup',
      theme: ThemeData(
        buttonTheme: Theme.of(context).buttonTheme.copyWith(
              highlightColor: Colors.deepPurple,
            ),
        primarySwatch: Colors.deepPurple,
        textTheme: GoogleFonts.robotoTextTheme(
          Theme.of(context).textTheme,
        ),
        visualDensity: VisualDensity.adaptivePlatformDensity,
        useMaterial3: true,
      ),
      routerConfig: _router, // new
    );
  }
}

Chaque écran est associé à un type d'action différent en fonction du nouvel état du flux d'authentification. Après la plupart des changements d'état d'authentification, vous pouvez revenir à un écran préféré, qu'il s'agisse de l'écran d'accueil ou d'un autre écran, tel que le profil.

  1. Dans la méthode build de la classe HomePage , intégrez l'état de l'application au widget AuthFunc :

lib/home_page.dart

import 'package:firebase_auth/firebase_auth.dart' // new
    hide EmailAuthProvider, PhoneAuthProvider;    // new
import 'package:flutter/material.dart';           // new
import 'package:provider/provider.dart';          // new

import 'app_state.dart';                          // new
import 'src/authentication.dart';                 // new
import 'src/widgets.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          // Add from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.signOut();
                }),
          ),
          // to here
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
        ],
      ),
    );
  }
}

Vous instanciez le widget AuthFunc et l'encapsulez dans un widget Consumer . Le widget Consommateur est la manière habituelle dont le package provider peut être utilisé pour reconstruire une partie de l'arborescence lorsque l'état de l'application change. Le widget AuthFunc correspond aux widgets supplémentaires que vous testez.

Tester le flux d'authentification

cdf2d25e436bd48d.png

  1. Dans l'application, appuyez sur le bouton RSVP pour lancer le SignInScreen .

2a2cd6d69d172369.png

  1. Entrez une adresse email. Si vous êtes déjà inscrit, le système vous demande de saisir un mot de passe. Sinon, le système vous invite à remplir le formulaire d'inscription.

e5e65065dba36b54.png

  1. Saisissez un mot de passe de moins de six caractères pour vérifier le flux de gestion des erreurs. Si vous êtes inscrit, vous voyez à la place le mot de passe.
  2. Saisissez des mots de passe incorrects pour vérifier le flux de gestion des erreurs.
  3. Entrez le mot de passe correct. Vous voyez l'expérience de connexion, qui offre à l'utilisateur la possibilité de se déconnecter.

4ed811a25b0cf816.png

6. Écrivez des messages sur Firestore

C'est formidable de savoir que des utilisateurs viennent, mais vous devez donner aux invités autre chose à faire dans l'application. Et s’ils pouvaient laisser des messages dans un livre d’or ? Ils peuvent expliquer pourquoi ils sont ravis de venir ou qui ils espèrent rencontrer.

Pour stocker les messages de discussion que les utilisateurs écrivent dans l'application, vous utilisez Firestore .

Modèle de données

Firestore est une base de données NoSQL et les données stockées dans la base de données sont divisées en collections, documents, champs et sous-collections. Vous stockez chaque message du chat en tant que document dans une collection de guestbook , qui est une collection de niveau supérieur.

7c20dc8424bb1d84.png

Ajouter des messages à Firestore

Dans cette section, vous ajoutez la fonctionnalité permettant aux utilisateurs d'écrire des messages dans la base de données. Tout d’abord, vous ajoutez un champ de formulaire et un bouton d’envoi, puis vous ajoutez le code qui connecte ces éléments à la base de données.

  1. Créez un nouveau fichier nommé guest_book.dart , ajoutez un widget avec état GuestBook pour construire les éléments d'interface utilisateur d'un champ de message et d'un bouton d'envoi :

lib/guest_book.dart

import 'dart:async';

import 'package:flutter/material.dart';

import 'src/widgets.dart';

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage, super.key});

  final FutureOr<void> Function(String message) addMessage;

  @override
  State<GuestBook> createState() => _GuestBookState();
}

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Form(
        key: _formKey,
        child: Row(
          children: [
            Expanded(
              child: TextFormField(
                controller: _controller,
                decoration: const InputDecoration(
                  hintText: 'Leave a message',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Enter your message to continue';
                  }
                  return null;
                },
              ),
            ),
            const SizedBox(width: 8),
            StyledButton(
              onPressed: () async {
                if (_formKey.currentState!.validate()) {
                  await widget.addMessage(_controller.text);
                  _controller.clear();
                }
              },
              child: Row(
                children: const [
                  Icon(Icons.send),
                  SizedBox(width: 4),
                  Text('SEND'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Il y a quelques points d’intérêt ici. Tout d'abord, vous instanciez un formulaire afin de pouvoir valider que le message contient réellement du contenu et afficher à l'utilisateur un message d'erreur s'il n'y en a pas. Pour valider un formulaire, vous accédez à l'état du formulaire derrière le formulaire avec un GlobalKey . Pour plus d’informations sur les clés et comment les utiliser, consultez Quand utiliser les clés .

Notez également la façon dont les widgets sont disposés, vous avez un Row avec un TextFormField et un StyledButton , qui contient un Row . Notez également que TextFormField est enveloppé dans un widget Expanded , ce qui oblige TextFormField à remplir tout espace supplémentaire dans la ligne. Pour mieux comprendre pourquoi cela est nécessaire, consultez Comprendre les contraintes .

Maintenant que vous disposez d'un widget qui permet à l'utilisateur de saisir du texte à ajouter au livre d'or, vous devez l'afficher à l'écran.

  1. Modifiez le corps de HomePage pour ajouter les deux lignes suivantes à la fin des enfants de ListView :
const Header("What we'll be doing"),
const Paragraph(
  'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),

Bien que cela soit suffisant pour afficher le widget, cela ne suffit pas pour faire quoi que ce soit d'utile. Vous mettez à jour ce code sous peu pour le rendre fonctionnel.

Aperçu de l'application

L'écran d'accueil de l'application sur Android avec intégration du chat

L'écran d'accueil de l'application sur iOS avec intégration de chat

L'écran d'accueil de l'application sur le Web avec intégration du chat

L'écran d'accueil de l'application sur macOS avec intégration de chat

Lorsqu'un utilisateur clique sur ENVOYER , cela déclenche l'extrait de code suivant. Il ajoute le contenu du champ de saisie du message à la collection guestbook de la base de données. Plus précisément, la méthode addMessageToGuestBook ajoute le contenu du message à un nouveau document avec un ID généré automatiquement dans la collection de guestbook .

Notez que FirebaseAuth.instance.currentUser.uid est une référence à l'ID unique généré automatiquement que l'authentification donne à tous les utilisateurs connectés.

  • Dans le fichier lib/app_state.dart , ajoutez la méthode addMessageToGuestBook . Vous connecterez cette fonctionnalité à l’interface utilisateur à l’étape suivante.

lib/app_state.dart

import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

  // Add from here...
  Future<DocumentReference> addMessageToGuestBook(String message) {
    if (!_loggedIn) {
      throw Exception('Must be logged in');
    }

    return FirebaseFirestore.instance
        .collection('guestbook')
        .add(<String, dynamic>{
      'text': message,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
      'name': FirebaseAuth.instance.currentUser!.displayName,
      'userId': FirebaseAuth.instance.currentUser!.uid,
    });
  }
  // ...to here.
}

Connectez l'interface utilisateur et la base de données

Vous disposez d'une interface utilisateur dans laquelle l'utilisateur peut saisir le texte qu'il souhaite ajouter au livre d'or et vous disposez du code pour ajouter l'entrée à Firestore. Il ne vous reste plus qu'à connecter les deux.

  • Dans le fichier lib/home_page.dart , apportez la modification suivante au widget HomePage :

lib/home_page.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'app_state.dart';
import 'guest_book.dart';                         // new
import 'src/authentication.dart';
import 'src/widgets.dart';

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          Consumer<ApplicationState>(
            builder: (context, appState, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.signOut();
                }),
          ),
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
          // Modify from here...
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (appState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // ...to here.
        ],
      ),
    );
  }
}

Vous avez remplacé les deux lignes que vous avez ajoutées au début de cette étape par l'implémentation complète. Vous utilisez à nouveau Consumer<ApplicationState> pour rendre l’état de l’application disponible pour la partie de l’arborescence que vous restituez. Cela vous permet de réagir à quelqu'un qui saisit un message dans l'interface utilisateur et de le publier dans la base de données. Dans la section suivante, vous testez si les messages ajoutés sont publiés dans la base de données.

Tester l'envoi de messages

  1. Si nécessaire, connectez-vous à l'application.
  2. Saisissez un message, tel que Hey there! , puis cliquez sur ENVOYER .

Cette action écrit le message dans votre base de données Firestore. Cependant, vous ne voyez pas le message dans votre application Flutter actuelle, car vous devez toujours implémenter la récupération des données, ce que vous ferez à l'étape suivante. Cependant, dans le tableau de bord de base de données de la console Firebase, vous pouvez voir votre message ajouté dans la collection de guestbook . Si vous envoyez plus de messages, vous ajoutez plus de documents à votre collection de guestbook . Par exemple, consultez l'extrait de code suivant :

713870af0b3b63c.png

7. Lire les messages

C'est bien que les invités puissent écrire des messages dans la base de données, mais ils ne peuvent pas encore les voir dans l'application. Il est temps de réparer ça !

Synchroniser les messages

Pour afficher les messages, vous devez ajouter des écouteurs qui se déclenchent lorsque les données changent, puis créer un élément d'interface utilisateur qui affiche les nouveaux messages. Vous ajoutez du code à l'état de l'application qui écoute les messages nouvellement ajoutés depuis l'application.

  1. Créez un nouveau fichier guest_book_message.dart , ajoutez la classe suivante pour exposer une vue structurée des données que vous stockez dans Firestore.

lib/guest_book_message.dart

class GuestBookMessage {
  GuestBookMessage({required this.name, required this.message});

  final String name;
  final String message;
}
  1. Dans le fichier lib/app_state.dart , ajoutez les importations suivantes :

lib/app_state.dart

import 'dart:async';                                     // new

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';
import 'guest_book_message.dart';                        // new
  1. Dans la section de ApplicationState où vous définissez l'état et les getters, ajoutez les lignes suivantes :

lib/app_state.dart

  bool _loggedIn = false;
  bool get loggedIn => _loggedIn;

  // Add from here...
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // ...to here.
  1. Dans la section d'initialisation de ApplicationState , ajoutez les lignes suivantes pour vous abonner à une requête sur la collection de documents lorsqu'un utilisateur se connecte et se désabonner lorsqu'il se déconnecte :

lib/app_state.dart

  Future<void> init() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform);

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);
    
    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
      } else {
        _loggedIn = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
      }
      notifyListeners();
    });
  }

Cette section est importante car c'est ici que vous construisez une requête sur la collection guestbook et gérez l'abonnement et le désabonnement à cette collection. Vous écoutez le flux, où vous reconstituez un cache local des messages de la collection guestbook et stockez également une référence à cet abonnement afin de pouvoir vous en désabonner ultérieurement. Il se passe beaucoup de choses ici, vous devriez donc l'explorer dans un débogueur pour inspecter ce qui se passe afin d'obtenir un modèle mental plus clair. Pour plus d'informations, consultez Obtenir des mises à jour en temps réel avec Firestore .

  1. Dans le fichier lib/guest_book.dart , ajoutez l'import suivant :
import 'guest_book_message.dart';
  1. Dans le widget GuestBook , ajoutez une liste de messages dans le cadre de la configuration pour connecter ce changement d'état à l'interface utilisateur :

lib/guest_book.dart

class GuestBook extends StatefulWidget {
  // Modify the following line:
  const GuestBook({
    super.key, 
    required this.addMessage, 
    required this.messages,
  });

  final FutureOr<void> Function(String message) addMessage;
  final List<GuestBookMessage> messages; // new

  @override
  _GuestBookState createState() => _GuestBookState();
}
  1. Dans _GuestBookState , modifiez la méthode build comme suit pour exposer cette configuration :

lib/guest_book.dart

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  // Modify from here...
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // ...to here.
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Row(
              children: [
                Expanded(
                  child: TextFormField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: 'Leave a message',
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return 'Enter your message to continue';
                      }
                      return null;
                    },
                  ),
                ),
                const SizedBox(width: 8),
                StyledButton(
                  onPressed: () async {
                    if (_formKey.currentState!.validate()) {
                      await widget.addMessage(_controller.text);
                      _controller.clear();
                    }
                  },
                  child: Row(
                    children: const [
                      Icon(Icons.send),
                      SizedBox(width: 4),
                      Text('SEND'),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
        // Modify from here...
        const SizedBox(height: 8),
        for (var message in widget.messages)
          Paragraph('${message.name}: ${message.message}'),
        const SizedBox(height: 8),
      ],
      // ...to here.
    );
  }
}

Vous encapsulez le contenu précédent de la méthode build() avec un widget Column , puis vous ajoutez une collection for à la fin des enfants de Column pour générer un nouveau Paragraph pour chaque message de la liste des messages.

  1. Mettez à jour le corps de HomePage pour construire correctement GuestBook avec le nouveau paramètre messages :

lib/home_page.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      if (appState.loggedIn) ...[
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages, // new
        ),
      ],
    ],
  ),
),

Tester la synchronisation des messages

Firestore synchronise automatiquement et instantanément les données avec les clients abonnés à la base de données.

Tester la synchronisation des messages :

  1. Dans l'application, recherchez les messages que vous avez créés précédemment dans la base de données.
  2. Écrivez de nouveaux messages. Ils apparaissent instantanément.
  3. Ouvrez votre espace de travail dans plusieurs fenêtres ou onglets. Les messages se synchronisent en temps réel dans les fenêtres et les onglets.
  4. Facultatif : dans le menu Base de données de la console Firebase, supprimez, modifiez ou ajoutez manuellement de nouveaux messages. Toutes les modifications apparaissent dans l'interface utilisateur.

Toutes nos félicitations! Vous lisez les documents Firestore dans votre application !

Aperçu de l'application

L'écran d'accueil de l'application sur Android avec intégration du chat

L'écran d'accueil de l'application sur iOS avec intégration de chat

L'écran d'accueil de l'application sur le Web avec intégration du chat

L'écran d'accueil de l'application sur macOS avec intégration de chat

8. Mettre en place des règles de sécurité de base

Vous avez initialement configuré Firestore pour utiliser le mode test, ce qui signifie que votre base de données est ouverte aux lectures et aux écritures. Cependant, vous ne devez utiliser le mode test que pendant les premières étapes du développement. Il est recommandé de configurer des règles de sécurité pour votre base de données lorsque vous développez votre application. La sécurité fait partie intégrante de la structure et du comportement de votre application.

Les règles de sécurité Firebase vous permettent de contrôler l'accès aux documents et aux collections de votre base de données. La syntaxe flexible des règles vous permet de créer des règles qui correspondent à tout, depuis toutes les écritures dans l'ensemble de la base de données jusqu'aux opérations sur un document spécifique.

Configurez des règles de sécurité de base :

  1. Dans le menu Développement de la console Firebase, cliquez sur Base de données > Règles . Vous devriez voir les règles de sécurité par défaut suivantes et un avertissement indiquant que les règles sont publiques :

7767a2d2e64e7275.png

  1. Identifiez les collections dans lesquelles l'application écrit des données :

Dans match /databases/{database}/documents , identifiez la collection que vous souhaitez sécuriser :

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
     // You'll add rules here in the next step.
  }
}

Étant donné que vous avez utilisé l'UID d'authentification comme champ dans chaque document du livre d'or, vous pouvez obtenir l'UID d'authentification et vérifier que toute personne tentant d'écrire dans le document possède un UID d'authentification correspondant.

  1. Ajoutez les règles de lecture et d'écriture à votre ensemble de règles :
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
        if request.auth.uid == request.resource.data.userId;
    }
  }
}

Désormais, seuls les utilisateurs connectés peuvent lire les messages dans le livre d'or, mais seul l'auteur d'un message peut modifier un message.

  1. Ajoutez une validation des données pour garantir que tous les champs attendus sont présents dans le document :
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
      if request.auth.uid == request.resource.data.userId
          && "name" in request.resource.data
          && "text" in request.resource.data
          && "timestamp" in request.resource.data;
    }
  }
}

9. Étape bonus : mettez en pratique ce que vous avez appris

Enregistrer le statut RSVP d'un participant

À l'heure actuelle, votre application permet aux utilisateurs de discuter uniquement lorsqu'ils sont intéressés par l'événement. De plus, la seule façon de savoir si quelqu'un vient est lorsqu'il le dit dans le chat.

Dans cette étape, vous vous organisez et faites savoir aux gens combien de personnes viennent. Vous ajoutez quelques fonctionnalités à l’état de l’application. Le premier est la possibilité pour un utilisateur connecté de désigner s'il participe ou non. Le second est un compteur du nombre de personnes présentes.

  1. Dans le fichier lib/app_state.dart , ajoutez les lignes suivantes à la section accesseurs de ApplicationState afin que le code de l'interface utilisateur puisse interagir avec cet état :

lib/app_state.dart

int _attendees = 0;
int get attendees => _attendees;

Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
  final userDoc = FirebaseFirestore.instance
      .collection('attendees')
      .doc(FirebaseAuth.instance.currentUser!.uid);
  if (attending == Attending.yes) {
    userDoc.set(<String, dynamic>{'attending': true});
  } else {
    userDoc.set(<String, dynamic>{'attending': false});
  }
}
  1. Mettez à jour la méthode init() de ApplicationState comme suit :

lib/app_state.dart

  Future<void> init() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform);

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);

    // Add from here...
    FirebaseFirestore.instance
        .collection('attendees')
        .where('attending', isEqualTo: true)
        .snapshots()
        .listen((snapshot) {
      _attendees = snapshot.docs.length;
      notifyListeners();
    });
    // ...to here.

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
        _emailVerified = user.emailVerified;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // Add from here...
        _attendingSubscription = FirebaseFirestore.instance
            .collection('attendees')
            .doc(user.uid)
            .snapshots()
            .listen((snapshot) {
          if (snapshot.data() != null) {
            if (snapshot.data()!['attending'] as bool) {
              _attending = Attending.yes;
            } else {
              _attending = Attending.no;
            }
          } else {
            _attending = Attending.unknown;
          }
          notifyListeners();
        });
        // ...to here.
      } else {
        _loggedIn = false;
        _emailVerified = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        _attendingSubscription?.cancel(); // new
      }
      notifyListeners();
    });
  }

Ce code ajoute une requête toujours souscrite pour déterminer le nombre de participants et une deuxième requête qui n'est active que lorsqu'un utilisateur est connecté pour déterminer si l'utilisateur est présent.

  1. Ajoutez l'énumération suivante en haut du fichier lib/app_state.dart .

lib/app_state.dart

enum Attending { yes, no, unknown }
  1. Créez un nouveau fichier yes_no_selection.dart , définissez un nouveau widget qui agit comme des boutons radio :

lib/yes_no_selection.dart

import 'package:flutter/material.dart';

import 'app_state.dart';
import 'src/widgets.dart';

class YesNoSelection extends StatelessWidget {
  const YesNoSelection(
      {super.key, required this.state, required this.onSelection});
  final Attending state;
  final void Function(Attending selection) onSelection;

  @override
  Widget build(BuildContext context) {
    switch (state) {
      case Attending.yes:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              FilledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              TextButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      case Attending.no:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              TextButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              FilledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      default:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              StyledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              StyledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
    }
  }
}

Il démarre dans un état indéterminé sans aucune sélection de Oui ou de Non . Une fois que l'utilisateur a choisi s'il participe, vous affichez cette option en surbrillance avec un bouton rempli et l'autre option recule avec un rendu plat.

  1. Mettez à jour la méthode build() de HomePage pour profiter de YesNoSelection , permettre à un utilisateur connecté de désigner s'il participe et d'afficher le nombre de participants à l'événement :

lib/home_page.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here...
      switch (appState.attendees) {
        1 => const Paragraph('1 person going'),
        >= 2 => Paragraph('${appState.attendees} people going'),
        _ => const Paragraph('No one going'),
      },
      // ...to here.
      if (appState.loggedIn) ...[
        // Add from here...
        YesNoSelection(
          state: appState.attending,
          onSelection: (attending) => appState.attending = attending,
        ),
        // ...to here.
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages,
        ),
      ],
    ],
  ),
),

Ajouter des règles

Vous avez déjà défini certaines règles, donc les données que vous ajoutez avec les boutons seront rejetées. Vous devez mettre à jour les règles pour autoriser les ajouts à la collection attendees .

  1. Dans la collection attendees , récupérez l'UID d'authentification que vous avez utilisé comme nom du document et vérifiez que l' uid de l'expéditeur est le même que celui du document qu'il rédige :
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

Cela permet à tout le monde de lire la liste des participants car il n'y a pas de données privées, mais seul le créateur peut la mettre à jour.

  1. Ajoutez une validation des données pour garantir que tous les champs attendus sont présents dans le document :
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId
          && "attending" in request.resource.data;

    }
  }
}
  1. Facultatif : dans l'application, cliquez sur les boutons pour voir les résultats dans le tableau de bord Firestore de la console Firebase.

Aperçu de l'application

L'écran d'accueil de l'application sur Android

L'écran d'accueil de l'application sur iOS

L'écran d'accueil de l'application sur le Web

L'écran d'accueil de l'application sur macOS

10. Félicitations !

Vous avez utilisé Firebase pour créer une application Web interactive en temps réel !

Apprendre encore plus