1. Hinweis
In diesem Codelab lernen Sie die Grundlagen von Firebase kennen, um mobile Flutter-Apps für Android und iOS zu erstellen.
Vorbereitung
- Vorkenntnisse mit Flutter
- Flutter SDK
- Einen Texteditor Ihrer Wahl
Lerninhalte
- Hier erfahren Sie, wie Sie mit Flutter eine App für die RSVP-Funktion und den Gästebuch-Chat für Android, iOS, das Web und macOS entwickeln.
- 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.
- Der iOS-Simulator (Xcode-Tools erforderlich)
- Der Android-Emulator (erfordert 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 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.
- 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 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 vorspulen oder sehen möchten, wie etwas nach einem Schritt aussehen sollte, suchen Sie im Verzeichnis nach dem Namen des gewünschten Schritts.
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 App ist auf mehrere Verzeichnisse verteilt. Diese Aufteilung der Funktionen erleichtert die Arbeit, da der Code nach Funktion 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 Authentifizierungsablauf werden in der Starter App noch nicht verwendet, Sie fügen sie aber bald hinzu.
Sie fügen nach Bedarf zusätzliche Dateien hinzu, um den Rest der App zu erstellen.
Datei lib/main.dart
prüfen
In dieser App wird das Paket google_fonts
verwendet, um Roboto als Standardschriftart in der gesamten App festzulegen. Sie können fonts.google.com besuchen und die dort gefundenen Schriftarten in verschiedenen Bereichen der App verwenden.
Sie verwenden die Hilfs-Widgets 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. Außerdem sorgt dies 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 einige dynamische Funktionen hinzufügen. Dazu müssen Sie Firebase mit Ihrer App verknüpfen. Um mit Firebase zu beginnen, 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 für die 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 finden Sie unter Firebase-Projekte verstehen.
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:Hier werden strukturierte Daten in der Cloud gespeichert und Sie werden sofort benachrichtigt, wenn sich Daten ändern.
- Firebase-Sicherheitsregeln:Schützen Ihre Datenbank.
Einige dieser Produkte erfordern eine spezielle Konfiguration oder Sie müssen sie in der Firebase Console aktivieren.
Authentifizierung per E-Mail-Anmeldung aktivieren
- Maximieren Sie im Bereich Projektübersicht der Firebase Console das Menü Build.
- Klicken Sie auf Authentifizierung > Jetzt starten > Anmeldemethode > E-Mail/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.
- Lassen Sie die Datenbank-ID auf
(default)
. - Wählen Sie einen Speicherort für die Datenbank aus und klicken Sie auf Weiter.
Für eine echte App sollten Sie einen Speicherort 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 richtig 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.
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: Authentifizierung 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. Außerdem verwenden Sie das provider
-Paket, mit dem sich die Geschäftslogik von der Darstellungslogik trennen lässt.
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 Befehlszeile 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 anhand der
.firebaserc
-Datei oder in der Firebase Console ein Firebase-Projekt aus. - Plattformen für die Konfiguration festlegen, z. B. Android, iOS, macOS und Web
- Geben Sie die Firebase-Apps an, 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
Mit Flutter auf macOS werden vollständig sandboxierte Apps erstellt. Da diese App in das Netzwerk eingebunden ist, um mit den Firebase-Servern zu kommunizieren, müssen Sie Ihre App 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 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-, iOS- und Web-Apps gibt es vorgefertigte FirebaseUI Auth
-Pakete. Für Flutter müssen Sie diese Funktion jedoch selbst 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 die Authentifizierung in die App einzubinden.
Geschäftslogik mit dem Provider
-Paket hinzufügen
Verwenden Sie das provider
-Paket, um ein zentrales App-Statusobjekt im gesamten Flutter-Widget-Baum 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();
});
}
}
Die import
-Anweisungen führen Firebase Core und Auth ein, laden das provider
-Paket ein, das das App-Statusobjekt im gesamten Widget-Baum verfügbar macht, und enthalten die Authentifizierungs-Widgets aus dem firebase_ui_auth
-Paket.
Dieses ApplicationState
-Anwendungsstatusobjekt hat für diesen Schritt eine Hauptaufgabe: Es benachrichtigt den Widget-Baum darüber, dass ein authentifizierter Status aktualisiert wurde.
Sie verwenden einen Anbieter nur, um den Anmeldestatus eines Nutzers an die App zu senden. Um einen Nutzer anzumelden, verwenden Sie die UIs des firebase_ui_auth
-Pakets. Dies ist eine gute Möglichkeit, Anmeldebildschirme in Ihren Apps schnell zu erstellen.
Authentifizierungsablauf einbinden
- Ändern Sie die Importe oben in 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 Authentifizierungsablauf 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. Sie verwenden diese spezifische provider
-Klasse, da das App-Statusobjekt die ChangeNotifier
-Klasse erweitert. So weiß das provider
-Paket, wann abhängige Widgets neu angezeigt werden müssen.
- Aktualisieren Sie Ihre App, damit die Navigation zu verschiedenen Bildschirmen, die FirebaseUI bietet, verarbeitet wird. 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!',
),
],
),
);
}
}
Sie instanziieren das AuthFunc
-Widget und fügen es in ein Consumer
-Widget ein. 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 sind die zusätzlichen Widgets, die Sie testen.
Authentifizierungsablauf testen
- Tippe in der App auf die Schaltfläche Teilnehmen, 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 der Fehlerbehandlung zu prüfen. Wenn Sie registriert sind, wird stattdessen das Passwort für angezeigt.
- Geben Sie falsche Passwörter ein, um die Fehlerbehandlung zu prü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 mitteilen, warum sie sich auf die Veranstaltung freuen oder wen sie dort treffen möchten.
Zum Speichern der Chatnachrichten, die Nutzer in der App schreiben, verwenden Sie Firestore.
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. Zuerst fügen Sie ein Formularfeld und eine Schaltfläche zum Senden hinzu und dann den Code, 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
in einem Expanded
-Widget eingeschlossen ist, wodurch das TextFormField
den gesamten zusätzlichen Platz in der Zeile ausfüllt. Weitere Informationen dazu finden Sie unter Einschränkungen.
Sie haben jetzt ein Widget, mit dem Nutzer Text eingeben und dem Gästebuch hinzufügen können. Jetzt müssen Sie es auf dem Bildschirm anzeigen.
- 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 Eingabefelds für Nachrichten wird der Sammlung guestbook
der Datenbank hinzugefügt. Die Methode addMessageToGuestBook
fügt 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. 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, in der Nutzer den Text eingeben können, den sie dem Gästebuch hinzufügen möchten, und Sie haben den Code, um den Eintrag Firestore hinzuzufügen. Jetzt müssen Sie nur noch die beiden Elemente 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 eine Nachricht reagieren, die jemand in der Benutzeroberfläche eingegeben hat, 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 Nachricht wird jedoch nicht in der eigentlichen Flutter-App angezeigt, da Sie das Abrufen der Daten noch implementieren müssen. Das tun Sie im nächsten Schritt. Im Dashboard „Datenbank“ der Firebase Console sehen Sie die hinzugefügte Nachricht jedoch in der Sammlung guestbook
. 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 ändern!
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 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 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 hier eine Abfrage für die Sammlung guestbook
erstellen und das Abonnieren und Abbestellen 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 passiert viel, deshalb sollten Sie es in einem Debugger untersuchen, um zu sehen, was passiert, und sich ein klareres mentales Modell zu machen. 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 werden sofort angezeigt.
- Ö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 in der Benutzeroberfläche angezeigt.
Glückwunsch! Sie lesen Firestore-Dokumente in Ihrer App.
App-Vorschau
8. Grundlegende Sicherheitsregeln einrichten
Sie haben Firestore ursprünglich so eingerichtet, dass der Testmodus verwendet wird. Das bedeutet, dass Ihre Datenbank für Lese- und Schreibvorgänge geöffnet ist. Sie sollten den Testmodus jedoch nur in den frühen Phasen der Entwicklung verwenden. Es empfiehlt sich, beim Entwickeln Ihrer App Sicherheitsregeln für Ihre Datenbank einzurichten. 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 für beliebige Bereiche erstellen – von sämtlichen Schreibvorgängen über die gesamte Datenbank bis hin zu Vorgängen in einem bestimmten Dokument.
Richten Sie grundlegende Sicherheitsregeln ein:
- Klicken Sie in der Firebase Console im Menü Entwickeln 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:
Wählen Sie unter match /databases/{database}/documents
die Sammlung aus, 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 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, damit 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: Gelerntes anwenden
Antwortstatus eines Teilnehmers erfassen
Derzeit können Nutzer in Ihrer App nur chatten, wenn sie sich für die Veranstaltung interessieren. Außerdem wissen Sie nur dann, ob jemand kommt, wenn er es 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 ist ein Zähler, der angibt, wie viele Personen anwesend sind.
- Fügen Sie in der Datei
lib/app_state.dart
dem Abschnitt „Accessors“ derApplicationState
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();
});
}
Mit diesem Code wird eine immer abonnierte Abfrage hinzugefügt, um die Anzahl der Teilnehmer zu ermitteln, und eine zweite Abfrage, die nur aktiv ist, wenn ein Nutzer angemeldet ist, um zu ermitteln, 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'),
),
],
),
);
}
}
}
Zu Beginn ist der Status unbestimmt und weder Ja noch Nein ist ausgewählt. 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 ausgeblendet.
- Aktualisiere die
build()
-Methode vonHomePage
, umYesNoSelection
zu nutzen, damit ein angemeldeter Nutzer angeben kann, ob er an der Veranstaltung teilnimmt, und die Anzahl der Teilnehmer für die Veranstaltung angezeigt wird:
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 Einreichenden 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 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-Webanwendung erstellt.