1. 開始之前
在此 Codelab 中,您將學習一些Firebase的基礎知識,以創建適用於 Android 和 iOS 的 Flutter 移動應用程序。
先決條件
本 Codelab 假設您熟悉 Flutter,並且您已經安裝了Flutter SDK和一個編輯器。
你將創造什麼
在此 Codelab 中,您將使用 Flutter 在 Android、iOS、Web 和 macOS 上構建一個事件 RSVP 和留言簿聊天應用程序。您將使用 Firebase 身份驗證對用戶進行身份驗證,並使用 Cloud Firestore 同步數據。
你需要什麼
您可以使用以下任何設備運行此代碼實驗室:
- 連接到您的計算機並設置為開發人員模式的物理設備(Android 或 iOS)。
- iOS 模擬器。 (需要安裝 Xcode 工具。)
- 安卓模擬器。 (需要在Android Studio中進行設置。)
除了上述內容,您還需要:
- 您選擇的瀏覽器,例如 Chrome。
- 您選擇的 IDE 或文本編輯器,例如配置了 Dart 和 Flutter 插件的Android Studio或VS Code 。
- Flutter的最新
stable
版本(如果您喜歡生活在邊緣,則為beta
)。 - 一個 Google 帳戶,例如 gmail 帳戶,用於創建和管理您的 Firebase 項目。
-
firebase
命令行工具,登錄到您的 gmail 帳戶。 - Codelab 的示例代碼。有關如何獲取代碼,請參閱下一步。
2.獲取示例代碼
讓我們首先從 GitHub 下載我們項目的初始版本。
從命令行克隆GitHub 存儲庫:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
或者,如果您安裝了GitHub 的 cli工具:
gh repo clone flutter/codelabs flutter-codelabs
示例代碼應克隆到flutter-codelabs
目錄中,該目錄包含 codelabs 集合的代碼。此代碼實驗室的代碼位於flutter-codelabs/firebase-get-to-know-flutter
中。
flutter-codelabs/firebase-get-to-know-flutter
下的目錄結構是一系列快照,說明您應該在每個命名步驟結束時所處的位置。這是第 2 步,因此查找匹配文件非常簡單:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
如果您想向前跳過,或者查看某個步驟之後的樣子,請查看以您感興趣的步驟命名的目錄。
導入入門應用
打開或導入flutter-codelabs/firebase-get-to-know-flutter/step_02
目錄到您首選的 IDE 中。此目錄包含 codelab 的起始代碼,其中包含一個尚未功能的 Flutter 聚會應用程序。
找到要處理的文件
此應用程序中的代碼分佈在多個目錄中。這種功能拆分旨在通過按功能對代碼進行分組來使其更易於使用。
在項目中找到以下文件:
-
lib/main.dart
:此文件包含主入口點和應用程序小部件。 -
lib/src/widgets.dart
:該文件包含一些小部件,以幫助標準化應用程序的樣式。這些用於組成入門應用程序的屏幕。 -
lib/src/authentication.dart
:此文件包含FirebaseUI身份驗證的部分實現,其中包含一組小部件,用於為基於 Firebase 電子郵件的身份驗證創建登錄用戶體驗。用於身份驗證流程的這些小部件尚未在入門應用程序中使用,但您很快就會將它們連接起來。
您將根據需要添加其他文件以構建應用程序的其餘部分。
查看lib/main.dart
文件
這個應用程序利用了google_fonts
包,使我們能夠將 Roboto 設置為整個應用程序的默認字體。對於有動力的讀者來說,一個練習是探索fonts.google.com並使用您在應用程序的不同部分中發現的字體。
您正在以Header
、 Paragraph
和IconAndDetail
的形式使用來自lib/src/widgets.dart
的輔助小部件。這些小部件通過消除重複代碼來減少HomePage
中描述的頁面佈局中的混亂。這具有實現一致的外觀和感覺的額外好處。
以下是您的應用在 Android、iOS、Web 和 macOS 上的外觀:
應用預覽
3. 創建並設置 Firebase 項目
顯示活動信息對您的客人來說非常有用,但僅顯示活動對任何人都不是很有用。讓我們為這個應用程序添加一些動態功能。為此,您需要將 Firebase 連接到您的應用。要開始使用 Firebase,您需要創建並設置一個 Firebase 項目。
創建一個 Firebase 項目
- 登錄Firebase 。
- 在 Firebase 控制台中,單擊Add Project (或Create a project ),然後將您的 Firebase 項目命名為Firebase-Flutter-Codelab 。
- 單擊項目創建選項。如果出現提示,請接受 Firebase 條款。跳過設置 Google Analytics,因為您不會為此應用使用 Analytics。
要詳細了解 Firebase 項目,請參閱了解 Firebase 項目。
您正在構建的應用使用了多個可用於網絡應用的 Firebase 產品:
- Firebase 身份驗證,允許您的用戶登錄您的應用。
- Cloud Firestore將結構化數據保存在雲端,並在數據更改時獲得即時通知。
- Firebase 安全規則來保護您的數據庫。
其中一些產品需要特殊配置或需要使用 Firebase 控制台啟用。
為 Firebase 身份驗證啟用電子郵件登錄
要允許用戶登錄 Web 應用,您將為此 Codelab 使用電子郵件/密碼登錄方法:
- 在 Firebase 控制台中,展開左側面板中的Build菜單。
- 單擊Authentication ,然後單擊Get Started按鈕,然後單擊Sign-in method選項卡(或單擊此處直接轉到Sign-in method選項卡)。
- 單擊登錄提供商列表中的電子郵件/密碼,將啟用開關設置為打開位置,然後單擊保存。
啟用 Cloud Firestore
該網絡應用使用Cloud Firestore來保存聊天消息並接收新的聊天消息。
啟用 Cloud Firestore:
- 在 Firebase 控制台的Build部分,點擊Cloud Firestore 。
- 單擊創建數據庫。
- 選擇在測試模式下啟動選項。閱讀有關安全規則的免責聲明。測試模式確保您可以在開發過程中自由寫入數據庫。單擊下一步。
- 選擇數據庫的位置(您可以使用默認位置)。請注意,此位置以後無法更改。
- 單擊啟用。
4. Firebase 配置
為了將 Firebase 與 Flutter 一起使用,您需要按照流程配置 Flutter 項目以正確使用 FlutterFire 庫:
- 將 FlutterFire 依賴項添加到您的項目中
- 在 Firebase 項目上註冊所需的平台
- 下載特定於平台的配置文件,並將其添加到代碼中。
在 Flutter 應用的頂級目錄中,有名為android
、 ios
、 macos
和web
的子目錄。這些目錄分別保存 iOS 和 Android 的特定於平台的配置文件。
配置依賴項
您需要為您在此應用中使用的兩個 Firebase 產品添加 FlutterFire 庫 - Firebase Auth 和 Cloud Firestore。運行以下三個命令以添加依賴項。
$ flutter pub add firebase_core Resolving dependencies... + firebase_core 1.10.5 + firebase_core_platform_interface 4.2.2 + firebase_core_web 1.5.2 + flutter_web_plugins 0.0.0 from sdk flutter + js 0.6.3 test_api 0.4.3 (0.4.8 available) Changed 5 dependencies!
firebase_core
是所有 Firebase Flutter 插件所需的通用代碼。
$ flutter pub add firebase_auth Resolving dependencies... + firebase_auth 3.3.3 + firebase_auth_platform_interface 6.1.8 + firebase_auth_web 3.3.4 + intl 0.17.0 test_api 0.4.3 (0.4.8 available) Changed 4 dependencies!
firebase_auth
支持與 Firebase 的身份驗證功能集成。
$ flutter pub add cloud_firestore Resolving dependencies... + cloud_firestore 3.1.4 + cloud_firestore_platform_interface 5.4.9 + cloud_firestore_web 2.6.4 test_api 0.4.3 (0.4.8 available) Changed 3 dependencies!
cloud_firestore
允許訪問 Cloud Firestore 數據存儲。
$ flutter pub add provider Resolving dependencies... + nested 1.0.0 + provider 6.0.1 test_api 0.4.3 (0.4.8 available) Changed 2 dependencies!
在您添加了所需的包後,您還需要配置 iOS、Android、macOS 和 Web 運行程序項目以適當地利用 Firebase。您還使用了provider
包,它將啟用業務邏輯與顯示邏輯的分離。
安裝flutterfire
FlutterFire CLI 依賴於底層的 Firebase CLI。如果您尚未這樣做,請確保您的計算機上已安裝Firebase CLI 。
接下來,通過運行以下命令安裝 FlutterFire CLI:
$ dart pub global activate flutterfire_cli
安裝後, flutterfire
命令將全局可用。
配置您的應用程序
CLI 從您的 Firebase 項目和選定的項目應用程序中提取信息,以生成特定平台的所有配置。
在應用程序的根目錄中,運行配置命令:
$ flutterfire configure
配置命令將引導您完成多個過程:
- 選擇一個 Firebase 項目(基於 .firebaserc 文件或從 Firebase 控制台)。
- 提示您想要配置的平台(例如 Android、iOS、macOS 和 Web)。
- 確定應使用所選平台的哪些 Firebase 應用程序來提取配置。默認情況下,CLI 將嘗試根據您當前的項目配置自動匹配 Firebase 應用。
- 在您的項目中生成一個 firebase_options.dart 文件。
配置 macOS
macOS 上的 Flutter 構建完全沙盒化的應用程序。由於此應用程序正在集成使用網絡與 Firebase 服務器進行通信,因此您需要為您的應用程序配置網絡客戶端權限。
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>
有關更多詳細信息,請參閱權利和應用程序沙盒。
5.添加用戶登錄(RSVP)
現在您已將 Firebase 添加到應用程序中,您可以設置一個 RSVP 按鈕,以使用Firebase 身份驗證註冊人員。對於 Android 原生、iOS 原生和 Web,有預構建的 FirebaseUI Auth 包,但對於 Flutter,您需要構建此功能。
您在第 2 步中檢索到的項目包括一組小部件,它們為大多數身份驗證流程實現了用戶界面。您將實現業務邏輯以將 Firebase 身份驗證集成到應用程序中。
提供者的業務邏輯
您將使用provider
包在整個應用程序的 Flutter 小部件樹中提供一個集中的應用程序狀態對象。首先,修改lib/main.dart
頂部的導入:
lib/main.dart
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart'; // new
import 'firebase_options.dart'; // new
import 'src/authentication.dart'; // new
import 'src/widgets.dart';
import
行介紹 Firebase Core 和 Auth,拉入您用於通過小部件樹使應用程序狀態對象可用的provider
包,並包括來自lib/src
的身份驗證小部件。
此應用程序狀態對象ApplicationState
對此步驟有兩個主要職責,但隨著您在後續步驟中向應用程序添加更多功能,將獲得額外的職責。第一個職責是通過調用Firebase.initializeApp()
來初始化 Firebase 庫,然後是授權流程的處理。將以下類添加到lib/main.dart
的末尾:
lib/main.dart
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
} else {
_loginState = ApplicationLoginState.loggedOut;
}
notifyListeners();
});
}
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
void startLoginFlow() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> verifyEmail(
String email,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
var methods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
if (methods.contains('password')) {
_loginState = ApplicationLoginState.password;
} else {
_loginState = ApplicationLoginState.register;
}
_email = email;
notifyListeners();
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
Future<void> signInWithEmailAndPassword(
String email,
String password,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void cancelRegistration() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> registerAccount(
String email,
String displayName,
String password,
void Function(FirebaseAuthException e) errorCallback) async {
try {
var credential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
await credential.user!.updateDisplayName(displayName);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void signOut() {
FirebaseAuth.instance.signOut();
}
}
值得注意的是這節課的幾個關鍵點。用戶在未經身份驗證的情況下開始,應用程序顯示一個請求用戶電子郵件地址的表單,取決於該電子郵件地址是否存檔,應用程序將要求用戶註冊或要求他們的密碼,然後假設一切正常,用戶已通過身份驗證。
必須注意,這不是 FirebaseUI 身份驗證流程的完整實現,因為它不處理使用現有帳戶的用戶無法登錄的情況。實現此附加功能將作為練習留給有動力的讀者。
集成身份驗證流程
現在您已經開始應用程序狀態,是時候將應用程序狀態連接到應用程序初始化並將身份驗證流添加到HomePage
中了。更新主入口點以通過provider
包集成應用程序狀態:
lib/main.dart
void main() {
// Modify from here
runApp(
ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: (context, _) => App(),
),
);
// to here.
}
對main
函數的修改使提供程序包負責使用ChangeNotifierProvider
小部件實例化應用程序狀態對象。您正在使用這個特定的提供程序類,因為應用程序狀態對象擴展了ChangeNotifier
,這使provider
包能夠知道何時重新顯示相關的小部件。最後,通過更新HomePage
的build
方法將應用程序狀態與Authentication
集成:
lib/main.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'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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!',
),
],
),
);
}
}
您實例化Authentication
小部件,並將其包裝在Consumer
小部件中。 Consumer 小部件是provider
包可用於在應用程序狀態更改時重建樹的一部分的常用方式。 Authentication
小部件是您現在將測試的身份驗證 UI。
測試身份驗證流程
這是身份驗證流程的開始,用戶可以點擊 RSVP 按鈕來啟動電子郵件表單。
輸入電子郵件後,系統會確認用戶是否已經註冊,在這種情況下,系統會提示用戶輸入密碼,或者如果用戶未註冊,則他們會通過註冊表單。
請務必嘗試輸入短密碼(少於六個字符)以檢查錯誤處理流程。如果用戶已註冊,他們將看到密碼。
在此頁面上確保輸入錯誤的密碼以檢查此頁面上的錯誤處理。最後,一旦用戶登錄,您將看到登錄體驗,使用戶能夠再次註銷。
這樣,您就實現了身份驗證流程。恭喜!
6. 向 Cloud Firestore 寫入消息
知道用戶來了很好,但讓我們在應用程序中為客人提供其他事情要做。如果他們可以在留言簿中留言怎麼辦?他們可以分享為什麼他們很高興來到這里或他們希望見到誰。
要存儲用戶在應用中編寫的聊天消息,您將使用Cloud Firestore 。
數據模型
Cloud Firestore 是一個 NoSQL 數據庫,存儲在數據庫中的數據分為集合、文檔、字段和子集合。您會將聊天的每條消息作為文檔存儲在名為guestbook
的頂級集合中。
向 Firestore 添加消息
在本節中,您將為用戶添加將新消息寫入數據庫的功能。首先,添加 UI 元素(表單字段和發送按鈕),然後添加將這些元素連接到數據庫的代碼。
首先,為cloud_firestore
包和dart:async
添加導入。
lib/main.dart
import 'dart:async'; // new
import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'firebase_options.dart';
import 'src/authentication.dart';
import 'src/widgets.dart';
要構造消息字段和發送按鈕的 UI 元素,請在lib/main.dart
GuestBook
lib/main.dart
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage});
final FutureOr<void> Function(String message) addMessage;
@override
_GuestBookState 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'),
],
),
),
],
),
),
);
}
}
這裡有幾個有趣的地方。首先,您正在實例化一個表單,以便您可以驗證消息實際上是否有一些內容,如果沒有,則向用戶顯示錯誤消息。驗證表單的方法涉及訪問表單後面的表單狀態,為此您使用GlobalKey
。有關 Keys 的更多信息,以及如何使用它們,請參閱Flutter Widgets 101 章節“何時使用 Keys” 。
還要注意小部件的佈局方式,你有一個Row
,一個TextFormField
和一個StyledButton
,它本身包含一個Row
。另請注意, TextFormField
包含在Expanded
小部件中,這會強制TextFormField
佔用行中的任何額外空間。為了更好地理解為什麼需要這樣做,請通讀理解約束。
現在您有了一個允許用戶輸入一些文本以添加到留言簿的小部件,您需要將其顯示在屏幕上。為此,請編輯HomePage
的正文,在ListView
的子項底部添加以下兩行:
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)),
雖然這足以顯示 Widget,但還不足以做任何有用的事情。您將很快更新此代碼以使其正常工作。
應用預覽
用戶單擊“發送”按鈕將觸發下面的代碼片段。它將消息輸入字段的內容添加到數據庫的guestbook
集合中。具體來說, addMessageToGuestBook
方法將消息內容添加到guestbook
集合中的新文檔(具有自動生成的 ID)中。
請注意, FirebaseAuth.instance.currentUser.uid
是對 Firebase 身份驗證為所有登錄用戶提供的自動生成的唯一 ID 的引用。
對lib/main.dart
文件進行另一次更改。添加addMessageToGuestBook
方法。您將在下一步中將用戶界面和此功能連接在一起。
lib/main.dart
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here
Future<DocumentReference> addMessageToGuestBook(String message) {
if (_loginState != ApplicationLoginState.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 連接到數據庫
您有一個 UI,用戶可以在其中輸入他們想要添加到留言簿的文本,並且您有將條目添加到 Cloud Firestore 的代碼。現在您需要做的就是將兩者連接在一起。在lib/main.dart
中對HomePage
小部件進行以下更改。
lib/main.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, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// To here.
],
),
);
}
}
您已將在此步驟開始時添加的兩行替換為完整的實現。您再次使用Consumer<ApplicationState>
使應用程序狀態可用於您正在呈現的樹的一部分。這使您能夠對在 UI 中輸入消息的人做出反應,並將其發佈到數據庫中。在下一節中,您將測試添加的消息是否已發佈到數據庫中。
測試發送消息
- 確保您已登錄該應用程序。
- 輸入諸如“嘿!”之類的消息,然後單擊“發送”。
此操作會將消息寫入您的 Cloud Firestore 數據庫。但是,您還不會在實際的 Flutter 應用程序中看到該消息,因為您仍然需要實現檢索數據。您將在下一步中執行此操作。
但是您可以在 Firebase 控制台中看到新添加的消息。
在 Firebase 控制台的Database dashboard中,您應該會看到帶有新添加消息的guestbook
集合。如果您繼續發送消息,您的留言簿集合將包含許多文檔,如下所示:
Firebase 控制台
7.閱讀信息
客人可以將消息寫入數據庫,但他們還不能在應用程序中看到它們,這真是太棒了。讓我們解決這個問題!
同步消息
要顯示消息,您需要添加在數據更改時觸發的偵聽器,然後創建一個顯示新消息的 UI 元素。您將向應用程序狀態添加代碼,以偵聽來自應用程序的新添加消息。
就在GuestBook
小部件上方是以下值類。此類公開您存儲在 Cloud Firestore 中的數據的結構化視圖。
lib/main.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
在ApplicationState
中定義狀態和 getter 的部分,添加以下新行:
lib/main.dart
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
// Add from here
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// to here.
最後,在ApplicationState
的初始化部分,添加以下內容以在用戶登錄時訂閱對文檔集合的查詢,並在用戶註銷時取消訂閱。
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
// Add from here
_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();
});
// to here.
} else {
_loginState = ApplicationLoginState.loggedOut;
// Add from here
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
notifyListeners();
});
}
這部分很重要,因為您可以在此處構建對guestbook
集合的查詢,並處理訂閱和取消訂閱該集合。您收聽流,在其中重建guestbook
集合中消息的本地緩存,並存儲對此訂閱的引用,以便您以後可以取消訂閱。這裡發生了很多事情,值得花一些時間在調試器中檢查什麼時候會發生什麼以獲得更清晰的心智模型。
有關更多信息,請參閱Cloud Firestore 文檔。
在GuestBook
小部件中,您需要將此變化的狀態連接到用戶界面。您可以通過添加消息列表作為其配置的一部分來修改小部件。
lib/main.dart
class GuestBook extends StatefulWidget {
// Modify the following line
const GuestBook({required this.addMessage, required this.messages});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
_GuestBookState createState() => _GuestBookState();
}
接下來,我們通過如下修改build
方法在_GuestBookState
中公開這個新配置。
lib/main.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.
);
}
}
您使用Column
小部件包裝 build 方法的先前內容,然後在Column
的孩子的尾部添加一個集合,以便為消息列表中的每條消息生成一個新的Paragraph
。
最後,您現在需要更新HomePage
的正文以使用新的messages
參數正確構造GuestBook
。
lib/main.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages, // new
),
],
],
),
),
測試同步消息
Cloud Firestore 會自動與訂閱數據庫的客戶端即時同步數據。
- 您之前在數據庫中創建的消息應顯示在應用程序中。隨意寫新消息;它們應該立即出現。
- 如果您在多個窗口或選項卡中打開工作區,消息將在選項卡之間實時同步。
- (可選)您可以嘗試直接在 Firebase 控制台的數據庫部分手動刪除、修改或添加新消息;任何更改都應顯示在 UI 中。
恭喜!您正在應用中閱讀 Cloud Firestore 文檔!
應用程序審查
8.設置基本安全規則
您最初將 Cloud Firestore 設置為使用測試模式,這意味著您的數據庫對讀寫是開放的。但是,您應該只在開發的早期階段使用測試模式。作為最佳實踐,您應該在開發應用程序時為數據庫設置安全規則。安全性應該是應用程序結構和行為不可或缺的一部分。
安全規則允許您控制對數據庫中文檔和集合的訪問。靈活的規則語法允許您創建匹配任何內容的規則,從對整個數據庫的所有寫入到對特定文檔的操作。
您可以在 Firebase 控制台中為 Cloud Firestore 編寫安全規則:
- 在 Firebase 控制台的開發部分,單擊數據庫,然後選擇規則選項卡(或單擊此處直接轉到規則選項卡)。
- 您應該會看到以下默認安全規則,以及有關公開規則的警告。
識別集合
首先,確定應用程序向其寫入數據的集合。
在match /databases/{database}/documents
中,確定要保護的集合:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
// You'll add rules here in the next step.
}
}
添加安全規則
因為您在每個留言簿文檔中使用了身份驗證 UID 作為字段,所以您可以獲得身份驗證 UID 並驗證嘗試寫入文檔的任何人是否具有匹配的身份驗證 UID。
將讀寫規則添加到您的規則集中,如下所示:
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;
}
}
}
現在,對於留言簿,只有登錄用戶才能閱讀消息(任何消息!),但只有消息的作者可以編輯消息。
添加驗證規則
添加數據驗證以確保文檔中存在所有預期的字段:
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. 獎勵步驟:練習你所學的
記錄與會者的 RSVP 狀態
現在,您的應用程序只允許人們在對活動感興趣時開始聊天。此外,您知道某人是否來的唯一方法是他們是否在聊天中發布。讓我們組織起來,讓人們知道有多少人來。
您將向應用程序狀態添加一些新功能。第一個是登錄用戶能夠提名他們是否參加。第二個能力是實際參加人數的計數器。
在lib/main.dart
中,將以下內容添加到 accessors 部分以使 UI 代碼能夠與此狀態進行交互:
lib/main.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});
}
}
更新ApplicationState
的init
方法如下:
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// 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();
});
}
上面添加了一個始終訂閱的查詢以查明參加者的數量,並添加了第二個查詢,該查詢僅在用戶登錄時才處於活動狀態,以查明用戶是否正在參加。接下來,在GuestBookMessage
聲明之後添加以下枚舉:
lib/main.dart
enum Attending { yes, no, unknown }
您現在將定義一個新的小部件,其作用類似於舊的單選按鈕。它以不確定的狀態開始,既沒有選擇“是”也沒有選擇“否”,但是一旦用戶選擇了他們是否參加,然後您會顯示該選項以填充按鈕突出顯示,而另一個選項以平面渲染後退。
lib/main.dart
class YesNoSelection extends StatelessWidget {
const YesNoSelection({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'),
),
],
),
);
}
}
}
接下來,您需要更新HomePage
的構建方法以利用YesNoSelection
,使登錄的用戶能夠提名他們是否參加。您還將顯示此活動的參加者人數。
lib/main.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.loginState == ApplicationLoginState.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,
),
],
],
),
),
添加規則
因為您已經設置了一些規則,所以您使用按鈕添加的新數據將被拒絕。您需要更新規則以允許添加到attendees
集合。
對於attendees
集合,由於您使用身份驗證 UID 作為文檔名稱,因此您可以獲取它並驗證提交者的uid
是否與他們正在編寫的文檔相同。您將允許所有人閱讀與會者列表(因為那裡沒有私人數據),但只有創建者才能更新它。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
添加驗證規則
添加數據驗證以確保文檔中存在所有預期的字段:
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;
}
}
}
(可選)您現在可以查看單擊按鈕的結果。轉到 Firebase 控制台中的 Cloud Firestore 信息中心。
應用預覽
10. 恭喜!
您已經使用 Firebase 構建了一個交互式實時網絡應用程序!
我們涵蓋的內容
- Firebase 身份驗證
- 雲防火牆
- Firebase 安全規則
下一步
- 想詳細了解其他 Firebase 產品?也許您想存儲用戶上傳的圖像文件?或者向您的用戶發送通知?查看Firebase 文檔。想了解有關 Firebase 的 Flutter 插件的更多信息?查看FlutterFire了解更多信息。
- 想了解有關 Cloud Firestore 的更多信息?也許您想了解子集合和事務?前往Cloud Firestore 網絡代碼實驗室,了解更深入地了解 Cloud Firestore 的代碼實驗室。或查看此YouTube 系列以了解 Cloud Firestore !
學到更多
- Firebase 網站: firebase.google.com
- 顫振網站: flutter.dev
- FlutterFire Firebase Flutter 小部件: firebase.flutter.dev
- Firebase YouTube 頻道
- 顫振 YouTube 頻道
進展如何?
我們希望得到您的反饋!請在此處填寫(非常)簡短的表格。