1. Avant de commencer
Dans cet atelier de programmation, vous allez découvrir les bases de Firebase pour créer des applications mobiles Flutter pour Android et iOS.
Prérequis
- Connaissances de base sur Flutter
- SDK Flutter
- Un éditeur de texte de votre choix
Points abordés
- Découvrez comment créer une application de chat pour les RSVP et le livre d'or d'un événement sur Android, iOS, le Web et macOS avec Flutter.
- Découvrez comment authentifier des utilisateurs avec Firebase Authentication et synchroniser des données avec Firestore.
Prérequis
L'un des appareils suivants :
- Un appareil Android ou iOS physique connecté à votre ordinateur et réglé en mode développeur
- Le simulateur iOS (outils Xcode requis)
- L'émulateur Android (qui doit être configuré dans Android Studio)
Vous devez également disposer 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 plug-ins Dart et Flutter, tel qu'Android Studio ou Visual Studio Code.
- La dernière version
stable
de Flutter oubeta
si vous aimez prendre des risques. - Un compte Google pour créer et gérer votre projet Firebase.
- La CLI
Firebase
est connectée à votre compte Google.
2. Obtenir l'exemple de code
Téléchargez la version initiale de votre projet depuis GitHub :
- Dans 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 votre projet devrait ressembler à la fin de chaque étape. Par exemple, vous êtes à la deuxième étape.
- Recherchez les fichiers correspondants pour la deuxième étape :
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
Si vous souhaitez passer à la suite ou voir à quoi doit ressembler un élément après une étape, consultez le répertoire portant le nom de 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 l'IDE de votre choix. Ce répertoire contient le code de départ de l'atelier de programmation, qui correspond à une application de meetup Flutter non fonctionnelle.
Localiser les fichiers à modifier
Le code de cette application est réparti dans 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 d'application.lib/home_page.dart
: ce fichier contient le widget de la page d'accueil.lib/src/widgets.dart
: ce fichier contient un certain nombre 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 permettant de créer une expérience de connexion pour l'authentification Firebase basée sur l'adresse e-mail. 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.
Ajoutez d'autres fichiers si nécessaire pour créer le reste de l'application.
Examiner le fichier lib/main.dart
Cette application utilise le package google_fonts
pour définir Roboto comme police par défaut dans toute l'application. Vous pouvez explorer fonts.google.com et utiliser les polices que vous y trouverez dans différentes parties de l'application.
Vous utilisez les widgets d'assistance du fichier lib/src/widgets.dart
sous la forme Header
, Paragraph
et IconAndDetail
. Ces widgets éliminent le code en double pour réduire l'encombrement de la mise en page décrite dans HomePage
. Cela permet également d'obtenir une apparence cohérente.
Voici à quoi ressemble votre application sur Android, iOS, le Web et macOS :
3. Créer et configurer un projet Firebase
L'affichage des informations sur les événements est idéal pour vos invités, mais il n'est pas très utile en soi. Vous devez ajouter des fonctionnalités dynamiques à l'application. Pour ce faire, vous devez associer Firebase à votre application. Pour commencer à utiliser Firebase, vous devez créer et configurer un projet Firebase.
Créer un projet Firebase
- Connectez-vous à la console Firebase à l'aide de votre compte Google.
- Cliquez sur le bouton pour créer un projet, puis saisissez un nom de projet (par exemple,
Firebase-Flutter-Codelab
).
- Cliquez sur Continuer.
- Si vous y êtes invité, lisez et acceptez les Conditions d'utilisation de Firebase, puis cliquez sur Continuer.
- (Facultatif) Activez l'assistance IA dans la console Firebase (appelée "Gemini dans Firebase").
- Pour cet atelier de programmation, vous n'avez pas besoin de Google Analytics. Désactivez donc l'option Google Analytics.
- Cliquez sur Créer un projet, attendez que votre projet soit provisionné, puis cliquez sur Continuer.
Pour en savoir plus sur les projets Firebase, consultez Comprendre les projets Firebase.
Configurer les produits Firebase
L'application utilise les produits Firebase suivants, qui sont disponibles pour les applications Web :
- Authentification : permet aux utilisateurs de se connecter à votre application.
- Firestore : permet de sauvegarder des données structurées sur le cloud et d'être notifié instantanément en cas de modification des données.
- Règles de sécurité Firebase : sécurisent votre base de données.
Certains de ces produits nécessitent une configuration particulière ou doivent être activés dans la console Firebase.
Activer l'authentification par connexion avec une adresse e-mail
- Dans le volet Vue d'ensemble du projet de la console Firebase, développez le menu Créer.
- Cliquez sur Authentification > Commencer > Méthode de connexion > E-mail/Mot de passe > Activer > Enregistrer.
Configurer Firestore
L'application Web utilise Firestore pour enregistrer des messages de chat et en recevoir.
Pour configurer Firestore dans votre projet Firebase :
- Dans le panneau de gauche de la console Firebase, développez Créer, puis sélectionnez Base de données Firestore.
- Cliquez sur Créer une base de données.
- Laissez le champ ID de la base de données défini sur
(default)
. - Sélectionnez un emplacement pour votre base de données, puis cliquez sur Suivant.
Pour une application réelle, choisissez un emplacement proche de vos utilisateurs. - Cliquez sur Démarrer en mode test. Lisez la clause de non-responsabilité concernant les règles de sécurité.
Dans cet atelier de programmation, vous ajouterez des règles de sécurité pour protéger vos données. Ne distribuez ni n'exposez publiquement une application sans ajouter de règles de sécurité pour votre base de données. - Cliquez sur Créer.
4. Configurer Firebase
Pour utiliser Firebase avec Flutter, vous devez effectuer les tâches suivantes afin de configurer le projet Flutter pour qu'il utilise 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, puis ajoutez-le au code.
Dans le répertoire de premier niveau de votre application Flutter, vous trouverez les sous-répertoires android
, ios
, macos
et web
, qui contiennent les fichiers de configuration propres aux plates-formes iOS et Android.
Configurer des dépendances
Vous devez ajouter les bibliothèques FlutterFire
pour les deux produits Firebase que vous utilisez dans cette application : Authentication et Firestore.
- Dans 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 plug-ins Firebase Flutter.
$ flutter pub add firebase_auth
Le package firebase_auth
permet l'intégration à 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 accélérer le développement avec les flux d'authentification.
$ flutter pub add firebase_ui_auth
Vous avez ajouté les packages requis, mais vous devez également configurer les projets d'exécution iOS, Android, macOS et Web 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.
Installer la CLI FlutterFire
La CLI FlutterFire dépend de la CLI Firebase sous-jacente.
- Si vous ne l'avez pas déjà fait, installez la CLI Firebase sur votre machine.
- Installez la CLI FlutterFire :
$ dart pub global activate flutterfire_cli
Une fois installé, la commande flutterfire
est disponible dans le monde entier.
Configurer vos applications
La CLI extrait des 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 guide à travers les processus suivants :
- Sélectionnez un projet Firebase en fonction du fichier
.firebaserc
ou dans la console Firebase. - Déterminez les plates-formes pour la configuration, telles qu'Android, iOS, macOS et le Web.
- 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 de votre projet actuel.
- Générez un fichier
firebase_options.dart
dans votre projet.
Configurer macOS
Flutter sur macOS crée des applications entièrement mises en bac à sable. Étant donné que cette application s'intègre au réseau pour communiquer avec les serveurs Firebase, vous devez la configurer avec des droits d'accès au 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 en savoir plus, consultez la page Desktop support for Flutter (Compatibilité avec les applications de bureau pour Flutter).
5. Ajouter une fonctionnalité de confirmation de présence
Maintenant que vous avez ajouté Firebase à l'application, vous pouvez créer un bouton RSVP qui enregistre les personnes avec Authentication. Pour Android natif, iOS natif et le 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.
Ajouter la 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 :
- Créez un 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
présentent 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
.
L'objet d'état de l'application ApplicationState
a une responsabilité principale pour cette étape : alerter l'arborescence des widgets qu'un état authentifié a été mis à jour.
Vous n'utilisez un fournisseur que pour communiquer l'état de connexion d'un utilisateur à l'application. Pour permettre à un utilisateur de se connecter, vous utilisez les UI fournies par le package firebase_ui_auth
, qui est un excellent moyen d'amorcer rapidement les écrans de connexion dans vos applications.
Intégrer le flux d'authentification
- 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';
- 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 apportée à la fonction main()
permet au package de fournisseur d'être 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
, ce qui permet au package provider
de savoir quand réafficher les widgets dépendants.
- Mettez à jour votre application pour gérer la navigation vers les différents écrans que FirebaseUI vous fournit, 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 lors de l'authentification, vous pouvez rediriger l'utilisateur vers un écran de votre choix, qu'il s'agisse de l'écran d'accueil ou d'un autre écran, comme celui du profil.
- Dans la méthode build de la classe
HomePage
, intégrez l'état de l'application au widgetAuthFunc
:
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 Consumer est la méthode habituelle pour utiliser le package provider
afin de 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
- Dans l'application, appuyez sur le bouton RSVP pour lancer la
SignInScreen
.
- Saisissez une adresse e-mail. Si vous êtes déjà inscrit, le système vous invite à saisir un mot de passe. Sinon, le système vous invite à remplir le formulaire d'inscription.
- Saisissez un mot de passe de moins de six caractères pour vérifier le flux de gestion des erreurs. Si vous êtes inscrit, le mot de passe s'affiche à la place.
- Saisissez des mots de passe incorrects pour vérifier le flux de gestion des erreurs.
- Saisissez le mot de passe correct. Vous voyez l'expérience de connexion, qui permet à l'utilisateur de se déconnecter.
6. Écrire des messages dans Firestore
C'est bien de savoir que des utilisateurs arrivent, mais vous devez leur proposer autre chose à faire dans l'application. Et s'ils pouvaient laisser des messages dans un livre d'or ? Ils peuvent expliquer pourquoi ils ont hâte de venir ou qui ils espèrent rencontrer.
Pour stocker les messages de chat que les utilisateurs écrivent dans l'application, vous utilisez Firestore.
Modèle de données
Firestore est une base de données NoSQL. Les données qui y sont stockées sont divisées en collections, documents, champs et sous-collections. Vous stockez chaque message du chat en tant que document dans une collection guestbook
, qui est une collection de premier niveau.
Ajouter des messages à Firestore
Dans cette section, vous allez ajouter la fonctionnalité permettant aux utilisateurs d'écrire des messages dans la base de données. Vous commencez par ajouter un champ de formulaire et un bouton d'envoi, puis vous ajoutez le code qui relie ces éléments à la base de données.
- Créez un fichier nommé
guest_book.dart
, puis ajoutez un widget avec étatGuestBook
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'),
],
),
),
],
),
),
);
}
}
Deux points sont à noter ici. Tout d'abord, vous instanciez un formulaire pour pouvoir vérifier que le message contient bien du contenu et afficher un message d'erreur à l'utilisateur si ce n'est pas le cas. Pour valider un formulaire, vous accédez à l'état du formulaire derrière le formulaire avec un GlobalKey
. Pour en savoir plus sur les clés et leur utilisation, 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 encapsulé 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 avez un widget qui permet à l'utilisateur de saisir du texte à ajouter au livre d'or, vous devez l'afficher à l'écran.
- Modifiez le corps de
HomePage
pour ajouter les deux lignes suivantes à la fin des enfants deListView
:
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 suffise à afficher le widget, cela ne suffit pas à faire quoi que ce soit d'utile. Vous allez bientôt mettre à jour ce code pour le rendre fonctionnel.
Aperçu de l'application
Lorsqu'un utilisateur clique sur SEND (ENVOYER), l'extrait de code suivant est déclenché. 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 guestbook
.
Notez que FirebaseAuth.instance.currentUser.uid
fait référence à l'ID unique généré automatiquement que l'authentification fournit pour tous les utilisateurs connectés.
- Dans le fichier
lib/app_state.dart
, ajoutez la méthodeaddMessageToGuestBook
. 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.
}
Connecter l'UI et la base de données
Vous disposez d'une UI dans laquelle l'utilisateur peut saisir le texte qu'il souhaite ajouter au livre d'or, ainsi que du code permettant d'ajouter l'entrée à Firestore. Il ne vous reste plus qu'à les associer.
- Dans le fichier
lib/home_page.dart
, apportez la modification suivante au widgetHomePage
:
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 affichez. Cela vous permet de réagir à un message saisi dans l'UI et de le publier dans la base de données. Dans la section suivante, vous allez vérifier si les messages ajoutés sont publiés dans la base de données.
Tester l'envoi de messages
- Si nécessaire, connectez-vous à l'application.
- Saisissez un message, tel que
Hey there!
, puis cliquez sur SEND (ENVOYER).
Cette action écrit le message dans votre base de données Firestore. Cependant, vous ne verrez pas le message dans votre application Flutter actuelle, car vous devez encore mettre en œuvre la récupération des données, ce que vous ferez à l'étape suivante. Toutefois, dans le tableau de bord Database de la console Firebase, vous pouvez voir le message que vous avez ajouté dans la collection guestbook
. Si vous envoyez plus de messages, vous ajoutez plus de documents à votre collection guestbook
. Par exemple, consultez l'extrait de code suivant :
7. Lire les messages
C'est super 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. Nous allons nous y atteler.
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'UI qui affiche les nouveaux messages. Vous ajoutez du code à l'état de l'application qui écoute les messages nouvellement ajoutés à l'application.
- Créez un fichier
guest_book_message.dart
et 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;
}
- 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
- Dans la section
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.
- 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ésabonne 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 là que vous construisez une requête sur la collection guestbook
et que vous gérez l'abonnement et la désinscription à cette collection. Vous écoutez le flux, où vous reconstruisez 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 et obtenir un modèle mental plus clair. Pour en savoir plus, consultez Obtenir des mises à jour en temps réel avec Firestore.
- Dans le fichier
lib/guest_book.dart
, ajoutez l'importation suivante :
import 'guest_book_message.dart';
- Dans le widget
GuestBook
, ajoutez une liste de messages à la configuration pour connecter cet état changeant à 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();
}
- Dans
_GuestBookState
, modifiez la méthodebuild
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.
- Mettez à jour le corps de
HomePage
pour construire correctementGuestBook
avec le nouveau paramètremessages
:
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 :
- Dans l'application, recherchez les messages que vous avez créés précédemment dans la base de données.
- rédiger de nouveaux messages ; Elles s'affichent instantanément.
- Ouvrez votre espace de travail dans plusieurs fenêtres ou onglets. Les messages sont synchronisés en temps réel dans les fenêtres et les onglets.
- Facultatif : Dans le menu Database (Base de données) de la console Firebase, supprimez, modifiez ou ajoutez manuellement des messages. Toutes les modifications apparaissent dans l'UI.
Félicitations ! Vous avez lu des documents Firestore dans votre application !
Aperçu de l'application
8. Configurer 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 en lecture et en écriture. Toutefois, vous ne devez utiliser le mode test que lors des premières étapes du développement. Nous vous recommandons de configurer des règles de sécurité pour votre base de données au fur et à mesure du développement de 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 des règles flexibles vous permet de créer des règles qui correspondent à toutes sortes d'éléments : toutes les écritures de l'intégralité de la base de données, ou bien les opérations sur un document spécifique.
Configurez des règles de sécurité de base :
- Dans le menu Développer de la console Firebase, cliquez sur Base de données > Règles. Les règles de sécurité par défaut suivantes devraient s'afficher, ainsi qu'un avertissement indiquant qu'elles sont publiques :
- 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 qui tente d'écrire dans le document possède un UID d'authentification correspondant.
- 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 du livre d'or, mais seul l'auteur d'un message peut le modifier.
- 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 : Mettez en pratique ce que vous avez appris
Enregistrer l'état de la réponse d'un participant
Pour le moment, votre application ne permet aux utilisateurs de discuter que lorsqu'ils sont intéressés par l'événement. De plus, la seule façon de savoir si quelqu'un arrive est de le demander dans le chat.
À cette étape, vous vous organisez et vous indiquez le nombre de personnes qui viendront. Vous ajoutez quelques fonctionnalités à l'état de l'application. La première est la possibilité pour un utilisateur connecté d'indiquer s'il participe ou non. Le second est un compteur indiquant le nombre de participants.
- Dans le fichier
lib/app_state.dart
, ajoutez les lignes suivantes à la section des accesseurs deApplicationState
afin que le code de l'UI 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});
}
}
- Mettez à jour la méthode
init()
deApplicationState
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 abonnée 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 s'il participe.
- Ajoutez l'énumération suivante en haut du fichier
lib/app_state.dart
.
lib/app_state.dart
enum Attending { yes, no, unknown }
- Créez un fichier
yes_no_selection.dart
et définissez un widget qui se comporte 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 commence dans un état indéterminé, sans que Oui ni Non ne soient sélectionnés. Une fois que l'utilisateur a sélectionné sa réponse, vous mettez en évidence l'option choisie avec un bouton plein et l'autre option est affichée avec un rendu plat.
- Mettez à jour la méthode
build()
deHomePage
pour profiter deYesNoSelection
, permettre à un utilisateur connecté d'indiquer s'il participe à l'événement et afficher le nombre de participants :
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à configuré des règles. Les données que vous ajouterez à l'aide des boutons seront donc refusées. Vous devez mettre à jour les règles pour autoriser les ajouts à la collection attendees
.
- Dans la collection
attendees
, récupérez l'UID d'authentification que vous avez utilisé comme nom de document et vérifiez que leuid
de l'auteur 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 elle ne contient aucune donnée privée, mais seul l'organisateur peut la modifier.
- 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 : Dans l'application, cliquez sur les boutons pour afficher les résultats dans le tableau de bord Firestore de 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.