1. Avant de commencer
Dans cet atelier de programmation, vous allez découvrir les principes de base 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
- Créer une application de réponse à un événement et de chat pour un livre d'or sur Android, iOS, le Web et macOS avec Flutter
- 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 à installer)
- Android Emulator (à configurer 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, tels qu'Android Studio ou Visual Studio Code.
- La dernière version
stable
de Flutter oubeta
si vous aimez vivre à la pointe de la technologie. - Un compte Google pour créer et gérer votre projet Firebase.
- La CLI
Firebase
connectée à votre compte Google.
2. Obtenir l'exemple de code
Téléchargez la version initiale de votre projet sur 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 doit 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 à l'étape suivante ou voir à quoi doit ressembler un élément après une étape, recherchez-le dans 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émarrage de l'atelier de programmation, qui se compose d'une application de rencontre Flutter non fonctionnelle.
Localiser les fichiers à modifier
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 quelques widgets qui permettent de 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 Authentication avec un ensemble de widgets permettant de créer une expérience utilisateur de connexion pour l'authentification Firebase par 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.
Vous ajoutez des fichiers supplémentaires 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 l'ensemble de 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 de Header
, Paragraph
et IconAndDetail
. Ces widgets éliminent le code en double pour désencombrer la mise en page de la page décrite dans HomePage
. Cela permet également d'obtenir un aspect cohérent.
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 l'événement est très utile pour vos invités, mais n'est pas très pratique pour quiconque. Vous devez ajouter des fonctionnalités dynamiques à l'application. Pour ce faire, vous devez connecter Firebase à votre application. Pour commencer à utiliser Firebase, vous devez créer et configurer un projet Firebase.
Créer un projet Firebase
- Connectez-vous à Firebase.
- Dans la console, cliquez sur Ajouter un projet ou Créer un projet.
- Dans le champ Nom du projet, saisissez Firebase-Flutter-Codelab, puis cliquez sur Continuer.
- Cliquez sur les options souhaitées. Si vous y êtes invité, acceptez les conditions d'utilisation de Firebase, mais ignorez la configuration de Google Analytics, car vous n'en aurez pas besoin pour cette application.
Pour en savoir plus sur les projets Firebase, consultez Comprendre les projets 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:enregistre des données structurées dans le cloud et envoie des notifications instantanées en cas de modification des données.
- Règles de sécurité Firebase:sécurise 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 adresse e-mail
- Dans le volet Vue d'ensemble du projet de la console Firebase, développez le menu Build (Compilation).
- Cliquez sur Authentication > Get Started > Sign-in method > Email/Password > Enable > Save (Authentification > Commencer > Méthode de connexion > Adresse 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 Build (Compilation), puis sélectionnez Firestore database (Base de données Firestore).
- Cliquez sur Créer une base de données.
- Laissez le champ Database ID (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é.
Plus tard dans cet atelier de programmation, vous ajouterez des règles de sécurité pour sécuriser vos données. Ne distribuez pas ni n'exposez pas publiquement une application sans ajouter de règles de sécurité à 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 correspondant à la plate-forme, puis ajoutez-le au code.
Dans le répertoire de premier niveau de votre application Flutter, vous trouverez des sous-répertoires android
, ios
, macos
et web
, qui contiennent les fichiers de configuration propres aux plates-formes 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: 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 à Authentication.
$ 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écuteur 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 ordinateur.
- Installez la CLI FlutterFire :
$ dart pub global activate flutterfire_cli
Une fois installée, 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.
Dans la racine de votre application, exécutez la commande configure
:
$ flutterfire configure
La commande de configuration vous guide dans les processus suivants:
- Sélectionnez un projet Firebase en fonction du fichier
.firebaserc
ou dans la console Firebase. - Déterminez les plates-formes à configurer, comme Android, iOS, macOS et le Web.
- Identifiez les applications Firebase à partir desquelles vous souhaitez 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 placées en bac à sable. Comme cette application s'intègre au réseau pour communiquer avec les serveurs Firebase, vous devez configurer votre application avec des droits 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 Compatibilité avec les applications de bureau pour Flutter.
5. Ajouter une fonctionnalité de réponse
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 inclut 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 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:
- 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
introduisent Firebase Core et Auth, extraient le package provider
qui rend l'objet d'état de l'application disponible dans l'ensemble de 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, à savoir alerter l'arborescence de 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
, ce qui est un excellent moyen de démarrer 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';
- Associez l'état de l'application à son initialisation, 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()
rend le package du 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
, ce qui permet au package provider
de savoir quand réafficher les widgets dépendants.
- Modifiez 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 de l'authentification, vous pouvez rediriger l'utilisateur vers un écran de son choix, qu'il s'agisse de l'écran d'accueil ou d'un autre écran, comme le profil.
- Dans la méthode de compilation 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 permettant d'utiliser le package provider
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
- Dans l'application, appuyez sur le bouton RSVP pour lancer l'
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 correspondant s'affiche à la place.
- Saisissez des mots de passe incorrects pour vérifier le flux de gestion des erreurs.
- Saisissez le bon mot de passe. L'expérience de connexion s'affiche, et l'utilisateur peut se déconnecter.
6. Écrire des messages dans Firestore
C'est bien que les utilisateurs viennent, mais vous devez leur donner autre chose à faire dans l'application. Que se passerait-il 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 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 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. Commencez par ajouter un champ de formulaire et un bouton d'envoi, puis le code qui relie ces éléments à la base de données.
- Créez un fichier nommé
guest_book.dart
, puis ajoutez un widgetGuestBook
avec état pour créer 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'),
],
),
),
],
),
),
);
}
}
Voici quelques points à noter : Tout d'abord, vous instanciez un formulaire afin de vérifier que le message contient bien du contenu et d'afficher un message d'erreur à l'utilisateur s'il n'y en a pas. Pour valider un formulaire, vous accédez à l'état du formulaire sous-jacent à l'aide d'un GlobalKey
. Pour en savoir plus sur les clés et leur utilisation, consultez Quand utiliser des clés.
Notez également la mise en page des widgets. 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 de la ligne. Pour mieux comprendre pourquoi cette étape est nécessaire, consultez Comprendre les contraintes.
Maintenant que vous disposez d'un widget permettant à 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)),
Cela suffit pour afficher le widget, mais pas pour effectuer une action utile. Vous allez bientôt modifier 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'identifiant unique généré automatiquement par Authentication pour tous les utilisateurs connectés.
- Dans le fichier
lib/app_state.dart
, ajoutez la méthodeaddMessageToGuestBook
. Vous allez connecter 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 et vous disposez du code permettant d'ajouter l'entrée à Firestore. Il vous suffit maintenant de les connecter.
- 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 utilisateur qui saisit un message dans l'UI 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
- Si nécessaire, connectez-vous à l'application.
- Saisissez un message, par exemple
Hey there!
, puis cliquez sur ENVOYER.
Cette action écrit le message dans votre base de données Firestore. Toutefois, le message ne s'affiche pas dans votre application Flutter actuelle, car vous devez encore implémenter la récupération des données, ce que vous ferez à l'étape suivante. Toutefois, dans le tableau de bord de la console Firebase "Base de données", 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
Il est agréable 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. C'est le moment de le corriger.
Synchroniser les messages
Pour afficher des 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 nouveaux messages ajoutés par l'application.
- Créez un fichier
guest_book_message.dart
, puis ajoutez la classe suivante pour afficher 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 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.
- 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 vous 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 là que vous créez une requête sur la collection 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 de la collection guestbook
et stockez également une référence à cet abonnement afin de pouvoir vous désabonner plus tard. Il se passe beaucoup de choses ici. Vous devez donc l'explorer dans un débogueur pour inspecter ce qui se passe afin d'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 dans la configuration pour associer cet état en évolution à 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.
);
}
}
Encapsulez le contenu précédent de la méthode build()
avec un widget Column
, puis ajoutez une collection pour à la fin des enfants de Column
pour générer un Paragraph
pour chaque message de la liste.
- Mettez à jour le corps de
HomePage
pour créer 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 entre 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 lisez 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 aux lectures et aux écritures. Toutefois, vous ne devez utiliser le mode test que pendant les premières phases de développement. Nous vous recommandons de configurer des règles de sécurité pour votre base de données lorsque vous développez votre application. La sécurité est un élément essentiel 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 et un avertissement indiquant qu'elles sont publiques doivent s'afficher:
- Identifiez les collections auxquelles 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.
}
}
Comme 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 qui tente 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:
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 le modifier.
- Ajoutez une 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 réponse d'un participant
Actuellement, votre application ne permet aux utilisateurs de discuter que lorsqu'ils s'intéressent à l'événement. De plus, le seul moyen de savoir si quelqu'un va venir est de le demander dans le chat.
À cette étape, vous vous organisez et informez les participants du nombre de personnes qui participeront à l'événement. Vous ajoutez quelques fonctionnalités à l'état de l'application. La première est la possibilité pour un utilisateur connecté d'indiquer s'il participera ou non. Le second est un compteur du nombre de personnes présentes.
- 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 souscrite pour déterminer le nombre de participants et une deuxième requête qui n'est active que lorsque l'utilisateur est connecté pour déterminer s'il participe à l'événement.
- 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
, puis définissez un widget qui fonctionne 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 que Oui ni Non ne soient sélectionnés. Une fois que l'utilisateur a indiqué s'il participera ou non, vous devez mettre en avant l'option sélectionnée à l'aide d'un bouton rempli, et faire reculer l'autre option avec un rendu plat.
- Mettez à jour la méthode
build()
deHomePage
pour profiter deYesNoSelection
, autorisez un utilisateur connecté à indiquer s'il participe ou non à l'événement, et affichez 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à configuré des règles. Par conséquent, les données que vous ajoutez à l'aide des boutons seront refusées. Vous devez mettre à jour les règles pour autoriser l'ajout à 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 l'uid
de l'expéditeur est identique à 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;
}
}
}
Tout le monde peut ainsi lire la liste des participants, car elle ne contient aucune donnée privée, mais seul le créateur peut la modifier.
- Ajoutez une 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 et en temps réel.