1. Hinweis
In diesem Codelab lernen Sie die Grundlagen von Firebase kennen, um mobile Flutter-Apps für Android und iOS zu erstellen.
Vorbereitung
- Flutter-Vorkenntnisse
- Flutter SDK
- Texteditor Ihrer Wahl
Lerninhalte
- Hier erfahren Sie, wie Sie mit Flutter eine Antwort- und Gästebuch-Chat-App für Android, iOS, das Web und macOS erstellen.
- Nutzer mit Firebase Authentication authentifizieren und Daten mit Firestore synchronisieren
Voraussetzungen
Eines der folgenden Geräte:
- Ein physisches Android- oder iOS-Gerät, das mit Ihrem Computer verbunden und auf den Entwicklermodus gesetzt ist.
- iOS-Simulator (Xcode-Tools erforderlich)
- Android Emulator (Einrichtung in Android Studio erforderlich)
Außerdem benötigen Sie Folgendes:
- Einen Browser Ihrer Wahl, z. B. Google Chrome.
- Eine IDE oder ein Texteditor Ihrer Wahl, die bzw. der mit den Dart- und Flutter-Plug-ins konfiguriert ist, z. B. Android Studio oder Visual Studio Code.
- Die neueste
stable
-Version von Flutter oderbeta
, wenn Sie gerne an der Spitze stehen. - Ein Google-Konto zum Erstellen und Verwalten Ihres Firebase-Projekts.
- Sie haben sich über die
Firebase
-Befehlszeile in Ihrem Google-Konto angemeldet.
2. Beispielcode abrufen
Laden Sie die erste Version Ihres Projekts von GitHub herunter:
- Klonen Sie über die Befehlszeile das GitHub-Repository in das Verzeichnis
flutter-codelabs
:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Das Verzeichnis flutter-codelabs
enthält den Code für eine Sammlung von Codelabs. Der Code für dieses Codelab befindet sich im Verzeichnis flutter-codelabs/firebase-get-to-know-flutter
. Das Verzeichnis enthält eine Reihe von Snapshots, die zeigen, wie Ihr Projekt am Ende jedes Schritts aussehen sollte. Angenommen, Sie befinden sich im zweiten Schritt.
- Suchen Sie die übereinstimmenden Dateien für den zweiten Schritt:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
Wenn Sie vorwärts springen oder sehen möchten, wie etwas nach einem Schritt aussehen sollte, suchen Sie in dem Verzeichnis, das nach dem Schritt benannt ist, für den Sie sich interessieren.
Start-App importieren
- Öffnen oder importieren Sie das Verzeichnis
flutter-codelabs/firebase-get-to-know-flutter/step_02
in Ihrer bevorzugten IDE. Dieses Verzeichnis enthält den Startcode für das Codelab, das aus einer noch nicht funktionsfähigen Flutter-Meetup-App besteht.
Dateien finden, die bearbeitet werden müssen
Der Code in dieser Anwendung ist auf mehrere Verzeichnisse verteilt. Diese Aufteilung der Funktionalität erleichtert die Arbeit, da der Code nach Funktionen gruppiert wird.
- Suchen Sie die folgenden Dateien:
lib/main.dart
: Diese Datei enthält den Haupteinstiegspunkt und das App-Widget.lib/home_page.dart
: Diese Datei enthält das Startseiten-Widget.lib/src/widgets.dart
: Diese Datei enthält einige Widgets, mit denen der Stil der App standardisiert werden kann. Sie bilden den Bildschirm der Start-App.lib/src/authentication.dart
: Diese Datei enthält eine teilweise Implementierung der Authentifizierung mit einer Reihe von Widgets, um eine Anmeldeoberfläche für die Firebase-E-Mail-basierte Authentifizierung zu erstellen. Diese Widgets für den Authentifizierungsvorgang werden in der Start-App noch nicht verwendet. Sie werden aber bald hinzugefügt.
Sie fügen nach Bedarf zusätzliche Dateien hinzu, um den Rest der Anwendung zu erstellen.
Datei lib/main.dart
prüfen
Diese App nutzt das google_fonts
-Paket, um Roboto in der gesamten App als Standardschriftart festzulegen. Unter fonts.google.com finden Sie die Schriftarten, die Sie in den verschiedenen Bereichen der App entdecken.
Sie verwenden die Hilfs-Widgets aus der Datei lib/src/widgets.dart
im Format Header
, Paragraph
und IconAndDetail
. Mit diesen Widgets wird doppelter Code entfernt, um das unter HomePage
beschriebene Seitenlayout unübersichtlich zu machen. Dies sorgt auch für ein einheitliches Erscheinungsbild.
So sieht Ihre App auf Android-, iOS-, Web- und macOS-Geräten aus:
3. Firebase-Projekt erstellen und konfigurieren
Die Anzeige von Veranstaltungsinformationen ist für Ihre Gäste sehr hilfreich, für andere aber nicht unbedingt. Sie müssen der App dynamische Funktionen hinzufügen. Dazu müssen Sie Firebase mit Ihrer App verbinden. Um Firebase zu nutzen, müssen Sie ein Firebase-Projekt erstellen und konfigurieren.
Firebase-Projekt erstellen
- Melden Sie sich in Firebase an.
- Klicken Sie in der Console auf Projekt hinzufügen oder Projekt erstellen.
- Geben Sie im Feld Project name (Projektname) Firebase-Flutter-Codelab ein und klicken Sie dann auf Continue (Weiter).
- Klicken Sie sich durch die Optionen zur Projekterstellung. Akzeptieren Sie die Firebase-Nutzungsbedingungen, wenn Sie dazu aufgefordert werden, überspringen Sie aber die Einrichtung von Google Analytics, da Sie es für diese App nicht verwenden werden.
Weitere Informationen zu Firebase-Projekten
Die App verwendet die folgenden Firebase-Produkte, die für Web-Apps verfügbar sind:
- Authentifizierung: Damit können sich Nutzer in Ihrer App anmelden.
- Firestore:Speichert strukturierte Daten in der Cloud und erhält Sofortbenachrichtigungen, wenn sich Daten ändern.
- Firebase-Sicherheitsregeln: Schützen Ihre Datenbank.
Einige dieser Produkte erfordern eine spezielle Konfiguration oder müssen in der Firebase Console aktiviert werden.
Authentifizierung per E-Mail-Anmeldung aktivieren
- Maximieren Sie in der Firebase Console im Bereich Projektübersicht das Menü Erstellen.
- Klicken Sie auf Authentifizierung > Jetzt starten > Anmeldemethode > E-Mail-Adresse/Passwort > Aktivieren > Speichern.
Firestore einrichten
Die Webanwendung verwendet Firestore, um Chatnachrichten zu speichern und neue Chatnachrichten zu empfangen.
So richten Sie Firestore in Ihrem Firebase-Projekt ein:
- Maximieren Sie im linken Bereich der Firebase Console Build und wählen Sie dann Firestore-Datenbank aus.
- Klicken Sie auf Datenbank erstellen.
- Belassen Sie die Datenbank-ID bei
(default)
. - Wählen Sie einen Speicherort für die Datenbank aus und klicken Sie auf Weiter.
Bei einer echten App sollten Sie einen Standort in der Nähe Ihrer Nutzer auswählen. - Klicken Sie auf Im Testmodus starten. Lesen Sie den Haftungsausschluss zu den Sicherheitsregeln.
Später in diesem Codelab fügen Sie Sicherheitsregeln hinzu, um Ihre Daten zu schützen. Veröffentlichen oder verteilen Sie keine App, ohne Sicherheitsregeln für Ihre Datenbank hinzuzufügen. - Klicken Sie auf Erstellen.
4. Firebase konfigurieren
Wenn Sie Firebase mit Flutter verwenden möchten, müssen Sie die folgenden Aufgaben ausführen, um das Flutter-Projekt so zu konfigurieren, dass die FlutterFire
-Bibliotheken korrekt verwendet werden:
- Fügen Sie Ihrem Projekt die
FlutterFire
-Abhängigkeiten hinzu. - Registrieren Sie die gewünschte Plattform im Firebase-Projekt.
- Lade die platformspezifische Konfigurationsdatei herunter und füge sie dem Code hinzu.
Das Verzeichnis der obersten Ebene Ihrer Flutter-App enthält die Unterverzeichnisse android
, ios
, macos
und web
mit den plattformspezifischen Konfigurationsdateien für iOS bzw. Android.
Abhängigkeiten konfigurieren
Sie müssen die FlutterFire
-Bibliotheken für die beiden Firebase-Produkte hinzufügen, die Sie in dieser App verwenden: Authentication und Firestore.
- Fügen Sie in der Befehlszeile die folgenden Abhängigkeiten hinzu:
$ flutter pub add firebase_core
Das firebase_core
-Paket ist der gemeinsame Code, der für alle Firebase-Flutter-Plug-ins erforderlich ist.
$ flutter pub add firebase_auth
Das firebase_auth
-Paket ermöglicht die Integration mit der Authentifizierung.
$ flutter pub add cloud_firestore
Das Paket cloud_firestore
ermöglicht den Zugriff auf den Firestore-Datenspeicher.
$ flutter pub add provider
Das firebase_ui_auth
-Paket bietet eine Reihe von Widgets und Dienstprogrammen, mit denen Entwickler die Geschwindigkeit mit Authentifizierungsabläufen erhöhen können.
$ flutter pub add firebase_ui_auth
Sie haben die erforderlichen Pakete hinzugefügt, aber Sie müssen auch die iOS-, Android-, macOS- und Web-Runner-Projekte so konfigurieren, dass Firebase ordnungsgemäß verwendet wird. Sie verwenden auch das Paket provider
, das die Trennung der Geschäftslogik von der Anzeigelogik ermöglicht.
FlutterFire CLI installieren
Die FlutterFire CLI hängt von der zugrunde liegenden Firebase CLI ab.
- Installieren Sie die Firebase CLI auf Ihrem Computer, falls noch nicht geschehen.
- Installieren Sie die FlutterFire CLI:
$ dart pub global activate flutterfire_cli
Nach der Installation ist der flutterfire
-Befehl weltweit verfügbar.
Apps konfigurieren
Die CLI extrahiert Informationen aus Ihrem Firebase-Projekt und den ausgewählten Projekt-Apps, um die gesamte Konfiguration für eine bestimmte Plattform zu generieren.
Führen Sie im Stammverzeichnis Ihrer Anwendung den Befehl configure
aus:
$ flutterfire configure
Der Konfigurationsbefehl führt Sie durch die folgenden Prozesse:
- Wählen Sie basierend auf der Datei
.firebaserc
oder in der Firebase Console ein Firebase-Projekt aus. - Plattformen für die Konfiguration festlegen, z. B. Android, iOS, macOS und Web
- Identifizieren Sie die Firebase-Apps, aus denen die Konfiguration extrahiert werden soll. Standardmäßig versucht die CLI, Firebase-Apps automatisch anhand Ihrer aktuellen Projektkonfiguration abzugleichen.
- Erstellen Sie eine
firebase_options.dart
-Datei in Ihrem Projekt.
macOS konfigurieren
Flutter unter macOS erstellt Apps, die vollständig in einer Sandbox ausgeführt werden. Da sich diese App in das Netzwerk einbinden lässt, um mit den Firebase-Servern zu kommunizieren, müssen Sie Ihre App mit Netzwerkclient-Berechtigungen konfigurieren.
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>
Weitere Informationen finden Sie unter Desktopunterstützung für Flutter.
5. Funktion zum Melden der Teilnahme hinzufügen
Nachdem Sie Firebase zur App hinzugefügt haben, können Sie eine Schaltfläche zum Teilnehmen erstellen, über die Nutzer mit Authentifizierung registriert werden. Für native Android- und iOS-Geräte sowie für das Web gibt es vordefinierte FirebaseUI Auth
-Pakete, aber Sie müssen diese Funktion für Flutter erstellen.
Das Projekt, das Sie zuvor abgerufen haben, enthielt eine Reihe von Widgets, die die Benutzeroberfläche für den größten Teil des Authentifizierungsablaufs implementieren. Sie implementieren die Geschäftslogik, um Authentication in die Anwendung zu integrieren.
Geschäftslogik mit dem Paket Provider
hinzufügen
Verwenden Sie das Paket provider
, um ein zentrales App-Statusobjekt in der Struktur der Flutter-Widgets der App verfügbar zu machen:
- Erstellen Sie eine Datei mit dem Namen
app_state.dart
und dem folgendem Inhalt:
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();
});
}
}
Mit den import
-Anweisungen werden Firebase Core und Auth vorgestellt, das provider
-Paket geladen, das das App-Statusobjekt in der gesamten Widget-Struktur zur Verfügung stellt, und die Authentifizierungs-Widgets aus dem firebase_ui_auth
-Paket enthalten.
Dieses ApplicationState
-Anwendungsstatusobjekt hat für diesen Schritt eine Hauptaufgabe: Es benachrichtigt den Widget-Baum, dass ein authentifizierter Status aktualisiert wurde.
Sie verwenden nur einen Anbieter, um den Status des Anmeldestatus eines Nutzers an die App zu senden. Damit sich ein Nutzer anmelden kann, verwenden Sie die im Paket firebase_ui_auth
bereitgestellten UI. So können Sie schnell einen Bootstrapping von Anmeldebildschirmen in Ihren Apps ausführen.
Authentifizierungsvorgang einbinden
- Ändern Sie die Importe am Anfang der Datei
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';
- Verbinden Sie den App-Status mit der Initialisierung der App und fügen Sie dann den Authentifizierungsvorgang zu
HomePage
hinzu:
lib/main.dart
void main() {
// Modify from here...
WidgetsFlutterBinding.ensureInitialized();
runApp(ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: ((context, child) => const App()),
));
// ...to here.
}
Durch die Änderung an der main()
-Funktion ist das Anbieterpaket für die Instanziierung des App-Statusobjekts mit dem ChangeNotifierProvider
-Widget verantwortlich. Du verwendest diese spezielle provider
-Klasse, weil das App-Statusobjekt die ChangeNotifier
-Klasse erweitert, die dem provider
-Paket mitteilt, wann abhängige Widgets noch einmal angezeigt werden sollen.
- Aktualisieren Sie Ihre App so, dass sie die Navigation zu verschiedenen Bildschirmen übernimmt, die FirebaseUI für Sie bereitstellt. Erstellen Sie dazu eine
GoRouter
-Konfiguration:
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
);
}
}
Jedem Bildschirm ist eine andere Aktion zugeordnet, die vom neuen Status des Authentifizierungsablaufs abhängt. Nach den meisten Statusänderungen bei der Authentifizierung können Sie zu einem bevorzugten Bildschirm zurückkehren, z. B. zum Startbildschirm oder zu einem anderen Bildschirm wie dem Profil.
- Integrieren Sie in der Build-Methode der Klasse
HomePage
den App-Status in dasAuthFunc
-Widget:
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!',
),
],
),
);
}
}
Instanziieren Sie das AuthFunc
-Widget und umschließen Sie es in einem Consumer
-Widget. Das Nutzer-Widget ist die übliche Methode, mit der das Paket provider
verwendet werden kann, um einen Teil der Baumstruktur neu zu erstellen, wenn sich der App-Status ändert. Das AuthFunc
-Widget sind die ergänzenden Widgets, die Sie testen.
Authentifizierungsvorgang testen
- Tippen Sie in der App auf die Schaltfläche Antworten, um die
SignInScreen
zu starten.
- Geben Sie eine E-Mail-Adresse ein. Wenn Sie bereits registriert sind, werden Sie vom System aufgefordert, ein Passwort einzugeben. Andernfalls werden Sie vom System aufgefordert, das Registrierungsformular auszufüllen.
- Geben Sie ein Passwort mit weniger als sechs Zeichen ein, um den Ablauf zur Fehlerbehandlung zu überprüfen. Wenn Sie registriert sind, wird stattdessen das Passwort für angezeigt.
- Geben Sie falsche Passwörter ein, um den Ablauf zur Fehlerbehandlung zu überprüfen.
- Geben Sie das richtige Passwort ein. Sie sehen die Seite, die Nutzer sehen, wenn sie angemeldet sind. Dort können sie sich auch wieder abmelden.
6. Nachrichten in Firestore schreiben
Es ist schön, dass Nutzer kommen, aber Sie müssen den Gästen etwas anderes in der App bieten. Was wäre, wenn sie Nachrichten in einem Gästebuch hinterlassen könnten? Sie können darüber berichten, warum sie sich darauf freuen oder wen sie gerne treffen möchten.
Verwenden Sie Firestore, um die Chatnachrichten zu speichern, die Nutzer in der App geschrieben haben.
Datenmodell
Firestore ist eine NoSQL-Datenbank. Die in der Datenbank gespeicherten Daten werden in Sammlungen, Dokumente, Felder und untergeordnete Sammlungen aufgeteilt. Sie speichern jede Nachricht des Chats als Dokument in einer guestbook
-Sammlung, einer Sammlung auf oberster Ebene.
Nachrichten in Firestore hinzufügen
In diesem Abschnitt fügen Sie die Funktion hinzu, mit der Nutzer Nachrichten in die Datenbank schreiben können. Zunächst fügen Sie ein Formularfeld und eine Schaltfläche "Senden" hinzu. Anschließend fügen Sie den Code hinzu, der diese Elemente mit der Datenbank verbindet.
- Erstellen Sie eine neue Datei mit dem Namen
guest_book.dart
und fügen Sie einGuestBook
-Widget mit Status hinzu, um die UI-Elemente eines Nachrichtenfelds und einer Schaltfläche zum Senden zu erstellen:
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'),
],
),
),
],
),
),
);
}
}
Hier gibt es einige interessante Punkte. Zuerst instanziieren Sie ein Formular, damit Sie prüfen können, ob die Nachricht tatsächlich Inhalte enthält, und dem Nutzer eine Fehlermeldung anzeigen, falls dies nicht der Fall ist. Um ein Formular zu validieren, greifen Sie mit einem GlobalKey
auf den Formularstatus hinter dem Formular zu. Weitere Informationen zu Schlüsseln und ihrer Verwendung finden Sie unter Schlüssel verwenden.
Beachten Sie auch die Anordnung der Widgets: Sie haben eine Row
mit einer TextFormField
und einer StyledButton
, die eine Row
enthält. Beachten Sie auch, dass das TextFormField
von einem Expanded
-Widget umschlossen ist. Dadurch wird TextFormField
gezwungen, den zusätzlichen Platz in der Zeile zu füllen. Weitere Informationen dazu finden Sie unter Einschränkungen.
Da Sie nun über ein Widget verfügen, das es dem Nutzer ermöglicht, Text einzugeben, der zum Gästebuch hinzugefügt werden soll, müssen Sie es auf dem Bildschirm anzeigen lassen.
- Bearbeiten Sie den Body von
HomePage
und fügen Sie die folgenden beiden Zeilen am Ende der untergeordneten Elemente vonListView
hinzu:
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)),
Das reicht zwar aus, um das Widget anzuzeigen, aber nicht, um etwas Nützliches damit zu tun. Sie aktualisieren diesen Code gleich, damit er funktioniert.
App-Vorschau
Wenn ein Nutzer auf SENDEN klickt, wird das folgende Code-Snippet ausgelöst. Der Inhalt des Nachrichteneingabefelds wird der Sammlung guestbook
der Datenbank hinzugefügt. Genauer gesagt fügt die Methode addMessageToGuestBook
den Nachrichteninhalt einem neuen Dokument mit einer automatisch generierten ID in der Sammlung guestbook
hinzu.
Hinweis: FirebaseAuth.instance.currentUser.uid
ist eine Referenz auf die automatisch generierte eindeutige ID, die die Authentifizierung für alle angemeldeten Nutzer bereitstellt.
- Fügen Sie in der Datei
lib/app_state.dart
die MethodeaddMessageToGuestBook
hinzu. Diese Funktion verbinden Sie im nächsten Schritt mit der Benutzeroberfläche.
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.
}
UI und Datenbank verbinden
Sie haben eine Benutzeroberfläche, in der der Nutzer den Text eingeben kann, den er dem Gästebuch hinzufügen möchte, und Sie haben den Code, um den Eintrag in Firestore hinzuzufügen. Jetzt müssen Sie die beiden nur noch verbinden.
- Nehmen Sie in der Datei
lib/home_page.dart
die folgende Änderung amHomePage
-Widget vor:
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.
],
),
);
}
}
Sie haben die beiden Zeilen, die Sie zu Beginn dieses Schritts hinzugefügt haben, durch die vollständige Implementierung ersetzt. Sie verwenden wieder Consumer<ApplicationState>
, um den App-Status für den Teil des Baums verfügbar zu machen, den Sie rendern. So können Sie auf Nutzer reagieren, die eine Nachricht auf der Benutzeroberfläche eingeben, und sie in der Datenbank veröffentlichen. Im nächsten Abschnitt testen Sie, ob die hinzugefügten Nachrichten in der Datenbank veröffentlicht werden.
Das Senden von Nachrichten testen
- Melden Sie sich gegebenenfalls in der App an.
- Geben Sie eine Nachricht ein, z. B.
Hey there!
, und klicken Sie auf SENDEN.
Dadurch wird die Nachricht in Ihre Firestore-Datenbank geschrieben. Die Meldung wird jedoch nicht in der eigentlichen Flutter-App angezeigt, da Sie den Datenabruf noch im nächsten Schritt implementieren müssen. Im Dashboard Datenbank der Firebase Console können Sie die hinzugefügte Nachricht jedoch in der Sammlung guestbook
sehen. Wenn Sie weitere Nachrichten senden, fügen Sie Ihrer guestbook
-Sammlung weitere Dokumente hinzu. Hier ein Beispiel-Code-Snippet:
7. Nachrichten lesen
Es ist schön, dass Gäste Nachrichten in die Datenbank schreiben können, sie werden aber noch nicht in der App angezeigt. Zeit, das zu beheben!
Nachrichten synchronisieren
Wenn Sie Nachrichten anzeigen möchten, müssen Sie Listener hinzufügen, die ausgelöst werden, wenn sich Daten ändern, und dann ein UI-Element erstellen, das neue Nachrichten anzeigt. Sie fügen dem App-Status Code hinzu, der auf neu hinzugefügte Nachrichten von der App wartet.
- Erstellen Sie eine neue Datei
guest_book_message.dart
und fügen Sie die folgende Klasse hinzu, um eine strukturierte Ansicht der Daten zu erhalten, die Sie in Firestore speichern.
lib/guest_book_message.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
- Fügen Sie in der Datei
lib/app_state.dart
die folgenden Importe hinzu:
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
- Fügen Sie im Abschnitt von
ApplicationState
, in dem Sie Status und Getter definieren, die folgenden Zeilen hinzu:
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.
- Fügen Sie im Abschnitt „Initialization“ von
ApplicationState
die folgenden Zeilen hinzu, um eine Abfrage über die Dokumentensammlung zu abonnieren, wenn sich ein Nutzer anmeldet, und das Abo zu kündigen, wenn er sich abmeldet:
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();
});
}
Dieser Abschnitt ist wichtig, da Sie darin eine Abfrage über die Sammlung guestbook
erstellen und das Abonnieren und Kündigen dieser Sammlung verwalten. Sie hören sich den Stream an, um einen lokalen Cache der Nachrichten in der Sammlung guestbook
zu rekonstruieren. Außerdem speichern Sie einen Verweis auf dieses Abo, damit Sie es später kündigen können. Hier ist viel los, also sollten Sie es in einem Debugger untersuchen, um zu überprüfen, was passiert, um ein klareres mentales Modell zu erhalten. Weitere Informationen finden Sie unter Echtzeitaktualisierungen mit Firestore abrufen.
- Fügen Sie in der Datei
lib/guest_book.dart
den folgenden Import hinzu:
import 'guest_book_message.dart';
- Fügen Sie im
GuestBook
-Widget im Rahmen der Konfiguration eine Liste mit Nachrichten hinzu, um diesen sich ändernden Status mit der Benutzeroberfläche zu verknüpfen:
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();
}
- Ändern Sie in
_GuestBookState
die Methodebuild
wie unten beschrieben, um diese Konfiguration freizugeben:
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.
);
}
}
Sie umschließen den vorherigen Inhalt der build()
-Methode mit einem Column
-Widget und fügen dann am Ende der untergeordneten Elemente von Column
eine collection for hinzu, um für jede Nachricht in der Nachrichtenliste eine neue Paragraph
zu generieren.
- Aktualisieren Sie den Textkörper von
HomePage
, damitGuestBook
mit dem neuen Parametermessages
korrekt erstellt wird:
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
),
],
],
),
),
Nachrichtensynchronisierung testen
Firestore synchronisiert Daten automatisch und sofort mit Clients, die für die Datenbank abonniert sind.
Nachrichtensynchronisierung testen:
- Suchen Sie in der App nach den Nachrichten, die Sie zuvor in der Datenbank erstellt haben.
- Neue Nachrichten schreiben. Sie erscheinen sofort.
- Öffnen Sie Ihren Arbeitsbereich in mehreren Fenstern oder Tabs. Die Nachrichten werden in Echtzeit zwischen den Fenstern und Tabs synchronisiert.
- Optional: Sie können im Menü Datenbank der Firebase Console neue Nachrichten manuell löschen, bearbeiten oder hinzufügen. Alle Änderungen werden auf der Benutzeroberfläche angezeigt.
Glückwunsch! Sie lesen Firestore-Dokumente in Ihrer App.
App-Vorschau
8. Grundlegende Sicherheitsregeln einrichten
Sie haben Firestore anfangs für die Verwendung des Testmodus eingerichtet. Das bedeutet, dass Ihre Datenbank für Lese- und Schreibvorgänge geöffnet ist. Sie sollten den Testmodus jedoch nur in einem frühen Entwicklungsstadium verwenden. Als Best Practice sollten Sie bei der Entwicklung Ihrer Anwendung Sicherheitsregeln für Ihre Datenbank festlegen. Sicherheit ist ein wesentlicher Bestandteil der Struktur und des Verhaltens Ihrer App.
Mit Firebase-Sicherheitsregeln können Sie den Zugriff auf Dokumente und Sammlungen in Ihrer Datenbank steuern. Mit der flexiblen Regelsyntax können Sie Regeln erstellen, die alles Mögliche abgleichen, von allen Schreibvorgängen über die gesamte Datenbank bis hin zu Vorgängen in einem bestimmten Dokument.
Richten Sie grundlegende Sicherheitsregeln ein:
- Klicken Sie im Menü Entwickeln der Firebase Console auf Datenbank > Regeln. Sie sollten die folgenden Standardsicherheitsregeln und eine Warnung dazu sehen, dass die Regeln öffentlich sind:
- Identifizieren Sie die Sammlungen, in die die App Daten schreibt:
Geben Sie in match /databases/{database}/documents
die Sammlung an, die Sie sichern möchten:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
// You'll add rules here in the next step.
}
}
Da Sie die Authentifizierungs-UID als Feld in jedem Gästebuchdokument verwendet haben, können Sie die Authentifizierungs-UID abrufen und prüfen, ob alle Nutzer, die versuchen, in das Dokument zu schreiben, eine übereinstimmende Authentifizierungs-UID haben.
- Fügen Sie Ihrem Regelsatz die Lese- und Schreibregeln hinzu:
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;
}
}
}
Jetzt können nur angemeldete Nutzer Nachrichten im Gästebuch lesen, aber nur der Autor einer Nachricht kann sie bearbeiten.
- Fügen Sie eine Datenvalidierung hinzu, um sicherzustellen, dass alle erwarteten Felder im Dokument vorhanden sind:
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. Bonusschritt: Das Gelernte praktisch anwenden
Antwortstatus eines Teilnehmers aufzeichnen
Momentan können Personen in deiner App nur dann chatten, wenn sie an der Veranstaltung interessiert sind. Ob jemand kommt, kannst du nur darüber erfahren, wenn er dies im Chat sagt.
In diesem Schritt organisieren Sie sich und informieren die Leute darüber, wie viele Personen kommen. Sie fügen dem App-Status einige Funktionen hinzu. Die erste ist die Möglichkeit für einen angemeldeten Nutzer, anzugeben, ob er teilnimmt. Der zweite Wert gibt an, wie viele Personen teilnehmen.
- Fügen Sie in der Datei
lib/app_state.dart
dem Zugriffsbereich vonApplicationState
die folgenden Zeilen hinzu, damit der UI-Code mit diesem Status interagieren kann:
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});
}
}
- Aktualisieren Sie die Methode
init()
vonApplicationState
so:
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();
});
}
Dieser Code fügt eine immer abonnierte Abfrage hinzu, um die Anzahl der Teilnehmer zu ermitteln, und eine zweite Abfrage, die nur aktiv ist, während ein Nutzer angemeldet ist, um zu ermitteln, ob der Nutzer teilnimmt.
- Fügen Sie oben in der Datei
lib/app_state.dart
die folgende Aufzählung hinzu.
lib/app_state.dart
enum Attending { yes, no, unknown }
- Erstelle eine neue Datei
yes_no_selection.dart
und definiere ein neues Widget, das wie Optionsfelder funktioniert:
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'),
),
],
),
);
}
}
}
Sie beginnt in einem unbestimmten Zustand, wobei weder Ja noch Nein ausgewählt ist. Sobald der Nutzer ausgewählt hat, ob er teilnimmt, wird diese Option mit einer gefüllten Schaltfläche hervorgehoben und die andere Option wird mit einem flachen Rendering in den Hintergrund gerückt.
- Aktualisieren Sie die
build()
-Methode vonHomePage
, um die Vorteile vonYesNoSelection
nutzen zu können. Außerdem können angemeldete Nutzer angeben, ob sie teilnehmen können, und die Anzahl der Teilnehmer für den Termin anzeigen lassen:
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,
),
],
],
),
),
Regeln hinzufügen
Sie haben bereits einige Regeln eingerichtet. Daher werden die Daten, die Sie über die Schaltflächen hinzufügen, abgelehnt. Sie müssen die Regeln aktualisieren, um der Sammlung attendees
Elemente hinzufügen zu können.
- Rufen Sie in der Sammlung
attendees
die Authentifizierungs-UID ab, die Sie als Dokumentnamen verwendet haben, und prüfen Sie, ob dieuid
des Absenders mit dem Dokument übereinstimmt, das er schreiben:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
So kann jeder die Teilnehmerliste lesen, da sie keine privaten Daten enthält. Nur der Ersteller kann sie aktualisieren.
- Fügen Sie eine Datenvalidierung hinzu, damit alle erwarteten Felder im Dokument vorhanden sind:
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;
}
}
}
- Optional: Klicken Sie in der App auf Schaltflächen, um die Ergebnisse im Firestore-Dashboard in der Firebase Console aufzurufen.
App-Vorschau
10. Glückwunsch!
Sie haben mit Firebase eine interaktive Echtzeit-Web-App erstellt.