1. Hinweis
In diesem Codelab lernen Sie einige Grundlagen von Firebase kennen, um mobile Flutter-Apps für Android und iOS zu erstellen.
Vorbereitung
- Vertrautheit mit Flutter
- Flutter SDK
- Ein Texteditor Ihrer Wahl
Lerninhalte
- So erstellen Sie mit Flutter eine Chat-App für Event-RSVP und Gästebücher für Android, iOS, das Web und macOS.
- 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 in den Entwicklermodus versetzt ist.
- Der iOS-Simulator (erfordert Xcode-Tools).
- Der Android-Emulator (erfordert die Einrichtung in Android Studio).
Außerdem benötigen Sie Folgendes:
- Einen Browser Ihrer Wahl, z. B. Google Chrome.
- Eine IDE oder ein Texteditor Ihrer Wahl, die mit den Dart- und Flutter-Plug-ins konfiguriert ist, z. B. Android Studio oder Visual Studio Code.
- Die aktuelle
stable
-Version von Flutter oderbeta
, wenn Sie gerne die neuesten Funktionen ausprobieren. - Ein Google-Konto zum Erstellen und Verwalten Ihres Firebase-Projekts.
- Die
Firebase
CLI ist 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 im 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. Sie befinden sich beispielsweise im zweiten Schritt.
- Suchen Sie die entsprechenden Dateien für den zweiten Schritt:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
Wenn Sie einen Schritt überspringen oder sehen möchten, wie etwas nach einem Schritt aussehen sollte, suchen Sie im Verzeichnis, das nach dem entsprechenden Schritt benannt ist.
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, der aus einer noch nicht funktionsfähigen Flutter-Meetup-App besteht.
Dateien finden, die bearbeitet werden müssen
Der Code in dieser App ist auf mehrere Verzeichnisse verteilt. Durch diese Aufteilung der Funktionalität wird die Arbeit erleichtert, da der Code nach Funktionalität 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 Starter-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-Authentifizierung auf E-Mail-Basis zu erstellen. Diese Widgets für den Authentifizierungsablauf werden noch nicht in der Starter-App verwendet, aber du kannst sie bald hinzufügen.
Fügen Sie nach Bedarf weitere Dateien hinzu, um den Rest der App zu erstellen.
Datei lib/main.dart
prüfen
In dieser App wird das google_fonts
-Paket verwendet, um Roboto als Standardschriftart in der gesamten App festzulegen. Auf fonts.google.com finden Sie weitere Schriftarten, die Sie in verschiedenen Teilen der App verwenden können.
Sie verwenden die Hilfswidgets aus der Datei lib/src/widgets.dart
in Form von Header
, Paragraph
und IconAndDetail
. Diese Widgets eliminieren doppelten Code, um das in HomePage
beschriebene Seitenlayout übersichtlicher zu gestalten. So lässt sich auch ein einheitliches Erscheinungsbild erzielen.
So sieht Ihre App auf Android, iOS, im Web und auf macOS aus:
3. Firebase-Projekt erstellen und einrichten
Die Anzeige von Veranstaltungsinformationen ist zwar für Ihre Gäste hilfreich, aber für sich genommen nicht sehr nützlich. Sie müssen der App einige dynamische Funktionen hinzufügen. Dazu müssen Sie Firebase mit Ihrer App verbinden. Um mit Firebase zu beginnen, müssen Sie ein Firebase-Projekt erstellen und einrichten.
Firebase-Projekt erstellen
- Melden Sie sich mit Ihrem Google-Konto in der Firebase Console an.
- Klicken Sie auf die Schaltfläche, um ein neues Projekt zu erstellen, und geben Sie dann einen Projektnamen ein (z. B.
Firebase-Flutter-Codelab
).
- Klicken Sie auf Weiter.
- Lesen und akzeptieren Sie bei Aufforderung die Firebase-Nutzungsbedingungen und klicken Sie dann auf Weiter.
- (Optional) Aktivieren Sie die KI-Unterstützung in der Firebase Console (als „Gemini in Firebase“ bezeichnet).
- Für dieses Codelab benötigen Sie kein Google Analytics. Deaktivieren Sie daher die Google Analytics-Option.
- Klicken Sie auf Projekt erstellen, warten Sie, bis Ihr Projekt bereitgestellt wurde, und klicken Sie dann auf Weiter.
Weitere Informationen zu Firebase-Projekten
Firebase-Produkte einrichten
Die App verwendet die folgenden Firebase-Produkte, die für Web-Apps verfügbar sind:
- Authentifizierung:Ermöglicht Nutzern, sich in Ihrer App anzumelden.
- Firestore:Speichert strukturierte Daten in der Cloud und benachrichtigt Sie sofort, wenn sich Daten ändern.
- Firebase-Sicherheitsregeln:Schützen Ihre Datenbank.
Für einige dieser Produkte ist eine spezielle Konfiguration erforderlich oder Sie müssen sie in der Firebase Console aktivieren.
Authentifizierung für die Anmeldung per E-Mail-Adresse aktivieren
- Maximieren Sie in der Firebase Console im Bereich Projektübersicht das Menü Build.
- Klicken Sie auf Authentifizierung > Erste Schritte > Anmeldemethode > E-Mail-Adresse/Passwort > Aktivieren > Speichern.
Firestore einrichten
Die Web-App 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 Database ID (Datenbank-ID) auf
(default)
. - Wählen Sie einen Speicherort für Ihre Datenbank aus und klicken Sie auf Weiter.
Für eine echte App sollten Sie einen Speicherort auswählen, der sich in der Nähe Ihrer Nutzer befindet. - 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 Sie eine App nicht öffentlich, 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 für die korrekte Verwendung der FlutterFire
-Bibliotheken zu konfigurieren:
- Fügen Sie Ihrem Projekt die
FlutterFire
-Abhängigkeiten hinzu. - Registrieren Sie die gewünschte Plattform im Firebase-Projekt.
- Laden Sie die plattformspezifische Konfigurationsdatei herunter und fügen Sie sie dem Code hinzu.
Im übergeordneten Verzeichnis Ihrer Flutter-App befinden sich die Unterverzeichnisse android
, ios
, macos
und web
, die die plattformspezifischen Konfigurationsdateien für iOS bzw. Android enthalten.
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 enthält den gemeinsamen Code, der für alle Firebase-Flutter-Plug‑ins erforderlich ist.
$ flutter pub add firebase_auth
Das firebase_auth
-Paket ermöglicht die Integration in die 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, um die Entwicklungsgeschwindigkeit bei Authentifizierungsabläufen zu erhöhen.
$ flutter pub add firebase_ui_auth
Sie haben die erforderlichen Pakete hinzugefügt, müssen aber auch die Runner-Projekte für iOS, Android, macOS und das Web so konfigurieren, dass Firebase richtig verwendet wird. Außerdem verwenden Sie das provider
-Paket, mit dem die Geschäftslogik von der Darstellungslogik getrennt werden kann.
FlutterFire CLI installieren
Die FlutterFire CLI hängt von der zugrunde liegenden Firebase CLI ab.
- Falls noch nicht geschehen, installieren Sie die Firebase CLI auf Ihrem Computer.
- Installieren Sie die FlutterFire CLI:
$ dart pub global activate flutterfire_cli
Nach der Installation ist der flutterfire
-Befehl global 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 App den Befehl configure
aus:
$ flutterfire configure
Der Konfigurationsbefehl führt Sie durch die folgenden Prozesse:
- Wählen Sie ein Firebase-Projekt anhand der
.firebaserc
-Datei oder in der Firebase Console aus. - Legen Sie Plattformen für die Konfiguration fest, z. B. Android, iOS, macOS und Web.
- Ermitteln Sie die Firebase-Apps, aus denen die Konfiguration extrahiert werden soll. Standardmäßig versucht die CLI, Firebase-Apps automatisch anhand Ihrer aktuellen Projektkonfiguration abzugleichen.
- Generieren Sie eine
firebase_options.dart
-Datei in Ihrem Projekt.
macOS konfigurieren
Mit Flutter auf macOS werden vollständig in einer Sandbox ausgeführte Apps erstellt. Da diese App in das Netzwerk eingebunden ist, um mit den Firebase-Servern zu kommunizieren, müssen Sie sie mit Netzwerkclientberechtigungen 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 Antworten hinzufügen
Nachdem Sie Firebase der App hinzugefügt haben, können Sie eine RSVP-Schaltfläche erstellen, mit der sich Nutzer mit Authentication registrieren können. Für Android Native, iOS Native und Web sind vorgefertigte FirebaseUI Auth
-Pakete verfügbar. Für Flutter müssen Sie diese Funktion jedoch selbst entwickeln.
Das Projekt, das Sie zuvor abgerufen haben, enthält eine Reihe von Widgets, die die Benutzeroberfläche für den Großteil des Authentifizierungsablaufs implementieren. Sie implementieren die Geschäftslogik, um die Authentifizierung in die App einzubinden.
Geschäftslogik mit dem Provider
-Paket hinzufügen
Mit dem provider
-Paket können Sie ein zentrales App-Statusobjekt im gesamten Baum der Flutter-Widgets der App verfügbar 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 eingeführt, das provider
-Paket wird abgerufen, mit dem das App-Statusobjekt im gesamten Widget-Baum verfügbar ist, und die Authentifizierungs-Widgets aus dem firebase_ui_auth
-Paket werden eingefügt.
Dieses ApplicationState
-Objekt für den Anwendungsstatus hat in diesem Schritt eine Hauptaufgabe: Es muss den Widget-Baum darüber informieren, dass der authentifizierte Status aktualisiert wurde.
Sie verwenden einen Anbieter nur, um den Anmeldestatus eines Nutzers an die App zu kommunizieren. Damit sich ein Nutzer anmelden kann, verwenden Sie die Benutzeroberflächen, die vom firebase_ui_auth
-Paket bereitgestellt werden. So lassen sich Anmeldebildschirme in Ihren Apps schnell einrichten.
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 App-Initialisierung 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 der Funktion main()
ist das Anbieterpaket für die Instanziierung des App-Statusobjekts mit dem Widget ChangeNotifierProvider
verantwortlich. Sie verwenden diese spezielle provider
-Klasse, weil das App-Zustandsobjekt die ChangeNotifier
-Klasse erweitert. Dadurch weiß das provider
-Paket, wann abhängige Widgets neu angezeigt werden müssen.
- Aktualisieren Sie Ihre App, um die Navigation zu verschiedenen Bildschirmen zu ermöglichen, 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 Art von Aktion zugeordnet, die auf dem neuen Status des Authentifizierungsablaufs basiert. Nach den meisten Statusänderungen bei der Authentifizierung können Sie zurück zu einem bevorzugten Bildschirm weiterleiten, z. B. zum Startbildschirm oder zu einem anderen Bildschirm wie dem Profil.
- Binden Sie in der Build-Methode der Klasse
HomePage
den App-Status in das WidgetAuthFunc
ein:
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!',
),
],
),
);
}
}
Sie instanziieren das AuthFunc
-Widget und umschließen es mit einem Consumer
-Widget. Das Consumer-Widget ist die übliche Methode, mit der das provider
-Paket verwendet werden kann, um einen Teil des Baums neu zu erstellen, wenn sich der App-Status ändert. Das AuthFunc
-Widget ist das zusätzliche Widget, das Sie testen.
Authentifizierungsvorgang testen
- Tippe in der App auf die Schaltfläche Zusagen/Absagen, um die
SignInScreen
zu starten.
- Geben Sie eine E-Mail-Adresse ein. Wenn Sie bereits registriert sind, werden Sie aufgefordert, ein Passwort einzugeben. Andernfalls werden Sie aufgefordert, das Registrierungsformular auszufüllen.
- Geben Sie ein Passwort mit weniger als sechs Zeichen ein, um den Fehlerbehandlungsablauf zu testen. Wenn Sie registriert sind, sehen Sie stattdessen das Passwort für.
- Geben Sie falsche Passwörter ein, um den Fehlerbehandlungsablauf zu prüfen.
- Geben Sie das richtige Passwort ein. Sie sehen die Ansicht für angemeldete Nutzer, in der sich der Nutzer abmelden kann.
6. Nachrichten in Firestore schreiben
Es ist gut zu wissen, dass Nutzer kommen, aber du musst den Gästen auch etwas in der App bieten. Was wäre, wenn sie Nachrichten in einem Gästebuch hinterlassen könnten? Sie können schreiben, warum sie sich auf die Veranstaltung freuen oder wen sie dort treffen möchten.
Zum Speichern der Chat-Nachrichten, die Nutzer in der App schreiben, verwenden Sie Firestore.
Datenmodell
Firestore ist eine NoSQL-Datenbank. Die darin gespeicherten Daten werden in Sammlungen, Dokumente, Felder und untergeordnete Sammlungen aufgeteilt. Jede Nachricht des Chats wird als Dokument in einer Sammlung guestbook
gespeichert, die eine Sammlung auf oberster Ebene ist.
Nachrichten in Firestore hinzufügen
In diesem Abschnitt fügen Sie die Funktion hinzu, mit der Nutzer Nachrichten in die Datenbank schreiben können. Zuerst fügen Sie ein Formularfeld und eine Schaltfläche zum 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
-Stateful-Widget 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 Inhalt enthält, und dem Nutzer eine Fehlermeldung anzeigen können, wenn 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 Wann Sie Schlüssel verwenden sollten.
Beachten Sie auch die Anordnung der Widgets. Sie haben ein Row
mit einem TextFormField
und einem StyledButton
, das ein Row
enthält. Beachten Sie außerdem, dass TextFormField
in ein Expanded
-Widget eingeschlossen ist. Dadurch wird TextFormField
gezwungen, den zusätzlichen Platz in der Zeile auszufüllen. Weitere Informationen dazu, warum dies erforderlich ist, finden Sie unter Einschränkungen.
Jetzt haben Sie ein Widget, mit dem der Nutzer Text eingeben kann, der dem Gästebuch hinzugefügt werden soll. Sie müssen es nur noch auf dem Bildschirm anzeigen.
- Bearbeiten Sie den Textkörper von
HomePage
, um die folgenden zwei Zeilen am Ende der untergeordneten Elemente vonListView
hinzuzufügen:
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 werden diesen Code in Kürze aktualisieren, damit er funktioniert.
App-Vorschau
Wenn ein Nutzer auf SENDEN klickt, wird das folgende Code-Snippet ausgelöst. Es fügt den Inhalt des Nachrichteneingabefelds der Sammlung guestbook
der Datenbank hinzu. Mit der Methode addMessageToGuestBook
wird der Nachrichteninhalt einem neuen Dokument mit einer automatisch generierten ID in der Sammlung guestbook
hinzugefügt.
FirebaseAuth.instance.currentUser.uid
ist eine Referenz auf die automatisch generierte eindeutige ID, die von Authentication für alle angemeldeten Nutzer bereitgestellt wird.
- Fügen Sie in der Datei
lib/app_state.dart
die MethodeaddMessageToGuestBook
hinzu. Im nächsten Schritt verbinden Sie diese Funktion 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.
}
Benutzeroberfläche und Datenbank verbinden
Sie haben eine Benutzeroberfläche, über die 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 am WidgetHomePage
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 am Anfang 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 in der Benutzeroberfläche eingeben, und diese 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 dann auf SENDEN.
Mit dieser Aktion wird die Nachricht in Ihre Firestore-Datenbank geschrieben. Die Nachricht wird jedoch nicht in der eigentlichen Flutter-App angezeigt, da Sie das Abrufen der Daten noch implementieren müssen. Das erfolgt im nächsten Schritt. Im Datenbank-Dashboard in der Firebase Console können Sie die hinzugefügte Nachricht in der Sammlung guestbook
sehen. Wenn Sie weitere Nachrichten senden, fügen Sie Ihrer Sammlung guestbook
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, aber sie können sie noch nicht in der App sehen. Das müssen wir ändern!
Nachrichten synchronisieren
Um Nachrichten anzuzeigen, 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 aus 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 ermöglichen, 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
ApplicationState
, in dem Sie den Status und die 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 Initialisierungsabschnitt von
ApplicationState
die folgenden Zeilen hinzu, um eine Anfrage für die Dokumentensammlung zu abonnieren, wenn sich ein Nutzer anmeldet, und das Abo zu beenden, 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 hier eine Abfrage für die Sammlung guestbook
erstellen und das Abonnieren und Abbestellen dieser Sammlung verarbeiten. Sie hören sich den Stream an, in dem Sie einen lokalen Cache der Nachrichten in der Sammlung guestbook
rekonstruieren und auch einen Verweis auf dieses Abo speichern, damit Sie es später abbestellen können. Hier passiert viel. Sie sollten den Code daher in einem Debugger untersuchen, um ein klareres Bild davon zu erhalten, was passiert. Weitere Informationen finden Sie unter Echtzeitaktualisierungen mit Firestore erhalten.
- 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 eine Liste von Nachrichten als Teil der Konfiguration hinzu, um diesen sich ändernden Status mit der Benutzeroberfläche zu verbinden:
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
so, dass diese Konfiguration verfügbar gemacht wird:
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 Liste der Nachrichten ein neues Paragraph
zu generieren.
- Aktualisieren Sie den Text von
HomePage
, umGuestBook
mit dem neuen Parametermessages
korrekt zu erstellen:
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 die Datenbank abonniert haben.
Nachrichtensynchronisierung testen:
- Suchen Sie in der App nach den Nachrichten, die Sie zuvor in der Datenbank erstellt haben.
- Neue Nachrichten schreiben Sie werden sofort angezeigt.
- Öffnen Sie Ihren Arbeitsbereich in mehreren Fenstern oder Tabs. Die Nachrichten werden in Echtzeit zwischen den Fenstern und Tabs synchronisiert.
- Optional: Löschen, ändern oder fügen Sie im Menü Datenbank der Firebase Console manuell neue Nachrichten hinzu. Alle Änderungen werden in der Benutzeroberfläche angezeigt.
Glückwunsch! Sie können Firestore-Dokumente in Ihrer App lesen.
App-Vorschau
8. Einfache Sicherheitsregeln einrichten
Sie haben Firestore ursprünglich für die Verwendung im Testmodus eingerichtet. Das bedeutet, dass Ihre Datenbank für Lese- und Schreibvorgänge geöffnet ist. Sie sollten den Testmodus jedoch nur in der frühen Entwicklungsphase verwenden. Es empfiehlt sich, Sicherheitsregeln für Ihre Datenbank einzurichten, während Sie Ihre App entwickeln. Sicherheit ist ein integraler 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 für beliebige Bereiche erstellen – von sämtlichen Schreibvorgängen über die gesamte Datenbank bis hin zu Vorgängen in einem bestimmten Dokument.
Einfache Sicherheitsregeln einrichten:
- Klicken Sie in der Firebase Console im Menü Entwickeln auf Datenbank > Regeln. Sie sollten die folgenden Standardsicherheitsregeln und eine Warnung sehen, dass die Regeln öffentlich sind:
- Ermitteln Sie die Sammlungen, in die die App Daten schreibt:
Geben Sie in match /databases/{database}/documents
die Sammlung an, die Sie schützen 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 jeder, der versucht, in das Dokument zu schreiben, eine passende Authentifizierungs-UID hat.
- Fügen Sie dem 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. Bearbeiten kann eine Nachricht aber nur der Autor.
- 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 üben
Antwortstatus eines Teilnehmers aufzeichnen
Derzeit können Nutzer in Ihrer App nur chatten, wenn sie sich für die Veranstaltung interessieren. Außerdem erfahren Sie nur dann, ob jemand kommt, wenn er es im Chat sagt.
In diesem Schritt organisieren Sie sich und teilen den Teilnehmern mit, wie viele Personen kommen. Sie fügen dem App-Status einige Funktionen hinzu. Erstens können angemeldete Nutzer angeben, ob sie teilnehmen. Die zweite ist ein Zähler, der angibt, wie viele Personen teilnehmen.
- Fügen Sie in der Datei
lib/app_state.dart
die folgenden Zeilen in den Abschnitt „accessors“ desApplicationState
ein, 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();
});
}
Mit diesem Code wird eine Abfrage hinzugefügt, die immer ausgeführt wird, um die Anzahl der Teilnehmer zu ermitteln. Außerdem wird eine zweite Abfrage hinzugefügt, die nur aktiv ist, wenn ein Nutzer angemeldet ist, um festzustellen, ob der Nutzer teilnimmt.
- Fügen Sie die folgende Aufzählung oben in die Datei
lib/app_state.dart
ein.
lib/app_state.dart
enum Attending { yes, no, unknown }
- Erstellen Sie eine neue Datei
yes_no_selection.dart
und definieren Sie 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, in dem 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 flach dargestellt.
- Aktualisieren Sie die
build()
-Methode vonHomePage
, umYesNoSelection
zu nutzen, einem angemeldeten Nutzer die Möglichkeit zu geben, anzugeben, ob er teilnimmt, und die Anzahl der Teilnehmer für die Veranstaltung anzuzeigen:
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. Die Daten, die Sie über die Schaltflächen hinzufügen, werden daher abgelehnt. Sie müssen die Regeln aktualisieren, damit Elemente der Sammlung attendees
hinzugefügt werden können.
- Suchen Sie in der Sammlung
attendees
nach der Authentifizierungs-UID, die Sie als Dokumentname verwendet haben, und prüfen Sie, ob dieuid
des Absenders mit dem Dokument übereinstimmt, das er schreibt:
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 jedoch aktualisieren.
- 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 /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 mithilfe von Firebase eine interaktive Web-App mit Echtzeitfunktionen erstellt.