1. Avant de commencer
Dans cet atelier de programmation, vous apprendrez certaines des bases de Firebase pour créer des applications mobiles Flutter pour Android et iOS.
Conditions préalables
Cet atelier de programmation suppose que vous connaissez Flutter et que vous avez installé le SDK Flutter et un éditeur .
Ce que vous allez créer
Dans cet atelier de programmation, vous allez créer une application de chat RSVP d'événement et de livre d'or sur Android, iOS, le Web et macOS à l'aide de Flutter. Vous authentifierez les utilisateurs avec Firebase Authentication et synchroniserez les données à l'aide de Cloud Firestore.
Ce dont vous aurez besoin
Vous pouvez exécuter cet atelier de programmation à l'aide de l'un des appareils suivants :
- Un appareil physique (Android ou iOS) connecté à votre ordinateur et défini en mode développeur.
- Le simulateur iOS. (Nécessite l'installation des outils Xcode .)
- L'émulateur Android. (Nécessite une configuration dans Android Studio .)
En plus de ce qui précède, vous aurez également besoin de :
- Un navigateur de votre choix, tel que Chrome.
- Un IDE ou un éditeur de texte de votre choix, comme Android Studio ou VS Code configuré avec les plugins Dart et Flutter.
- La dernière version
stable
de Flutter (oubeta
si vous aimez vivre à la limite). - Un compte Google, comme un compte gmail, pour créer et gérer votre projet Firebase.
- L' outil de ligne de commande
firebase
, connecté à votre compte gmail. - L'exemple de code du codelab. Voir l'étape suivante pour savoir comment obtenir le code.
2. Obtenez l'exemple de code
Commençons par télécharger la version initiale de notre projet depuis GitHub.
Clonez le dépôt GitHub à partir de la ligne de commande :
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Alternativement, si vous avez installé l'outil cli de GitHub :
gh repo clone flutter/codelabs flutter-codelabs
L'exemple de code doit être cloné dans le répertoire flutter-codelabs
, qui contient le code d'une collection de codelabs. Le code de ce laboratoire de programmation se trouve dans flutter-codelabs/firebase-get-to-know-flutter
.
La structure de répertoire sous flutter-codelabs/firebase-get-to-know-flutter
est une série d'instantanés de l'endroit où vous devriez être à la fin de chaque étape nommée. Il s'agit de l'étape 2, donc localiser les fichiers correspondants est aussi simple que :
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
Si vous souhaitez avancer ou voir à quoi quelque chose devrait ressembler 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 flutter-codelabs/firebase-get-to-know-flutter/step_02
dans votre IDE préféré. Ce répertoire contient le code de démarrage du laboratoire de programmation qui consiste en une application de rencontre Flutter pas encore fonctionnelle.
Localisez les fichiers sur lesquels travailler
Le code de cette application est réparti sur plusieurs répertoires. Cette répartition des fonctionnalités est conçue pour faciliter le travail, en regroupant le code par fonctionnalité.
Localisez les fichiers suivants dans le projet :
-
lib/main.dart
: Ce fichier contient le point d'entrée principal et le widget de l'application. -
lib/src/widgets.dart
: Ce fichier contient une poignée de widgets pour aider à standardiser le style de l'application. Ceux-ci sont utilisés pour composer l'écran de l'application de démarrage. -
lib/src/authentication.dart
: ce fichier contient une implémentation partielle de FirebaseUI Auth avec un ensemble de widgets pour créer une expérience utilisateur de connexion pour l'authentification basée sur les e-mails Firebase. Ces widgets pour le flux d'authentification ne sont pas encore utilisés dans l'application de démarrage, mais vous les connecterez bientôt.
Vous ajouterez des fichiers supplémentaires si nécessaire pour développer le reste de l'application.
Examen du fichier lib/main.dart
Cette application tire parti du package google_fonts
pour nous permettre de faire de Roboto la police par défaut dans toute l'application. Un exercice pour le lecteur motivé consiste à 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 de lib/src/widgets.dart
sous la forme de Header
, Paragraph
et IconAndDetail
. Ces widgets réduisent l'encombrement de la mise en page décrite dans HomePage
en éliminant le code dupliqué. Cela a l'avantage supplémentaire de permettre une apparence et une sensation cohérentes.
Voici à quoi ressemble votre application sur Android, iOS, le Web et macOS :
Aperçu de l'application
3. Créer et configurer un projet Firebase
L'affichage des informations sur l'événement est idéal pour vos invités, mais le simple fait d'afficher les événements n'est très utile pour personne. Ajoutons quelques fonctionnalités dynamiques à cette application. Pour cela, vous devrez connecter Firebase à votre application. Pour démarrer avec Firebase, vous devez créer et configurer un projet Firebase.
Créer un projet Firebase
- Connectez-vous à Firebase .
- Dans la console Firebase, cliquez sur Ajouter un projet (ou Créer un projet ) et nommez votre projet Firebase Firebase-Flutter-Codelab .
- Cliquez sur les options de création de projet. Acceptez les conditions de Firebase si vous y êtes invité. Ignorez la configuration de Google Analytics, car vous n'utiliserez pas Analytics pour cette application.
Pour en savoir plus sur les projets Firebase, consultez Comprendre les projets Firebase .
L'application que vous créez utilise plusieurs produits Firebase disponibles pour les applications Web :
- Firebase Authentication pour permettre à vos utilisateurs de se connecter à votre application.
- Cloud Firestore pour enregistrer des données structurées sur le cloud et recevoir une notification instantanée lorsque les données changent.
- Règles de sécurité Firebase pour sécuriser votre base de données.
Certains de ces produits nécessitent une configuration spéciale ou doivent être activés à l'aide de la console Firebase.
Activer la connexion par e-mail pour l'authentification Firebase
Pour autoriser les utilisateurs à se connecter à l'application Web, vous utiliserez la méthode de connexion par e-mail/mot de passe pour cet atelier de programmation :
- Dans la console Firebase, développez le menu Build dans le panneau de gauche.
- Cliquez sur Authentification , puis cliquez sur le bouton Commencer , puis sur l'onglet Méthode de connexion (ou cliquez ici pour accéder directement à l'onglet Méthode de connexion).
- Cliquez sur E-mail/Mot de passe dans la liste Fournisseurs de connexion, réglez le commutateur Activer sur la position Activé, puis cliquez sur Enregistrer .
Activer Cloud Firestore
L'application Web utilise Cloud Firestore pour enregistrer les messages de chat et recevoir de nouveaux messages de chat.
Activez Cloud Firestore :
- Dans la section Build de la console Firebase, cliquez sur Cloud Firestore .
- Cliquez sur Créer une base de données .
- Sélectionnez l'option Démarrer en mode test . Lisez l'avertissement sur 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. Cliquez sur Suivant .
- Sélectionnez l'emplacement de votre base de données (vous pouvez simplement utiliser la valeur par défaut). Notez que cet emplacement ne peut pas être modifié ultérieurement.
- Cliquez sur Activer .
4. Configuration Firebase
Pour utiliser Firebase avec Flutter, vous devez suivre un processus pour configurer le projet Flutter afin d'utiliser correctement les bibliothèques FlutterFire :
- Ajoutez les dépendances FlutterFire à votre projet
- Enregistrez la plate-forme souhaitée sur le projet Firebase
- Téléchargez le fichier de configuration spécifique à la plate-forme et ajoutez-le au code.
Dans le répertoire de niveau supérieur de votre application Flutter, il existe des sous-répertoires appelés android
, ios
, macos
et web
. Ces répertoires contiennent les fichiers de configuration spécifiques à la plate-forme pour iOS et Android, respectivement.
Configurer les dépendances
Vous devez ajouter les bibliothèques FlutterFire pour les deux produits Firebase que vous utilisez dans cette application - Firebase Auth et Cloud Firestore. Exécutez les trois commandes suivantes pour ajouter les dépendances.
$ flutter pub add firebase_core Resolving dependencies... + firebase_core 1.10.5 + firebase_core_platform_interface 4.2.2 + firebase_core_web 1.5.2 + flutter_web_plugins 0.0.0 from sdk flutter + js 0.6.3 test_api 0.4.3 (0.4.8 available) Changed 5 dependencies!
Le firebase_core
est le code commun requis pour tous les plugins Firebase Flutter.
$ flutter pub add firebase_auth Resolving dependencies... + firebase_auth 3.3.3 + firebase_auth_platform_interface 6.1.8 + firebase_auth_web 3.3.4 + intl 0.17.0 test_api 0.4.3 (0.4.8 available) Changed 4 dependencies!
firebase_auth
permet l'intégration avec la capacité d'authentification de Firebase.
$ flutter pub add cloud_firestore Resolving dependencies... + cloud_firestore 3.1.4 + cloud_firestore_platform_interface 5.4.9 + cloud_firestore_web 2.6.4 test_api 0.4.3 (0.4.8 available) Changed 3 dependencies!
Le cloud_firestore
permet d'accéder au stockage de données Cloud Firestore.
$ flutter pub add provider Resolving dependencies... + nested 1.0.0 + provider 6.0.1 test_api 0.4.3 (0.4.8 available) Changed 2 dependencies!
Bien que vous ayez ajouté les packages requis, vous devez également configurer les projets iOS, Android, macOS et Web Runner pour utiliser correctement Firebase. Vous utilisez également le package de provider
qui permettra de séparer la logique métier de la logique d'affichage.
Installation flutterfire
La CLI FlutterFire dépend de la CLI Firebase sous-jacente. Si vous ne l'avez pas déjà fait, assurez-vous que la CLI Firebase est installée sur votre machine.
Ensuite, installez la CLI FlutterFire en exécutant la commande suivante :
$ dart pub global activate flutterfire_cli
Une fois installée, la commande flutterfire
sera disponible dans le monde entier.
Configuration de 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 d'une plate-forme spécifique.
À la racine de votre application, exécutez la commande configure :
$ flutterfire configure
La commande de configuration vous guidera à travers un certain nombre de processus :
- Sélection d'un projet Firebase (basé sur le fichier .firebaserc ou depuis la console Firebase).
- Indiquez les plates-formes (par exemple, Android, iOS, macOS et Web) pour lesquelles vous souhaitez configurer.
- Identifiez les applications Firebase pour les plates-formes choisies qui doivent être utilisées pour extraire la configuration. Par défaut, la CLI tentera de faire correspondre automatiquement les applications Firebase en fonction de la configuration actuelle de votre projet.
- 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 en utilisant le réseau pour communiquer avec les serveurs Firebase, vous devrez configurer votre application avec les privilèges du 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>
Voir Autorisations et App Sandbox pour plus de détails.
5. Ajouter une connexion utilisateur (RSVP)
Maintenant que vous avez ajouté Firebase à l'application, vous pouvez configurer un bouton RSVP qui enregistre les personnes utilisant Firebase Authentication . Pour Android natif, iOS natif et Web, il existe des packages FirebaseUI Auth prédéfinis, mais pour Flutter, vous devrez créer cette capacité.
Le projet que vous avez récupéré à l'étape 2 comprenait un ensemble de widgets qui implémente l'interface utilisateur pour la majeure partie du flux d'authentification. Vous implémenterez la logique métier pour intégrer Firebase Authentication dans l'application.
Logique métier avec fournisseur
Vous allez utiliser le package de provider
pour rendre un objet d'état d'application centralisé disponible dans l'arborescence des widgets Flutter de l'application. Pour commencer, modifiez les importations en haut de lib/main.dart
:
lib/main.dart
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart'; // new
import 'firebase_options.dart'; // new
import 'src/authentication.dart'; // new
import 'src/widgets.dart';
Les lignes import
introduisent Firebase Core et Auth, extraient le package de provider
que vous utilisez pour rendre l'objet d'état de l'application disponible via l'arborescence des widgets et incluent les widgets d'authentification de lib/src
.
Cet objet d'état d'application, ApplicationState
, a deux responsabilités principales pour cette étape, mais acquerra des responsabilités supplémentaires au fur et à mesure que vous ajouterez des fonctionnalités à l'application dans les étapes ultérieures. La première responsabilité est d'initialiser la bibliothèque Firebase avec un appel à Firebase.initializeApp()
, puis il y a la gestion du flux d'autorisation. Ajoutez la classe suivante à la fin de lib/main.dart
:
lib/main.dart
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
} else {
_loginState = ApplicationLoginState.loggedOut;
}
notifyListeners();
});
}
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
void startLoginFlow() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> verifyEmail(
String email,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
var methods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
if (methods.contains('password')) {
_loginState = ApplicationLoginState.password;
} else {
_loginState = ApplicationLoginState.register;
}
_email = email;
notifyListeners();
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
Future<void> signInWithEmailAndPassword(
String email,
String password,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void cancelRegistration() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> registerAccount(
String email,
String displayName,
String password,
void Function(FirebaseAuthException e) errorCallback) async {
try {
var credential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
await credential.user!.updateDisplayName(displayName);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void signOut() {
FirebaseAuth.instance.signOut();
}
}
Il convient de noter quelques points clés dans cette classe. L'utilisateur démarre non authentifié, l'application affiche un formulaire demandant l'adresse e-mail de l'utilisateur, selon que cette adresse e-mail est enregistrée, l'application demandera soit à l'utilisateur de s'inscrire, soit de demander son mot de passe, puis en supposant que tout fonctionne, l'utilisateur est authentifié.
Il convient de noter qu'il ne s'agit pas d'une implémentation complète du flux FirebaseUI Auth, car il ne gère pas le cas d'un utilisateur avec un compte existant qui rencontre des difficultés pour se connecter. L'implémentation de cette fonctionnalité supplémentaire est laissée à l'exercice. lecteur motivé.
Intégration du flux d'authentification
Maintenant que vous avez le début de l'état de l'application, il est temps de relier l'état de l'application à l'initialisation de l'application et d'ajouter le flux d'authentification dans HomePage
. Mettez à jour le point d'entrée principal pour intégrer l'état de l'application via le package du provider
:
lib/main.dart
void main() {
// Modify from here
runApp(
ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: (context, _) => App(),
),
);
// to here.
}
La modification de la fonction main
rend le package du fournisseur responsable de l'instanciation de l'objet d'état de l'application à l'aide du widget ChangeNotifierProvider
. Vous utilisez cette classe de fournisseur spécifique car l'objet d'état de l'application étend ChangeNotifier
et cela permet au package du provider
de savoir quand réafficher les widgets dépendants. Enfin, intégrez l'état de l'application à l' Authentication
en mettant à jour la méthode de build
de HomePage
:
lib/main.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'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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 Authentication
et l'encapsulez dans un widget Consumer
. Le widget Consommateur est la façon habituelle dont le package du provider
peut être utilisé pour reconstruire une partie de l'arborescence lorsque l'état de l'application change. Le widget d' Authentication
est l'interface utilisateur d'authentification que vous allez maintenant tester.
Test du flux d'authentification
Voici le début du flux d'authentification, où l'utilisateur peut appuyer sur le bouton RSVP pour lancer le formulaire de courrier électronique.
Lors de la saisie de l'e-mail, le système confirme si l'utilisateur est déjà enregistré, auquel cas l'utilisateur est invité à entrer un mot de passe, sinon si l'utilisateur n'est pas enregistré, il passe par le formulaire d'inscription.
Assurez-vous d'essayer de saisir un mot de passe court (moins de six caractères) pour vérifier le flux de traitement des erreurs. Si l'utilisateur est enregistré, il verra le mot de passe pour à la place.
Sur cette page, assurez-vous de saisir des mots de passe incorrects pour vérifier la gestion des erreurs sur cette page. Enfin, une fois que l'utilisateur est connecté, vous verrez l'expérience de connexion qui offre à l'utilisateur la possibilité de se déconnecter à nouveau.
Et avec cela, vous avez implémenté un flux d'authentification. Bravo!
6. Écrivez des messages sur Cloud Firestore
Savoir que les utilisateurs arrivent, c'est bien, mais donnons aux invités autre chose à faire dans l'application. Et s'ils pouvaient laisser des messages dans un livre d'or ? Ils peuvent partager pourquoi ils sont ravis de venir ou qui ils espèrent rencontrer.
Pour stocker les messages de chat que les utilisateurs écrivent dans l'application, vous utiliserez Cloud Firestore .
Modèle de données
Cloud 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 stockerez chaque message du chat en tant que document dans une collection de niveau supérieur appelée guestbook
d'or .
Ajouter des messages à Firestore
Dans cette section, vous allez ajouter la fonctionnalité permettant aux utilisateurs d'écrire de nouveaux messages dans la base de données. Tout d'abord, vous ajoutez les éléments de l'interface utilisateur (champ de formulaire et bouton d'envoi), puis vous ajoutez le code qui relie ces éléments à la base de données.
Tout d'abord, ajoutez des importations pour le package cloud_firestore
et dart:async
.
lib/main.dart
import 'dart:async'; // new
import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'firebase_options.dart';
import 'src/authentication.dart';
import 'src/widgets.dart';
Pour construire les éléments d'interface utilisateur d'un champ de message et d'un bouton d'envoi, ajoutez un nouveau widget avec état GuestBook
au bas de lib/main.dart
.
lib/main.dart
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage});
final FutureOr<void> Function(String message) addMessage;
@override
_GuestBookState 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 que vous puissiez valider que le message a réellement du contenu et montrer à l'utilisateur un message d'erreur s'il n'y en a pas. La façon de valider un formulaire consiste à accéder à l'état du formulaire derrière le formulaire, et pour cela, vous utilisez un GlobalKey
. Pour plus d'informations sur les clés et comment les utiliser, veuillez consulter l' épisode Flutter Widgets 101 "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 lui-même un Row
. Notez également que TextFormField
est enveloppé dans un widget Expanded
, ce qui force TextFormField
à occuper tout espace supplémentaire dans la ligne. Pour mieux comprendre pourquoi cela est nécessaire, veuillez lire Comprendre les contraintes .
Maintenant que vous avez un widget qui permet à l'utilisateur de saisir du texte à ajouter au livre d'or, vous devez l'afficher à l'écran. Pour ce faire, modifiez le corps de HomePage
pour ajouter les deux lignes suivantes au bas 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 mettrez à jour ce code sous peu pour le rendre fonctionnel.
Aperçu de l'application
Un utilisateur cliquant sur le bouton ENVOYER déclenchera l'extrait de code ci-dessous. Il ajoute le contenu du champ de saisie du message à la collection de livres d' 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) à la collection de livres d' guestbook
.
Notez que FirebaseAuth.instance.currentUser.uid
est une référence à l'ID unique généré automatiquement que Firebase Authentication donne à tous les utilisateurs connectés.
Apportez une autre modification au fichier lib/main.dart
. Ajoutez la méthode addMessageToGuestBook
. Vous relierez l'interface utilisateur et cette fonctionnalité à l'étape suivante.
lib/main.dart
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here
Future<DocumentReference> addMessageToGuestBook(String message) {
if (_loginState != ApplicationLoginState.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
}
Câblage de l'interface utilisateur dans 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 à Cloud Firestore. Maintenant, tout ce que vous avez à faire est de câbler les deux ensemble. Dans lib/main.dart
apportez la modification suivante au widget HomePage
.
lib/main.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, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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.loginState == ApplicationLoginState.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 rendez. Cela vous permet de réagir à la saisie d'un message dans l'interface utilisateur et de le publier dans la base de données. Dans la section suivante, vous testerez si les messages ajoutés sont publiés dans la base de données.
Tester l'envoi de messages
- Assurez-vous que vous êtes connecté à l'application.
- Entrez un message tel que "Salut !", puis cliquez sur ENVOYER .
Cette action écrit le message dans votre base de données Cloud Firestore. Cependant, vous ne verrez pas encore le message dans votre application Flutter réelle car vous devez toujours implémenter la récupération des données. Vous le ferez à l'étape suivante.
Mais vous pouvez voir le message nouvellement ajouté dans la console Firebase.
Dans la console Firebase, dans le tableau de bord de la base de données, vous devriez voir la collection de livres d' guestbook
avec votre message nouvellement ajouté. Si vous continuez à envoyer des messages, votre collection de livres d'or contiendra de nombreux documents, comme celui-ci :
Console Firebase
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. Réparons ç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 allez ajouter du code à l'état de l'application qui écoute les nouveaux messages ajoutés à partir de l'application.
Juste au-dessus du widget GuestBook
, la classe de valeur suivante. Cette classe expose une vue structurée des données que vous stockez dans Cloud Firestore.
lib/main.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
Dans la section d' ApplicationState
où vous définissez l'état et les getters, ajoutez les nouvelles lignes suivantes :
lib/main.dart
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
// Add from here
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// to here.
Et enfin, dans la section d'initialisation de ApplicationState
, ajoutez ce qui suit pour vous abonner à une requête sur la collection de documents lorsqu'un utilisateur se connecte et vous désabonner lorsqu'il se déconnecte.
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
// Add from here
_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();
});
// to here.
} else {
_loginState = ApplicationLoginState.loggedOut;
// Add from here
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
notifyListeners();
});
}
Cette section est importante, car c'est ici que vous construisez une requête sur la collection de livres d' guestbook
et que vous gérez l'abonnement et le désabonnement à cette collection. Vous écoutez le flux, où vous reconstruisez un cache local des messages dans la collection de livres d' guestbook
, et stockez également une référence à cet abonnement afin que vous puissiez vous en désabonner ultérieurement. Il se passe beaucoup de choses ici, et cela vaut la peine de passer du temps dans un débogueur à inspecter ce qui se passe pour obtenir un modèle mental plus clair.
Pour plus d'informations, consultez la documentation Cloud Firestore .
Dans le widget GuestBook
, vous devez connecter cet état changeant à l'interface utilisateur. Vous modifiez le widget en ajoutant une liste de messages dans le cadre de sa configuration.
lib/main.dart
class GuestBook extends StatefulWidget {
// Modify the following line
const GuestBook({required this.addMessage, required this.messages});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
_GuestBookState createState() => _GuestBookState();
}
Ensuite, nous exposons cette nouvelle configuration dans _GuestBookState
en modifiant la méthode de build
comme suit.
lib/main.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 à la fin des enfants de Column
, vous ajoutez une collection pour générer un nouveau Paragraph
pour chaque message de la liste des messages.
Enfin, vous devez maintenant mettre à jour le corps de HomePage
pour construire correctement GuestBook
avec le nouveau paramètre messages
.
lib/main.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages, // new
),
],
],
),
),
Tester la synchronisation des messages
Cloud Firestore synchronise automatiquement et instantanément les données avec les clients abonnés à la base de données.
- Les messages que vous avez créés précédemment dans la base de données doivent être affichés dans l'application. N'hésitez pas à écrire de nouveaux messages; ils devraient apparaître instantanément.
- Si vous ouvrez votre espace de travail dans plusieurs fenêtres ou onglets, les messages seront synchronisés en temps réel entre les onglets.
- (Facultatif) Vous pouvez essayer de supprimer, de modifier ou d'ajouter manuellement de nouveaux messages directement dans la section Base de données de la console Firebase ; toute modification doit apparaître dans l'interface utilisateur.
Toutes nos félicitations! Vous lisez des documents Cloud Firestore dans votre application !
Examen de l' application p
8. Mettre en place des règles de sécurité de base
Vous avez initialement configuré Cloud Firestore pour utiliser le mode test, ce qui signifie que votre base de données est ouverte en lecture et en écriture. Cependant, vous ne devez utiliser le mode test qu'aux toutes premières étapes du développement. La meilleure pratique consiste à configurer des règles de sécurité pour votre base de données lorsque vous développez votre application. La sécurité doit faire partie intégrante de la structure et du comportement de votre application.
Les règles de sécurité vous permettent de contrôler l'accès aux documents et aux collections de votre base de données. La syntaxe des règles flexibles vous permet de créer des règles qui correspondent à n'importe quoi, de toutes les écritures dans l'ensemble de la base de données aux opérations sur un document spécifique.
Vous pouvez écrire des règles de sécurité pour Cloud Firestore dans la console Firebase :
- Dans la section Développer de la console Firebase, cliquez sur Base de données , puis sélectionnez l'onglet Règles (ou cliquez ici pour accéder directement à l'onglet Règles ).
- Vous devriez voir les règles de sécurité par défaut suivantes, ainsi qu'un avertissement indiquant que les règles sont publiques.
Identifier les collections
Tout d'abord, 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.
}
}
Ajouter des règles de sécurité
Étant donné que vous avez utilisé l'UID d'authentification comme champ dans chaque document de livre d'or, vous pouvez obtenir l'UID d'authentification et vérifier que toute personne tentant d'écrire dans le document dispose d'un UID d'authentification correspondant.
Ajoutez les règles de lecture et d'écriture à votre ensemble de règles comme indiqué ci-dessous :
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, pour le livre d'or, seuls les utilisateurs connectés peuvent lire les messages (n'importe quel message !), mais seul l'auteur d'un message peut modifier un message.
Ajouter des règles de validation
Ajoutez la validation des données pour vous assurer 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 : pratiquez ce que vous avez appris
Enregistrer le statut RSVP d'un participant
Pour le moment, votre application permet simplement aux utilisateurs de commencer à discuter s'ils sont intéressés par l'événement. De plus, la seule façon de savoir si quelqu'un vient est de le publier dans le chat. Organisons-nous et informons les gens du nombre de personnes qui viennent.
Vous allez ajouter quelques nouvelles fonctionnalités à l'état de l'application. Le premier est la possibilité pour un utilisateur connecté de désigner s'il participe ou non. La deuxième capacité est un compteur du nombre de personnes réellement présentes.
Dans lib/main.dart
, ajoutez ce qui suit à la section des accesseurs pour permettre au code de l'interface utilisateur d'interagir avec cet état :
lib/main.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});
}
}
Mettez à jour la méthode init
de ApplicationState
comme suit :
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// 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) {
_loginState = ApplicationLoginState.loggedIn;
_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 {
_loginState = ApplicationLoginState.loggedOut;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
_attendingSubscription?.cancel(); // new
}
notifyListeners();
});
}
Ce qui précède ajoute une requête toujours abonnée pour connaître le nombre de participants, et une deuxième requête qui n'est active que lorsqu'un utilisateur est connecté pour savoir si l'utilisateur participe. Ensuite, ajoutez l'énumération suivante après la déclaration GuestBookMessage
:
lib/main.dart
enum Attending { yes, no, unknown }
Vous allez maintenant définir un nouveau widget qui agit comme les anciens boutons radio. Il commence dans un état indéterminé, sans oui ni non sélectionné, mais une fois que l'utilisateur sélectionne s'il participe ou non, vous affichez cette option en surbrillance avec un bouton rempli et l'autre option recule avec un rendu plat.
lib/main.dart
class YesNoSelection extends StatelessWidget {
const YesNoSelection({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: [
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
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),
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
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'),
),
],
),
);
}
}
}
Ensuite, vous devez mettre à jour la méthode de construction de HomePage
pour tirer parti de YesNoSelection
, permettant à un utilisateur connecté de désigner s'il participe. Vous afficherez également le nombre de participants à cet événement.
lib/main.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Add from here
if (appState.attendees >= 2)
Paragraph('${appState.attendees} people going')
else if (appState.attendees == 1)
const Paragraph('1 person going')
else
const Paragraph('No one going'),
// To here.
if (appState.loginState == ApplicationLoginState.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
Étant donné que vous avez déjà configuré certaines règles, les nouvelles données que vous ajoutez avec les boutons seront rejetées. Vous devrez mettre à jour les règles pour autoriser l'ajout à la collection de attendees
.
Pour la collection des attendees
, puisque vous avez utilisé l'UID d'authentification comme nom de document, vous pouvez le récupérer et vérifier que l' uid
de l'émetteur est le même que le document qu'il écrit. Vous autoriserez tout le monde à lire la liste des participants (puisqu'il n'y a pas de données privées), mais seul le créateur devrait pouvoir la mettre à jour.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
Ajouter des règles de validation
Ajoutez la validation des données pour vous assurer 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;
}
}
}
(Facultatif) Vous pouvez maintenant afficher les résultats en cliquant sur les boutons. Accédez à votre tableau de bord Cloud Firestore dans la console Firebase.
Aperçu de l'application
10. Félicitations !
Vous avez utilisé Firebase pour créer une application Web interactive en temps réel !
Ce que nous avons couvert
- Authentification Firebase
- Cloud Firestore
- Règles de sécurité Firebase
Prochaines étapes
- Vous souhaitez en savoir plus sur les autres produits Firebase ? Peut-être souhaitez-vous stocker des fichiers image que les utilisateurs téléchargent ? Ou envoyer des notifications à vos utilisateurs ? Consultez la documentation Firebase . Vous voulez en savoir plus sur les plugins Flutter pour Firebase ? Consultez FlutterFire pour plus d'informations.
- Vous voulez en savoir plus sur Cloud Firestore ? Peut-être souhaitez-vous en savoir plus sur les sous-collections et les transactions ? Rendez-vous sur l'atelier de programmation Web Cloud Firestore pour un atelier de programmation approfondi sur Cloud Firestore. Ou consultez cette série YouTube pour découvrir Cloud Firestore !
Apprendre encore plus
- Site Firebase : firebase.google.com
- Site Flutter : flutter.dev
- Widgets FlutterFire Firebase Flutter : firebase.flutter.dev
- Chaîne YouTube Firebase
- Chaîne YouTube flottante
Comment c'était?
Nous aimerions vos retours. Merci de remplir un (très) court formulaire ici .