1. Bevor Sie beginnen
In diesem Codelab lernen Sie einige der Grundlagen von Firebase zum Erstellen mobiler Flutter-Apps für Android und iOS.
Voraussetzungen
- Vertrautheit mit Flutter
- Das Flutter SDK
- Ein Texteditor Ihrer Wahl
Was Sie lernen werden
- So erstellen Sie mit Flutter eine Event-RSVP- und Gästebuch-Chat-App auf Android, iOS, im Web und macOS.
- So authentifizieren Sie Benutzer mit der Firebase-Authentifizierung und synchronisieren Daten mit Firestore.
Was du brauchen wirst
Eines der folgenden Geräte:
- Ein physisches Android- oder iOS-Gerät, das mit Ihrem Computer verbunden und auf den Entwicklermodus eingestellt ist.
- Der iOS-Simulator (erfordert Xcode-Tools ).
- Der Android-Emulator (Setup in Android Studio erforderlich).
Sie benötigen außerdem Folgendes:
- Ein Browser Ihrer Wahl, z. B. Google Chrome.
- Eine IDE oder ein Texteditor Ihrer Wahl, konfiguriert mit den Dart- und Flutter-Plugins, z. B. Android Studio oder Visual Studio Code .
- Die neueste
stable
Version von Flutter oderbeta
, wenn Sie gerne am Rande leben. - Ein Google-Konto für die Erstellung und Verwaltung Ihres Firebase-Projekts.
- Die
Firebase
CLI meldet sich bei Ihrem Google-Konto an.
2. Holen Sie sich den Beispielcode
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 Schnappschüssen, die zeigen, wie Ihr Projekt am Ende jedes Schritts aussehen sollte. Sie befinden sich beispielsweise im zweiten Schritt.
- Finden Sie die passenden 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 soll, schauen Sie in dem Verzeichnis nach, das nach dem Schritt benannt ist, an dem Sie interessiert sind.
Importieren Sie die Starter-App
- Öffnen oder importieren Sie das Verzeichnis
flutter-codelabs/firebase-get-to-know-flutter/step_02
in Ihrer bevorzugten IDE. Dieses Verzeichnis enthält den Startercode für das Codelab, das aus einer noch nicht funktionsfähigen Flutter-Meetup-App besteht.
Suchen Sie die Dateien, die bearbeitet werden müssen
Der Code in dieser App ist über mehrere Verzeichnisse verteilt. Diese Aufteilung der Funktionalität erleichtert die Arbeit, 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 Homepage-Widget. -
lib/src/widgets.dart
: Diese Datei enthält eine Handvoll Widgets, die dabei helfen, den Stil der App zu standardisieren. 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 Login-Benutzererfahrung für die E-Mail-basierte Firebase-Authentifizierung zu erstellen. Diese Widgets für den Authentifizierungsfluss werden in der Starter-App noch nicht verwendet, aber Sie fügen sie bald hinzu.
-
Sie fügen nach Bedarf zusätzliche Dateien hinzu, um den Rest der App zu erstellen.
Überprüfen Sie die Datei lib/main.dart
Diese App nutzt das google_fonts
Paket, um Roboto zur Standardschriftart in der gesamten App zu machen. Sie können „fonts.google.com“ durchsuchen und die Schriftarten, die Sie dort entdecken, in verschiedenen Teilen 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. Dies ermöglicht auch ein einheitliches Erscheinungsbild.
So sieht Ihre App auf Android, iOS, im Web und macOS aus:
3. Erstellen und konfigurieren Sie ein Firebase-Projekt
Die Anzeige von Veranstaltungsinformationen ist für Ihre Gäste großartig, aber für niemanden allein ist sie 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 konfigurieren.
Erstellen Sie ein Firebase-Projekt
- Melden Sie sich bei Firebase an.
- Klicken Sie in der Konsole auf Projekt hinzufügen oder Projekt erstellen .
- Geben Sie im Feld „Projektname“ „Firebase-Flutter-Codelab“ ein und klicken Sie dann auf „Weiter“ .
- Klicken Sie sich durch die Projekterstellungsoptionen. Wenn Sie dazu aufgefordert werden, akzeptieren Sie die Firebase-Bedingungen, überspringen Sie jedoch die Einrichtung von Google Analytics, da Sie es für diese App nicht verwenden werden.
Weitere Informationen zu Firebase-Projekten finden Sie unter Grundlegendes zu Firebase-Projekten .
Die App nutzt die folgenden Firebase-Produkte, die für Web-Apps verfügbar sind:
- Authentifizierung: Ermöglicht Benutzern die Anmeldung bei Ihrer App.
- Firestore: Speichert strukturierte Daten in der Cloud und erhält sofortige Benachrichtigungen, wenn sich Daten ändern.
- Firebase-Sicherheitsregeln: Sichert Ihre Datenbank.
Einige dieser Produkte erfordern eine spezielle Konfiguration oder Sie müssen sie in der Firebase-Konsole aktivieren.
Aktivieren Sie die E-Mail-Anmeldeauthentifizierung
- Erweitern Sie im Projektübersichtsbereich der Firebase-Konsole das Menü „Erstellen“ .
- Klicken Sie auf Authentifizierung > Erste Schritte > Anmeldemethode > E-Mail/Passwort > Aktivieren > Speichern .
Aktivieren Sie Firestore
Die Web-App verwendet Firestore, um Chat-Nachrichten zu speichern und neue Chat-Nachrichten zu empfangen.
Firestore aktivieren:
- Klicken Sie im Menü „Erstellen“ auf Firestore-Datenbank > Datenbank erstellen .
- Wählen Sie „Im Testmodus starten“ und lesen Sie dann den Haftungsausschluss zu den Sicherheitsregeln. Der Testmodus stellt sicher, dass Sie während der Entwicklung frei in die Datenbank schreiben können.
- Klicken Sie auf Weiter und wählen Sie dann den Speicherort für Ihre Datenbank aus. Sie können die Standardeinstellung verwenden. Sie können den Standort später nicht mehr ändern.
- Klicken Sie auf Aktivieren .
4. Konfigurieren Sie Firebase
Um Firebase mit Flutter zu verwenden, 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 die
FlutterFire
Abhängigkeiten zu Ihrem Projekt hinzu. - Registrieren Sie die gewünschte Plattform im Firebase-Projekt.
- Laden Sie die plattformspezifische Konfigurationsdatei herunter und fügen Sie sie dann dem Code hinzu.
Im obersten Verzeichnis Ihrer Flutter-App gibt es 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 über die Befehlszeile die folgenden Abhängigkeiten hinzu:
$ flutter pub add firebase_core
Das Paket firebase_core
ist der gemeinsame Code, der für alle Firebase Flutter-Plugins erforderlich ist.
$ flutter pub add firebase_auth
Das Paket firebase_auth
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 Paket firebase_ui_auth
stellt eine Reihe von Widgets und Dienstprogrammen bereit, um die Entwicklergeschwindigkeit mit Authentifizierungsabläufen zu erhöhen.
$ flutter pub add firebase_ui_auth
Sie haben die erforderlichen Pakete hinzugefügt, müssen aber auch die iOS-, Android-, macOS- und Web Runner-Projekte konfigurieren, um Firebase ordnungsgemäß zu verwenden. Sie verwenden auch das provider
, das die Trennung von Geschäftslogik und Anzeigelogik ermöglicht.
Installieren Sie die FlutterFire-CLI
Die FlutterFire-CLI hängt von der zugrunde liegenden Firebase-CLI ab.
- Falls Sie dies noch nicht getan haben, installieren Sie die Firebase-CLI auf Ihrem Computer.
- Installieren Sie die FlutterFire-CLI:
$ dart pub global activate flutterfire_cli
Nach der Installation ist der Befehl flutterfire
weltweit verfügbar.
Konfigurieren Sie Ihre Apps
Die CLI extrahiert Informationen aus Ihrem Firebase-Projekt und 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 basierend auf der
.firebaserc
Datei oder aus der Firebase-Konsole aus. - Bestimmen Sie Plattformen für die Konfiguration, 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 basierend auf Ihrer aktuellen Projektkonfiguration automatisch abzugleichen.
- Generieren Sie eine Datei
firebase_options.dart
in Ihrem Projekt.
Konfigurieren Sie macOS
Flutter unter macOS erstellt vollständig Sandbox-Apps. Da diese App in das Netzwerk integriert wird, um mit den Firebase-Servern zu kommunizieren, müssen Sie Ihre App mit Netzwerk-Client-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 Desktop-Unterstützung für Flutter .
5. Fügen Sie die RSVP-Funktionalität hinzu
Nachdem Sie Firebase nun zur App hinzugefügt haben, können Sie eine RSVP- Schaltfläche erstellen, die Personen mit Authentifizierung registriert. Für natives Android, natives iOS und Web gibt es vorgefertigte FirebaseUI Auth
Pakete, Sie müssen diese Funktion jedoch 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 die Authentifizierung in die App zu integrieren.
Fügen Sie Geschäftslogik mit dem Provider
-Paket hinzu
Verwenden Sie das provider
, um ein zentralisiertes App-Statusobjekt im gesamten Flutter-Widget-Baum der App verfügbar zu machen:
- Erstellen Sie eine neue Datei namens
app_state.dart
mit 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
führen Firebase Core und Auth ein, ziehen das provider
ein, das das App-Statusobjekt im gesamten Widget-Baum verfügbar macht, und schließen die Authentifizierungs-Widgets aus dem Paket firebase_ui_auth
ein.
Dieses ApplicationState
Anwendungsstatusobjekt hat für diesen Schritt eine Hauptaufgabe: Es soll den Widget-Baum darüber informieren, dass eine Aktualisierung auf einen authentifizierten Status stattgefunden hat.
Sie verwenden einen Anbieter lediglich, um der App den Status des Anmeldestatus eines Benutzers mitzuteilen. Um einem Benutzer die Anmeldung zu ermöglichen, verwenden Sie die vom Paket firebase_ui_auth
bereitgestellten Benutzeroberflächen. Dies ist eine hervorragende Möglichkeit, Anmeldebildschirme in Ihren Apps schnell zu booten.
Integrieren Sie den Authentifizierungsfluss
- Ä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 Authentifizierungsfluss 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 ChangeNotifierProvider
-Widget verantwortlich. Sie verwenden diese spezielle provider
, da das App-Statusobjekt die ChangeNotifier
Klasse erweitert, die dem provider
mitteilt, wann abhängige Widgets erneut angezeigt werden müssen.
- Aktualisieren Sie Ihre App, um die Navigation zu verschiedenen Bildschirmen zu ermöglichen, die FirebaseUI für Sie bereitstellt, indem Sie eine
GoRouter
Konfiguration erstellen:
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 basierend auf dem neuen Status des Authentifizierungsablaufs eine andere Art von Aktion zugeordnet. Nach den meisten Statusänderungen bei der Authentifizierung können Sie zu einem bevorzugten Bildschirm zurückkehren, sei es der Startbildschirm oder ein anderer Bildschirm, z. B. das Profil.
- Integrieren Sie in der Build-Methode der
HomePage
Klasse den App-Status mit demAuthFunc
-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 binden es in ein Consumer
Widget ein. Das Consumer-Widget ist die übliche Möglichkeit, mit dem provider
einen Teil des Baums neu zu erstellen, wenn sich der App-Status ändert. Das AuthFunc
Widget ist das zusätzliche Widget, das Sie testen.
Testen Sie den Authentifizierungsablauf
- Tippen Sie in der App auf die RSVP- Schaltfläche, um den
SignInScreen
zu starten.
- Gib 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 Fehlerbehandlungsablauf zu überprüfen. Wenn Sie registriert sind, sehen Sie stattdessen das Passwort für.
- Geben Sie falsche Passwörter ein, um den Fehlerbehandlungsablauf zu überprüfen.
- Geben Sie das richtige Passwort ein. Sie sehen die Anmeldeerfahrung, die dem Benutzer die Möglichkeit bietet, sich abzumelden.
6. Schreiben Sie Nachrichten an Firestore
Es ist schön zu wissen, dass Benutzer kommen, aber Sie müssen den Gästen in der App etwas anderes zu tun geben. Was wäre, wenn sie Nachrichten in einem Gästebuch hinterlassen könnten? Sie können mitteilen, warum sie sich auf den Besuch freuen oder wen sie kennenzulernen hoffen.
Um die Chatnachrichten, die Benutzer in der App schreiben, zu speichern, verwenden Sie Firestore .
Datenmodell
Firestore ist eine NoSQL-Datenbank und die in der Datenbank gespeicherten Daten sind in Sammlungen, Dokumente, Felder und Untersammlungen aufgeteilt. Sie speichern jede Nachricht des Chats als Dokument in einer guestbook
, einer Sammlung auf oberster Ebene.
Fügen Sie Nachrichten zum Firestore hinzu
In diesem Abschnitt fügen Sie die Funktionalität hinzu, mit der Benutzer Nachrichten in die Datenbank schreiben können. Zuerst fügen Sie ein Formularfeld und eine Schaltfläche zum Senden hinzu und dann 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 ein zustandsbehaftetesGuestBook
Widget hinzu, um die UI-Elemente eines Nachrichtenfelds und einer Schaltfläche „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 überprüfen können, ob die Nachricht tatsächlich Inhalt enthält, und dem Benutzer eine Fehlermeldung anzeigen können, wenn keiner vorhanden ist. Um ein Formular zu validieren, greifen Sie mit einem GlobalKey
auf den Formularstatus hinter dem Formular zu. Weitere Informationen zu Schlüsseln und deren Verwendung finden Sie unter Verwendung von Schlüsseln .
Beachten Sie auch die Art und Weise, wie die Widgets angeordnet sind. Sie haben eine Row
mit einem TextFormField
und einem StyledButton
, der eine Row
enthält. Beachten Sie außerdem, dass das TextFormField
in ein Expanded
Widget eingeschlossen ist, wodurch das TextFormField
gezwungen wird, jeden zusätzlichen Platz in der Zeile auszufüllen. Um besser zu verstehen, warum dies erforderlich ist, lesen Sie Erläuterungen zu Einschränkungen .
Da Sie nun über ein Widget verfügen, mit dem der Benutzer Text zum Hinzufügen zum Gästebuch eingeben kann, müssen Sie ihn auf dem Bildschirm anzeigen.
- Bearbeiten Sie den Hauptteil von
HomePage
, um die folgenden zwei Zeilen am Ende der untergeordnetenListView
Elemente 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)),
Dies reicht zwar aus, um das Widget anzuzeigen, reicht jedoch nicht aus, um etwas Nützliches zu tun. Sie aktualisieren diesen Code in Kürze, damit er funktionsfähig ist.
App-Vorschau
Wenn ein Benutzer auf SENDEN klickt, wird das folgende Code-Snippet ausgelöst. Es fügt den Inhalt des Nachrichteneingabefelds zur guestbook
der Datenbank hinzu. Konkret fügt die Methode addMessageToGuestBook
den Nachrichteninhalt einem neuen Dokument mit einer automatisch generierten ID in der guestbook
hinzu.
Beachten Sie, dass FirebaseAuth.instance.currentUser.uid
ein Verweis auf die automatisch generierte eindeutige ID ist, die die Authentifizierung allen angemeldeten Benutzern vergibt.
- Fügen Sie in der Datei
lib/app_state.dart
die MethodeaddMessageToGuestBook
hinzu. Diese Fähigkeit verknüpfen 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.
}
Verbinden Sie UI und Datenbank
Sie haben eine Benutzeroberfläche, in der der Benutzer den Text eingeben kann, den er dem Gästebuch hinzufügen möchte, und Sie haben den Code, um den Eintrag zu Firestore hinzuzufügen. Jetzt müssen Sie nur noch die beiden 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 erneut Consumer<ApplicationState>
, um den App-Status für den Teil des Baums verfügbar zu machen, den Sie rendern. Dadurch können Sie auf jemanden reagieren, der eine Nachricht in die Benutzeroberfläche eingibt, und diese in der Datenbank veröffentlichen. Im nächsten Abschnitt testen Sie, ob die hinzugefügten Nachrichten in der Datenbank veröffentlicht werden.
Testen Sie das Versenden von Nachrichten
- Melden Sie sich bei Bedarf bei der App an.
- Geben Sie eine Nachricht ein, z. B.
Hey there!
, und klicken Sie dann auf SENDEN .
Diese Aktion schreibt die Nachricht in Ihre Firestore-Datenbank. Allerdings wird die Meldung in Ihrer eigentlichen Flutter-App nicht angezeigt, da Sie den Abruf der Daten noch implementieren müssen, was Sie im nächsten Schritt tun. Im Datenbank- Dashboard der Firebase-Konsole können Sie jedoch Ihre hinzugefügte Nachricht in der guestbook
sehen. Wenn Sie mehr Nachrichten senden, fügen Sie Ihrer guestbook
weitere Dokumente hinzu. Sehen Sie sich beispielsweise den folgenden Codeausschnitt an:
7. Nachrichten lesen
Es ist schön, dass Gäste Nachrichten in die Datenbank schreiben können, diese aber noch nicht in der App sehen können. Zeit, das zu beheben!
Nachrichten synchronisieren
Um Nachrichten anzuzeigen, müssen Sie Listener hinzufügen, die bei Datenänderungen ausgelöst werden, 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 bereitzustellen, 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 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 Initialisierungsabschnitt von
ApplicationState
die folgenden Zeilen hinzu, um eine Abfrage über die Dokumentensammlung zu abonnieren, wenn sich ein Benutzer anmeldet, und um das Abonnement abzumelden, 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 guestbook
erstellen und das Abonnieren und Abbestellen dieser Sammlung verwalten. Sie hören sich den Stream an, rekonstruieren dabei einen lokalen Cache der Nachrichten in der guestbook
und speichern außerdem einen Verweis auf dieses Abonnement, damit Sie es später wieder abbestellen können. Hier ist viel los, daher sollten Sie es in einem Debugger erkunden, um zu untersuchen, was passiert, um ein klareres mentales Modell zu erhalten. Weitere Informationen finden Sie unter Echtzeit-Updates 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 als Teil der Konfiguration eine Liste von Nachrichten 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
diebuild
Methode wie folgt, um diese Konfiguration verfügbar zu machen:
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 der Column
eine Sammlung für hinzu, um für jede Nachricht in der Nachrichtenliste einen neuen Paragraph
zu generieren.
- Aktualisieren Sie den Hauptteil von
HomePage
, umGuestBook
mit dem neuenmessages
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
),
],
],
),
),
Testen Sie die Nachrichtensynchronisierung
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.
- Schreiben Sie neue Nachrichten. Sie erscheinen sofort.
- Öffnen Sie Ihren Arbeitsbereich in mehreren Fenstern oder Registerkarten. Die Nachrichten werden in Echtzeit über alle Fenster und Registerkarten hinweg synchronisiert.
- Optional: Im Menü „Datenbank“ der Firebase-Konsole können Sie Nachrichten manuell löschen, ändern oder neue hinzufügen. Alle Änderungen werden in der Benutzeroberfläche angezeigt.
Glückwunsch! Sie lesen Firestore-Dokumente in Ihrer App!
App-Vorschau
8. Richten Sie grundlegende Sicherheitsregeln ein
Sie richten Firestore zunächst für die Verwendung des Testmodus ein, was bedeutet, dass Ihre Datenbank für Lese- und Schreibvorgänge geöffnet ist. Sie sollten den Testmodus jedoch nur in frühen Entwicklungsstadien verwenden. Als Best Practice sollten Sie beim Entwickeln Ihrer App Sicherheitsregeln für Ihre Datenbank einrichten. 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 abgleichen, von allen Schreibvorgängen in der gesamten Datenbank bis hin zu Vorgängen an einem bestimmten Dokument.
Legen Sie grundlegende Sicherheitsregeln fest:
- Klicken Sie im Menü „Entwickeln“ der Firebase-Konsole auf Datenbank > Regeln . Sie sollten die folgenden Standardsicherheitsregeln und eine Warnung sehen, dass die Regeln öffentlich sind:
- Identifizieren Sie die Sammlungen, in die die App Daten schreibt:
Identifizieren Sie in match /databases/{database}/documents
die Sammlung, 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 überprüfen, ob jeder, der versucht, in das Dokument zu schreiben, über eine passende Authentifizierungs-UID verfügt.
- Fügen Sie die Lese- und Schreibregeln zu Ihrem Regelsatz 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 Benutzer Nachrichten im Gästebuch lesen, aber nur der Autor einer Nachricht kann eine Nachricht 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: Üben Sie, was Sie gelernt haben
Erfassen Sie den RSVP-Status eines Teilnehmers
Im Moment erlaubt Ihre App nur Leuten das Chatten, wenn sie an der Veranstaltung interessiert sind. Außerdem wissen Sie nur, ob jemand kommt, wenn er es im Chat sagt.
In diesem Schritt organisieren Sie sich und teilen den Leuten mit, wie viele Leute kommen. Sie fügen dem App-Status einige Funktionen hinzu. Die erste besteht darin, dass ein angemeldeter Benutzer angeben kann, ob er teilnehmen möchte. Der zweite ist ein Zähler dafür, wie viele Personen anwesend sind.
- Fügen Sie in der Datei
lib/app_state.dart
die folgenden Zeilen zum Accessors-Abschnitt desApplicationState
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
init()
Methode vonApplicationState
wie folgt:
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 Benutzer angemeldet ist, um festzustellen, ob der Benutzer teilnimmt.
- Fügen Sie die folgende Enumeration oben in der Datei
lib/app_state.dart
hinzu.
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 sich wie Optionsfelder verhält:
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'),
),
],
),
);
}
}
}
Es beginnt in einem unbestimmten Zustand, in dem weder „Ja“ noch „Nein“ ausgewählt ist. Sobald der Benutzer auswählt, ob er teilnehmen möchte, wird diese Option mit einer ausgefüllten Schaltfläche hervorgehoben und die andere Option wird mit einer flachen Darstellung ausgeblendet.
- Aktualisieren Sie
build()
Methode vonHomePage
, umYesNoSelection
zu nutzen, einem angemeldeten Benutzer die Möglichkeit zu geben, zu benennen, 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, sodass die Daten, die Sie über die Schaltflächen hinzufügen, abgelehnt werden. Sie müssen die Regeln aktualisieren, um Ergänzungen zur attendees
zu ermöglichen.
- Suchen Sie in der
attendees
nach der Authentifizierungs-UID, die Sie als Dokumentnamen verwendet haben, und stellen Sie sicher, dass dieuid
des Einreichers mit der des Dokuments ü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;
}
}
}
Dadurch kann jeder die Teilnehmerliste lesen, da dort keine privaten Daten vorhanden sind, sondern nur der Ersteller sie aktualisieren kann.
- 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-Konsole anzuzeigen.
App-Vorschau
10. Herzlichen Glückwunsch!
Sie haben Firebase verwendet, um eine interaktive Echtzeit-Web-App zu erstellen!