1. Bevor Sie beginnen
In diesem Codelab lernen Sie einige der Grundlagen von Firebase kennen, um mobile Flutter-Apps für Android und iOS zu erstellen.
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 für Android, iOS, das 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 ( Xcode-Tools erforderlich).
- Der Android-Emulator (erfordert Einrichtung in Android Studio ).
Sie benötigen außerdem Folgendes:
- Ein Browser Ihrer Wahl, z. B. Google Chrome.
- Eine IDE oder einen Texteditor Ihrer Wahl, 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 am Limit leben. - Ein Google-Konto für die Erstellung und Verwaltung Ihres Firebase-Projekts.
- Die
Firebase
CLI hat sich bei Ihrem Google-Konto angemeldet.
2. Holen Sie sich den Beispielcode
Laden Sie die ursprüngliche 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 flutter-codelabs
Verzeichnis 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 auf der zweiten Stufe.
- 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 sollte, schauen Sie in das Verzeichnis, das nach dem Schritt benannt ist, der Sie interessiert.
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 Startcode für das Codelab, das aus einer noch nicht funktionierenden 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, um den Stil der App zu standardisieren. Sie bilden den Bildschirm der Starter-App. -
lib/src/authentication.dart
: Diese Datei enthält eine Teilimplementierung der Authentifizierung mit einer Reihe von Widgets, um eine Anmeldebenutzererfahrung für die E-Mail-basierte Authentifizierung von Firebase zu erstellen. Diese Widgets für den Authentifizierungsablauf 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 Look-and-Feel.
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 großartig für Ihre Gäste, aber für niemanden allein 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-Nutzungsbedingungen, aber überspringen Sie 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 verwendet 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 müssen speziell konfiguriert oder in der Firebase-Konsole aktiviert werden.
Aktivieren Sie die E-Mail-Anmeldeauthentifizierung
- Erweitern Sie im Bereich Projektübersicht der Firebase-Konsole das Menü Erstellen .
- Klicken Sie auf Authentifizierung > Erste Schritte > Anmeldemethode > E-Mail/Kennwort > Aktivieren > Speichern .
Firestore aktivieren
Die Web-App verwendet Firestore, um Chat-Nachrichten zu speichern und neue Chat-Nachrichten zu empfangen.
Firestore aktivieren:
- Klicken Sie im Menü Erstellen auf Cloud Firestore > 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 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 dann dem Code hinzu.
Im obersten 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 über die 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 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 firebase_ui_auth
Paket bietet eine Reihe von Widgets und Dienstprogrammen, um die Entwicklergeschwindigkeit mit Authentifizierungsabläufen zu erhöhen.
$ 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 konfigurieren, um Firebase ordnungsgemäß zu verwenden. Sie verwenden auch das provider
, das die Trennung der Geschäftslogik von der 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-Befehlszeilenschnittstelle auf Ihrem Computer.
- Installieren Sie die FlutterFire-CLI:
$ dart pub global activate flutterfire_cli
Nach der Installation ist der Befehl flutterfire
global verfügbar.
Konfigurieren Sie Ihre Anwendungen
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 in Ihrem Projekt eine
firebase_options.dart
Datei.
macOS konfigurieren
Flutter auf macOS erstellt vollständig Sandbox-Apps. Da sich diese App in das Netzwerk integriert, 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. RSVP-Funktionalität hinzufügen
Nachdem Sie der App Firebase hinzugefügt haben, können Sie eine RSVP- Schaltfläche erstellen, die Personen mit Authentifizierung registriert. Für Android nativ, iOS nativ und Web gibt es vorgefertigte FirebaseUI Auth
, aber Sie müssen diese Funktion für Flutter erstellen.
Das Projekt, das Sie zuvor abgerufen haben, enthielt eine Reihe von Widgets, die die Benutzeroberfläche für den größten Teil des Authentifizierungsflusses 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
Anweisungen führen Firebase Core und Auth ein, rufen das provider
ab, das das App-Statusobjekt in der gesamten Widget-Struktur verfügbar macht, und schließen die Authentifizierungs-Widgets aus dem Paket firebase_ui_auth
ein.
Dieses ApplicationState
Anwendungsstatusobjekt hat eine Hauptverantwortung für diesen Schritt, nämlich die Widget-Struktur zu warnen, dass eine Aktualisierung auf einen authentifizierten Status stattgefunden hat.
Sie verwenden einen Anbieter nur, um den Status des Anmeldestatus eines Benutzers an die App zu übermitteln. Um einen Benutzer sich anmelden zu lassen, verwenden Sie die Benutzeroberflächen, die vom Paket firebase_ui_auth
bereitgestellt werden, was eine großartige Möglichkeit ist, Anmeldebildschirme in Ihren Apps schnell zu booten.
Integrieren Sie den Authentifizierungsablauf
- Ä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.
}
Die Änderung an der main()
-Funktion macht das Anbieterpaket für die Instanziierung des App-Statusobjekts mit dem ChangeNotifierProvider
-Widget verantwortlich. Sie verwenden diese spezifische provider
, da das App-Statusobjekt die ChangeNotifier
Klasse erweitert, die das provider
darüber informiert, wann abhängige Widgets erneut angezeigt werden sollen.
- Aktualisieren Sie Ihre App, um die Navigation zu verschiedenen Bildschirmen zu handhaben, 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) {
if (state is SignedIn || state is UserCreated) {
var user = (state is SignedIn)
? state.user
: (state as UserCreated).credential.user;
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.queryParams;
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 Authentifizierungsflusses ein anderer Aktionstyp zugeordnet. Nach den meisten Statusänderungen bei der Authentifizierung können Sie zu einem bevorzugten Bildschirm zurückkehren, unabhängig davon, ob es sich um den Startbildschirm oder einen anderen Bildschirm, z. B. das Profil, handelt.
- 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 packen es in ein Consumer
Widget. Das Consumer-Widget ist die übliche Methode, mit der das provider
verwendet werden kann, um einen Teil der Struktur neu zu erstellen, wenn sich der App-Status ändert. Das AuthFunc
Widget sind die ergänzenden Widgets, die 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 fordert Sie das System auf, das Registrierungsformular auszufüllen.
- Geben Sie ein Kennwort mit weniger als sechs Zeichen ein, um den Ablauf der Fehlerbehandlung zu überprüfen. Wenn Sie registriert sind, sehen Sie stattdessen das Passwort für.
- Geben Sie falsche Passwörter ein, um den Ablauf der Fehlerbehandlung zu überprüfen.
- Geben Sie das richtige Passwort ein. Sie sehen das eingeloggte Erlebnis, das dem Benutzer die Möglichkeit bietet, sich abzumelden.
6. Schreiben Sie Nachrichten an Firestore
Es ist großartig 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 gerne kommen oder wen sie zu treffen hoffen.
Um die Chat-Nachrichten zu speichern, die Benutzer in der App schreiben, verwenden Sie Firestore .
Datenmodell
Firestore ist eine NoSQL-Datenbank, und in der Datenbank gespeicherte Daten werden in Sammlungen, Dokumente, Felder und Untersammlungen aufgeteilt. Sie speichern jede Nachricht des Chats als Dokument in einer gustbook
, die eine Sammlung der obersten Ebene ist.
Nachrichten zu Firestore hinzufügen
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 Sendeschaltfläche hinzu, und dann fügen Sie den Code hinzu, der diese Elemente mit der Datenbank verbindet.
- Erstellen Sie eine neue Datei namens
guest_book.dart
, fügen Sie ein zustandsbehaftetesGuestBook
Widget hinzu, um die UI-Elemente eines Nachrichtenfelds und eine Sendeschaltfläche 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 ein paar interessante Punkte. Zunächst instanziieren Sie ein Formular, damit Sie überprüfen können, ob die Nachricht tatsächlich Inhalt enthält, und dem Benutzer eine Fehlermeldung anzeigen, wenn keine 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 ihrer 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 auch, dass das TextFormField
in ein Expanded
Widget eingeschlossen ist, wodurch das TextFormField
gezwungen wird, zusätzlichen Platz in der Zeile zu füllen. Um besser zu verstehen, warum dies erforderlich ist, siehe Einschränkungen verstehen .
Nachdem Sie nun ein Widget haben, mit dem der Benutzer Text eingeben kann, um ihn dem Gästebuch hinzuzufügen, müssen Sie ihn auf den Bildschirm bringen.
- Bearbeiten Sie den Hauptteil 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)),
Dies reicht zwar aus, um das Widget anzuzeigen, aber nicht, um etwas Nützliches zu tun. Sie aktualisieren diesen Code in Kürze, damit er funktioniert.
App-Vorschau
Wenn ein Benutzer auf SENDEN klickt, wird das folgende Code-Snippet ausgelöst. Es fügt den Inhalt des Nachrichteneingabefelds der guestbook
der Datenbank hinzu. Insbesondere fügt die addMessageToGuestBook
Methode 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 für alle angemeldeten Benutzer bereitstellt.
- Fügen Sie in der Datei
lib/app_state.dart
die MethodeaddMessageToGuestBook
hinzu. Diese Fähigkeit verbinden Sie im nächsten Schritt mit der Benutzeroberfläche.
lib/app_state.dart
import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'
hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here...
Future<DocumentReference> addMessageToGuestBook(String message) {
if (!_loggedIn) {
throw Exception('Must be logged in');
}
return FirebaseFirestore.instance
.collection('guestbook')
.add(<String, dynamic>{
'text': message,
'timestamp': DateTime.now().millisecondsSinceEpoch,
'name': FirebaseAuth.instance.currentUser!.displayName,
'userId': FirebaseAuth.instance.currentUser!.uid,
});
}
// ...to here.
}
UI und Datenbank verbinden
Sie haben eine Benutzeroberfläche, in der der Benutzer den Text eingeben kann, den er zum 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 Consumer<ApplicationState>
erneut, um den App-Zustand für den Teil der Struktur verfügbar zu machen, den Sie rendern. Auf diese Weise 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 Senden 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. Sie sehen die Nachricht jedoch nicht in Ihrer eigentlichen Flutter-App, 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 das folgende Code-Snippet an:
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. Zeit, das zu beheben!
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 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 von
ApplicationState
, in dem Sie Status und Getter definieren, die folgenden Zeilen hinzu:
lib/app_state.dart
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
// Add from here...
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// ...to here.
- Fügen Sie im Initialisierungsabschnitt von
ApplicationState
die folgenden Zeilen hinzu, um eine Abfrage über die Dokumentensammlung zu abonnieren, wenn sich ein Benutzer anmeldet, und 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 dort eine Abfrage über die guestbook
erstellen und das Abonnieren und Abbestellen dieser Sammlung handhaben. Sie hören sich den Stream an, wo Sie einen lokalen Cache der Nachrichten in der guestbook
rekonstruieren und auch einen Verweis auf dieses Abonnement speichern, damit Sie es später wieder abbestellen können. Hier passiert viel, also sollten Sie es in einem Debugger untersuchen, um zu untersuchen, was passiert, um ein klareres mentales Modell zu erhalten. Weitere Informationen finden Sie unter Abrufen von Echtzeit-Updates mit Firestore .
- 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 Zustand 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 Methode build()
mit einem Column
-Widget und fügen dann eine Sammlung für am Ende der untergeordneten Elemente von Column
hinzu, um einen neuen Paragraph
für jede Nachricht in der Nachrichtenliste 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 automatisch und sofort Daten mit Clients, die die Datenbank abonniert haben.
Nachrichtensynchronisation testen:
- Suchen Sie in der App die 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 die Fenster und Registerkarten hinweg synchronisiert.
- Optional: Löschen, ändern oder fügen Sie neue Nachrichten im Datenbankmenü der Firebase-Konsole manuell hinzu. 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 haben Firestore zunächst für die Verwendung des Testmodus eingerichtet, was bedeutet, dass Ihre Datenbank für Lese- und Schreibvorgänge geöffnet ist. Sie sollten den Testmodus jedoch nur in frühen Phasen der Entwicklung verwenden. Als Best Practice sollten Sie bei der Entwicklung 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 die gesamte Datenbank bis hin zu Vorgängen in einem bestimmten Dokument.
Richten Sie grundlegende Sicherheitsregeln ein:
- 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 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 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 zu chatten, die an der Veranstaltung interessiert sind. Außerdem weißt du nur, ob jemand kommt, wenn er es im Chat sagt.
In diesem Schritt organisieren Sie sich und lassen die Leute wissen, wie viele Leute kommen. Sie fügen dem App-Status einige Funktionen hinzu. Die erste ist die Möglichkeit für einen angemeldeten Benutzer, zu nominieren, ob er teilnehmen möchte. Der zweite ist ein Zähler dafür, wie viele Personen teilnehmen.
- Fügen Sie in der Datei
lib/app_state.dart
die folgenden Zeilen zum Accessors-Abschnitt vonApplicationState
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
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) {
_loginState = ApplicationLoginState.loggedIn;
_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 {
_loginState = ApplicationLoginState.loggedOut;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
_attendingSubscription?.cancel(); // new
}
notifyListeners();
});
}
Dieser Code fügt eine immer abonnierte Abfrage hinzu, um die Anzahl der Teilnehmer zu bestimmen, und eine zweite Abfrage, die nur aktiv ist, während ein Benutzer angemeldet ist, um festzustellen, ob der Benutzer teilnimmt.
- Fügen Sie oben in der Datei
lib/app_state.dart
die folgende Aufzählung hinzu.
lib/app_state.dart
enum Attending { yes, no, unknown }
- Erstellen Sie eine neue Datei
yes_no_selection.dart
, 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: [
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
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),
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
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 mit einer flachen Darstellung ausgeblendet.
- Aktualisieren Sie die Methode
build()
vonHomePage
, umYesNoSelection
zu nutzen, ermöglichen Sie es einem angemeldeten Benutzer, anzugeben, ob er teilnehmen möchte, und zeigen Sie die Anzahl der Teilnehmer für die Veranstaltung an:
lib/home_page.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Add from here...
if (appState.attendees >= 2)
Paragraph('${appState.attendees} people going')
else if (appState.attendees == 1)
const Paragraph('1 person going')
else
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 mit den Schaltflächen hinzufügen, abgelehnt werden. Sie müssen die Regeln aktualisieren, um Ergänzungen zur Sammlung der attendees
zuzulassen.
- Greifen Sie in der
attendees
auf die Authentifizierungs-UID zu, die Sie als Dokumentnamen verwendet haben, und vergewissern Sie sich, dass dieuid
des Einreichers 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;
}
}
}
Dadurch kann jeder die Teilnehmerliste lesen, da dort keine privaten Daten vorhanden sind, aber 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!