---
tags: Flutter
---
# 使用 Flutter Firebase 建構聊天室 APP
通過 firebase 建構即時聊天室,主要會用到 firebase 中的以下功能:
1. Authentication 身份驗證功能 - 用戶登入、登出、註冊、判斷當前用戶是否登入等等
2. Storage 文件存儲功能 - 保存與獲取圖片連結以顯示用戶頭像
3. Cloud Firestore 雲端資料庫 - 保存/獲取訊息、用戶資訊
4. Messaging 應用程式通訊 - 發送推播通知到設備上
5. Functions 函式 - 撰寫客製化函示,監聽資料庫變化以執行推播通知
## 建立 Flutter 專案
建立一個名為 chatapp 的專案:
`flutter create chatapp --org com.wangxuan`
>建立專案時加上 --org com.domain 可以預先設定 app ID
>這邊要注意 applicationId 限制不能帶有 **`_`**
>另外 flutter 專案名稱限制不能使用 **英文大寫** 與 **`-`**
## 建立 Firebase 專案
開啟 [firebase 官網](https://firebase.google.com/) 登入 google 帳號並新增一個專案
>免費版的 firebase 最多只能建立三個專案
>想繼續免費建議創新的 google 帳號
按照步驟依序設置好專案名稱、選擇是否開啟 google 分析(該專案用不到可不開)、選擇國家,
接著等待專案建立完成
### 新增 Android 應用程式
專案建立完成後會自動跳轉到專案總覽頁面,
此時於專案名稱下方可以點擊新增應用程式,
點選新增 Android 應用程式:
1. **註冊應用程式**
從 chatapp 專案中的 `/android/app/build.gradle` 裏面找到 `applicationId`,將其填入
2. **進入下載設定檔案**
按照說明,下載檔案 `google-services.json` 並將該檔案新增到 `/android/app` 中
3. **進入新增 Firebase SDK**
按照說明,開啟 `/android/build.gradle` 檔案,確認 `buildscript` 與 `allprojects` 中是否都有 `google()` 存在
並在 `buildscript` 中的 `dependencies` 裡面添加 `classpath 'com.google.gms:google-services:4.3.15'`
4. **完成建立**
### 新增 IOS 應用程式
完成建立 Android 應用程式後會返回專案總覽頁面,
一樣從專案名稱下方點擊新增 IOS 應用程式:
1. **註冊應用程式**
從 chatapp 專案中的 `/ios/Runner.xcodeproj/project.pbxproj` 裏面找到 `PRODUCT_BUNDLE_IDENTIFIER`,將其填入(應該與 Android 中的 `applicationId` 一致)
2. **進入下載設定檔案**
按照說明,下載檔案 `GoogleService-Info.plist` 並將該檔案新增到 `/ios/Runner` 中
3. **直接點擊左上角 x** 跳出剩下步驟
由於我們要使用 `Flutter Firebase SDK`,所以『新增 Firebase SDK』與『初始化程式碼』步驟直接略過
## 設置 Firebase + Flutter
請參考:https://firebase.flutter.dev/docs/overview
開啟終端機並 cd 到稍早建立的 flutter 專案 chatapp 資料夾中:
1. 執行命令: `flutter pub add firebase_core` 安裝 firebase_core
2. 執行命令: `curl -sL https://firebase.tools | bash` 安裝 Firebase CLI
3. 執行命令: `firebase login` 登入 firebase 帳號
4. 執行命令: `dart pub global activate flutterfire_cli` 啟用 flutterfire_cli
>假設出現錯誤導致無法進行,請參考[此問答](https://stackoverflow.com/questions/70320263/the-term-flutterfire-is-not-recognized-as-the-name-of-a-cmdlet-function-scri)
>並於解決問題後再次執行命令 `dart pub global activate flutterfire_cli`
5. 執行命令:`flutterfire configure`
6. 依序選擇稍早已建立好的 firebase 專案、選擇開好的應用程式(IOS、Android)
7. 回到 VS Code 中,進入 `main.dart` 檔案,引入 firebase_core 與 `firebase_options.dart`
8. 將 main 函式改為:
```javascript=
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MyApp());
}
```
## 安裝與使用 Cloud Firestore(保存與獲取訊息、用戶資訊)
請先於專案項目 chatapp 中,執行命令: `flutter pub add cloud_firestore` 進行安裝 cloud_firestore
* NOTE:如果你是使用 IOS 或 macOS,在首次啟動模擬器時將會耗時非常久的時間(可能要幾個小時),可參考[官方建議](https://firebase.flutter.dev/docs/firestore/overview#4-optional-improve-ios--macos-build-times)以及 [GitHub READEME.md](https://github.com/invertase/firestore-ios-sdk-frameworks#usage)進行處理:
1. 開啟 `/ios/Podfile` 檔案於 `target 'Runner' do` 添加 `pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '8.15.0'`
2. 開啟終端機,cd 至 ios 資料夾中,運行命令 `pod install`
3. 如出現 `CocoaPods could not find compatible versions for pod "FirebaseFirestore":` 錯誤消息,請查看 `Firebase/Firestore (= 10.7.0) was resolved to 10.7.0,`
4. 將 `/ios/Podfile` 檔案中的 `:tag => '8.15.0'` 更改為 `:tag => '10.7.0'`(請藉由上一步看到的數字將 tag 改為該版本號)
5. 再次執行命令 `pod install`,並啟動模擬器即可。
* NOTE:如果在啟動 Android 模擬器時,出現錯誤`Error while merging dex archives: The number of method references in a .dex file cannot exceed 64K`,可參考[官方說明](https://firebase.flutter.dev/docs/manual-installation/android#enabling-multidex),啟用`Multidex`:
1. 開啟 `/android/app/build.gradle` 檔案
2. 於 `defaultConfig` 中添加一行 `multiDexEnabled true`
3. 於 `dependencies` 中添加一行 `implementation 'com.android.support:multidex:1.0.3'`
4. 再次重新啟動模擬器即可。
接著進入 firebase 專案總覽處,於左側選單找到 Firestore Database 啟用資料庫
1. 選擇 test mode
2. 選擇國家(有台灣可選了耶!)
3. 點擊完成按鈕等待完成後進入資料頁面
4. 點擊『新增集合』,設置集合 ID 為 `chats`
5. 於文件 ID 處點擊『自動產生的 ID』,接著刪掉下方欄位,點擊儲存
6. 於 `chats` 的右側再次點擊『新增集合』,設置集合 ID 為 `messages`
7. 於文件 ID 處點擊『自動產生的 ID』,接著於欄位設置名稱為`text`,類型`string`,值`Hi, there!`,點擊儲存
最後即可回到 flutter 專案 chatapp 中開始使用 Firestore Database 中的數據
```dart=
// 獲取數據的方式:
StreamBuilder( // 使用 flutter 提供的 StreamBuilder 小部件自動根據數據進行 UI 更新,與 FutureBuilder 小不見得用法相似,僅需傳入 builder 與 stream 即可
builder: (context, snapshot) => ListView.builder(
itemBuilder: (context, index) => snapshot.connectionState == ConnectionState.waiting
? const LinearProgressIndicator()
: Container(
padding: const EdgeInsets.all(10),
child: Text(snapshot.data?.docs[index]['text']), // 主要通過 snapshot.data 獲取 stream 的數據
),
itemCount: snapshot.data?.docs.length,
padding: const EdgeInsets.all(10),
),
stream: FirebaseFirestore.instance.collection('chats/XRB18xKXPbnR87LLh3mq/messages').snapshots(), // 傳入 Firestore 中的 messages 集合快照
),
// 添加數據的方式:
floatingActionButton: FloatingActionButton( // 新增一個觸發事件用的按鈕
child: const Icon(Icons.add),
onPressed: () { // 點擊按鈕後執行
FirebaseFirestore.instance // 通過 FirebaseFirestore.instance
.collection('chats/XRB18xKXPbnR87LLh3mq/messages') // 取得 Firestore 中的 messages 集合
.add({ // 傳入物件,以 key-value 的形式增加數據資料
'text': 'add new line to test.'
})
});
},
),
```
## 安裝與使用 Firebase Authentication(登入、註冊、登出、獲取當前使用者)
首先於專案項目 chatapp 中,執行命令: `flutter pub add firebase_auth` 進行安裝 firebase_auth
接著進入 firebase 專案總覽處,於左側選單找到 Authentication 點擊開始使用
並於 `Sign-in method` 中點選『電子郵件/密碼』,啟用並儲存即可
最後回到 flutter 專案 chatapp 中,先建立一個用於登入/註冊的表單頁面 `auth.dart` 檔案([可參考](https://hackmd.io/WHaymHAzSwS9C0Xkj5Vc_A#%E8%A1%A8%E5%96%AE%E5%B0%8F%E9%83%A8%E4%BB%B6-Form))
1. 新增 Form 小部件、設置 GlobalKey() 為 formkey
2. 新增四個 TextFormField 小部件,分別用於輸入『暱稱、電子郵件、密碼、確認密碼』
3. 設置 TextFormField 小部件中的 keyboardType、validator、onSaved 等重要內容
4. 新增登入與註冊按鈕,點擊後調用登入或註冊的函數
5. 建立 submit 函數用來登入或註冊,裡面先通過 `_formKey.currentState!.validate()` 判斷是否完成驗證
6. 執行 `_formKey.currentState!.save();` 讓 onSaved 事件被觸發
7. 將 onSaved 中保存的『暱稱、電子郵件、密碼』以及『當前為登入或註冊』當為參數傳遞並發送登入或註冊請求
*submit 函數中發送登入與註冊請求的方法可參考[官方文件](https://firebase.google.com/docs/auth/flutter/password-auth)
### FirebaseAuth 登入與註冊請求範例
```dart=
final _auth = FirebaseAuth.instance;
void sendAuthRequest(bool isSignup, String name, String email, String password) async {
try {
dynamic authResult;
if (isSignup) { // 判斷當前為登入還是註冊
authResult = await _auth.createUserWithEmailAndPassword(email: email, password: password); // 使用信箱與密碼創建帳號
FirebaseFirestore.instance.collection('users').doc(authResult.user.uid).set({ // 使用 FirebaseFirestore 建立 users 集合,並設置文件 id 為 user.uid
'username': name, // 於文件中新增欄位 username 為 name
'email': email, // 於文件中新增欄位 email 為 email
});
} else {
authResult = await _auth.signInWithEmailAndPassword(email: email, password: password); // 登入帳號
}
} on FirebaseAuthException catch (err) { // 處理錯誤
var errMsg = '系統出現問題,請重新啟動。';
if (err.code == 'invalid-email') {
errMsg = '無效的電子信箱!';
} else if (err.code == 'user-disabled') {
errMsg = '此帳號已被管理員禁用!';
} else if (err.code == 'user-not-found') {
errMsg = '帳號不存在,請先建立帳號。';
} else if (err.code == 'wrong-password') {
errMsg = '密碼錯誤!';
} else if (err.code == 'email-already-in-use') {
errMsg = '此帳號已被使用。';
} else if (err.code == 'operation-not-allowed') {
errMsg = '不允許操作!';
} else if (err.code == 'weak-password') {
errMsg = '密碼強度不足。';
} else if (err.code == 'too-many-requests') {
errMsg = '請求次數過多,請稍後再試。';
}
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
behavior: SnackBarBehavior.floating,
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.8),
elevation: 0,
content: Text(errMsg),
));
}
}
```
>error.code 列表可從[此連結](https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#createuserwithemailandpassword)與[此連結](https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#signinwithemailandpassword)中尋找
假設完成後無法正確進行登入或註冊,請嘗試重啟模擬器
>如於重啟模擬器時發生錯誤,請參考[此連結](https://stackoverflow.com/questions/67443265/error-regarding-undefined-method-map-for-nilnilclass-for-flutter-app-cocoap)進行修復(誠摯感謝估狗)
### FirebaseAuth 關於當前用戶的幾個常用方法
1. 判斷當前是否有用戶登入:`FirebaseAuth.instance.authStateChanges()` 假設 hasData 則表示當前有用戶登入
2. 取得當前用戶的 id:`FirebaseAuth.instance.currentUser?.uid`
3. 設置當前用戶的名稱:`FirebaseAuth.instance.currentUser?.updateDisplayName(name)`
4. 登出:`FirebaseAuth.instance.signOut()`
>其他相關資訊可查看[官方文件](https://firebase.google.com/docs/auth/flutter/manage-users)說明
## 安裝與使用 Firebase Storage(保存與顯示用戶頭像)
首先於專案項目 chatapp 中,執行命令: `flutter pub add firebase_storage` 進行安裝 firebase_storage
接著進入 firebase 專案總覽處,於左側選單找到 Storage 點擊開始使用
選用 test mode 並選取 Cloud Storage 位置並按下完成即可。
>這邊要注意,免費版本只能使用一個 bucket (值區),
>假設原本的 Firebase 專案已經有使用 Storage 功能,建議重新申請一個 Firebase 帳號
>也可以選擇將 Firebase 升級為付費版本,建立新的 bucket (值區):
>在 Storage 頁面的右上角有三個點點,點選後就會看到刪除與新增值區的選項
於 chatapp 中,我們為每個使用者添加頭像,
首先需要在註冊頁面中,新增關於圖片的函數,可以使用 image_picker 套件
通過命命: flutter pub add image_picker 安裝 image_picker 套件
安裝後針對 IOS 設置好 ios/Runner/Info.plist 檔案([參考](https://pub.dev/packages/image_picker#ios))
主要添加內容如下:
```plist=
<key>NSCameraUsageDescription</key>
<string>Places App need to use Camera.</string> // 輸入為什麼要存取相機
<key>NSPhotoLibraryUsageDescription</key>
<string>Places App need to use PhotoLibrary.</string> // 輸入為什麼要存取照片庫
```
>通常都是一個 key配一個 value,在新增上方四行時,
>需注意不要將其放置在某組 key 與 value 的中間。
完成後在註冊的表單中建立一個圖片預覽用的 CircleAvatar 小部件,
再新增一個點擊後可以開啟照片庫用的 IconButton 按鈕小部件,並為其添加一個用來獲取圖片的 onPressed 用函數:
```dart=
void _pickImage() async {
final imageFile = await ImagePicker().pickImage(
source: ImageSource.gallery, // 開啟照片庫
);
if (imageFile == null) { // 如果沒有選取圖片則返回
return;
}
setState(() { // 新增一個 _previewImage 參數用於存放要顯示的預覽圖
_previewImage = imageFile.path;
});
widget.imagePickFn(File(_previewImage)); // 呼叫外部函數用於傳遞圖片檔案給外部使用
}
```
>這邊是將圖片有關的小部件自己封裝成一支 user_image_picker.dart 檔案
>而外部則是主要的註冊表單 auth_form.dart 文件,於提交表單的 submit 函數中傳入用戶上傳的圖片
接著針對註冊的函數,原先在幫用戶建立帳號之後,會一併新增用戶的資料到 users 集合中
當時新增的資料為用戶 id 以及用戶名,
現在為其多新增一個欄位 user_image_url 用來存放圖片,
註冊函數應更新為:
```dart=
import 'package:firebase_storage/firebase_storage.dart'; // 引入 firebase_storage
void _submitAuthForm(bool isSignup, String name, String email, String password, File? userImageFile) async { // 多傳入圖片參數
try {
dynamic authResult;
if (isSignup) {
authResult = await _auth.createUserWithEmailAndPassword(email: email, password: password);
final ref = FirebaseStorage.instance // 使用 firebase_storage
.ref() // 創建飲用
.child('user_image') // 新增文件夾 user_image
.child('${_auth.currentUser!.uid}.jpg'); // 新增文件 ${_auth.currentUser!.uid}.jpg
UploadTask uploadTask = ref.putFile(userImageFile!); // 將圖片檔案上傳為 user_image/${_auth.currentUser!.uid}.jpg
String imgurl = await (await uploadTask).ref.getDownloadURL(); // 於上傳完成後獲取圖片連結
FirebaseFirestore.instance.collection('users').doc(authResult.user.uid).set({
'username': name,
'email': email,
'user_image_url': imgurl, // 新增欄位 user_image_url 存放圖片連結
});
} else { // 登入函數不動
authResult = await _auth.signInWithEmailAndPassword(email: email, password: password);
}
} on FirebaseAuthException catch (err) {
// 處理錯誤並顯示 SnackBar
}
}
```
接著就可以通過 Firestore 獲取 users 集合中存放的圖片來顯示用戶頭像:
```dart=
FutureBuilder( // 使用 FutureBuilder 小部件
builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (snapshot.data!.data()!['user_image_url'] != null)
Padding(
padding: const EdgeInsets.only(right: 10),
child: CircleAvatar( // 新增 CircleAvatar 小部件用於顯示用戶頭像
backgroundImage: NetworkImage(snapshot.data!.data()!['user_image_url']),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
snapshot.data!.data()!['username'],
style: TextStyle(
color: Theme.of(context).primaryColorDark,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 5,
),
Text(
message,
style: TextStyle(
color: Theme.of(context).primaryColorDark,
),
),
],
),
],
),
future: FirebaseFirestore.instance.collection('users').doc(userId).get(), // 使用 Firestore 提供的方法,針對 users 集合進行資料獲取
),
```
## 安裝與使用 Cloud Messaging(向手機發送推播通知)
首先於專案項目 chatapp 中,執行命令: `flutter pub add firebase_messaging` 進行安裝 firebase_messaging
### 接著針對 Android 進行設置
1. 確保 `android/build.gradle` 文件中有:
```
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.15'
}
```
2. 確保 `android/app/build.gradle` 文件中有 `apply plugin: 'com.google.gms.google-services'`
3. 確保你的設備有安裝 Google Play 服務(可以通過 Android Studio 開啟 "AVD Manager" 確認模擬器是否擁有 Google Play 的播放鍵 icon 即可)
4. (此項可選)於 `android/app/src/main/AndroidManifest.xml` 文件中,往`<activity>`標籤中新增:
```
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
```
### 測試 Android 是否可成功接收推播通知
1. 在 chatapp 中關閉當前執行中的設備,並使用 Android 設備重新啟動服務
2. 進入 Firebase Messaging 中,點擊“建立第一個廣告活動”
3. 選擇“Firebase 通知訊息”進行建立
4. 輸入標題、文字,點擊下一步
5. 選取 Android 應用程式(下拉選項應該有你註冊的 IOS 與 Android 可選,點擊下一步
6. 排定時間設為現在,直接點擊右下角審查,點擊發表
7. 開啟執行中的 Android 設備,將 chatapp 整個關掉(螢幕網上滑動並將應用程式整個上滑移除)
8. 接著開啟 Android 的設定,點擊通知,點擊應用程式設定,將 chatapp 開啟通知
9. 回到 Firebase Messaging 中再次新增活動,即可看到通知(有時候需要幾分鐘的時間才會收到通知,可以多新增活動幾次)
### 針對 IOS 進行設置
1. 將 Apple 開發者帳號升級為付費版本
1. 登入 Apple Developer 帳號
2. 進入 account 頁面,將帳號升級為“蘋果開發者計劃”(應該會出現在 account 頁面問你是否要升級)
3. 輸入個人資料進行購買計劃(費用是一年 99 美金)
4. 等待審核通過會寄送電子郵件通知啟用
2. 下載 APNs key 檔案
1. 於 Apple Developer 頁面最下方點擊進入 Certificates, IDs, & Profiles 頁面
2. 於左側選單點擊進入 keys 頁面,點擊加號新增 key
3. 設置 Key Name 並勾選 APNs
4. 點擊右上角 Continue,接著確認配置無誤繼續點擊 Confirm
5. 下載 key 檔案(注意:該檔案僅能下載一次,若遺失檔案就只能刪除現有的 key 並重新建立一個 key 了)
3. 設置 App ID
1. 回到 Certificates, IDs, & Profiles 頁面,點擊進入 Identifiers 頁面
2. 點擊加號新增,選擇 App ID 點擊 Continue
3. 輸入簡短的描述(FLutter Chat Example 之類即可)、填上 Bundle ID(於 `/ios/Runner.xcodeproj/project.pbxproj` 文件中可找到 PRODUCT_BUNDLE_IDENTIFIER)
4. 往頁面下方滾動,確保有勾選 “Push Notifications” 後點擊右上角 Continue,確認配置無誤繼續點擊 Confirm
4. 設置 Xcode
1. 用 Xcode 開啟 cahtapp/ios
2. 點選 Runner 並於右側選單切換至 Signing & Capabilities
3. 點擊 “+Capability” 選擇 “Push Notifications”(點兩下啟用)
4. 再次點擊 “+Capability” 選擇 “Background Modes”(點兩下啟用)
5. 於 “Background Modes” 中勾選 “Background fetch” 跟 “Remote notifications”
5. 上傳 APNs key 到 Firebase 專案中
1. 於 Firebase 中點擊左上角專案總覽旁的齒輪,並點選專案設定,切換至 “雲端通訊”
2. 往下滾到 “Apple 應用程式設定” 於 APN 驗證金鑰處點擊上傳,上傳步驟 2 下載的 .p8 檔案
3. 輸入金鑰 ID(於 Certificates, IDs, & Profiles 頁面進入 keys 後可看到) 與團隊 ID(在 Certificates, IDs, & Profiles 頁面的右上角可看到)
### 測試 IOS 是否可成功接收推播通知
1. 在 chatapp 中關閉當前執行中的設備,並使用真實 iPhone 設備進行測試(IOS 模擬器無法提供測試)
2. 將 chatapp 中的 chat_screen.dart 更改為有狀態小部件
3. 撰寫程式碼要求獲取通知權限:
```dart=
class _ChatScreenState extends State<ChatScreen> {
@override
void initState() {
final fbn = FirebaseMessaging.instance;
fbn.requestPermission();
super.initState();
}
@override
Widget build(BuildContext context) {
//...
}
}
```
4. 進入 Firebase Messaging 中,點擊“建立第一個廣告活動”
5. 選擇“Firebase 通知訊息”進行建立
6. 輸入標題、文字,點擊下一步
7. 選取 Android 應用程式(下拉選項應該有你註冊的 IOS 與 Android 可選,點擊下一步
8. 排定時間設為現在,直接點擊右下角審查,點擊發表
9. 開啟執行中的 Android 設備,將 chatapp 整個關掉(螢幕網上滑動並將應用程式整個上滑移除)
10. 接著開啟 Android 的設定,點擊通知,點擊應用程式設定,將 chatapp 開啟通知
11. 回到 Firebase Messaging 中再次新增活動,即可看到通知(有時候需要幾分鐘的時間才會收到通知,可以多新增活動幾次)
## 安裝與使用 Firebase Functions
開啟 Firebase 介面,於左側選單點選 Functions
升級為付費版本(會涵蓋所有免費版本的權益,基本上用量爆掉才真的會跟你收費)
點擊開始使用,按照步驟於終端機輸入指令:
1. 先 cd 到 chatapp 專案項目中
2. 安裝 Firebase 工具: `npm install -g firebase-tools`
3. 啟動專案: `firebase init`
```
? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices.
Functions: Configure a Cloud Functions directory and its files
? Please select an option:
Use an existing project
? Select a default Firebase project for this directory:
flutter-chatroom-xxxxx (flutter-chatroom)
? What language would you like to use to write Cloud Functions?
JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style?
Yes
? Do you want to install dependencies with npm now?
Yes
```
4. 開啟 chatapp/functions/index.js 進行程式碼撰寫([主要的程式碼寫法說明](https://firebase.google.com/docs/functions/firestore-events)):
撰寫 Functions 於發送訊息時觸發推播通知:
```javascript=
const functions = require("firebase-functions"); // 引入 firebase-functions
const admin = require("firebase-admin"); // 引入 firebase-admin
admin.initializeApp(); // 初始化應用程序
exports.myFunction = functions.firestore // 呼叫 firestore
.document("chat/{message}") // 監聽 chat 集合中的內容
.onCreate( (snap, context) => { // 當建立新的文件就觸發函數
return admin.firestore().collection("users").doc(snap.data().user_id).get().then((data) => // 回傳觸發推播通知
admin.messaging().sendToTopic("chat", { // 使用 admin.messaging() 發送推播通知,此處的 sendToTopic("chat") 則表示向特定主題發送
notification: { // 傳入推播通知內容
title: data.data().username, // 推播通知標題
body: snap.data().text, // 推播通知描述
clickAction: "FLUTTER_NOTIFICATION_CLICK", // 針對安卓設置的 key-value 為了確保點擊後可正確跳轉至 chatapp
},
}),
);
});
```
>如需設置主題,可於 `.dart` 中撰寫 `const fbm = FirebaseMessaging.instance;` 處的最後添加一行:
>`fbm.subscribeToTopic('chat');` => 'chat' 為主題的名稱
5. 部署 index.js 內容到 Functions:執行 `firebase deploy`
6. 測試:
1. 用 VS Code 執行安卓模擬器開啟 chatapp 並登入或註冊一個帳號
2. 將安卓模擬器的 chatapp 滑掉
3. 用真實 iPhone 開啟 chatapp 並登入或註冊另一個帳號
4. 從 iPhone 發送一則訊息,至 Functions 點擊 myFunction 的三個點點,選擇查看紀錄檔
5. 進入 Google Cloud 查看紀錄檔案
1. 假設出現錯誤 `Error: An error occurred when trying to authenticate to the FCM servers. Make sure the credential used to authenticate this SDK has the proper permissions....`
2. 到 Google Cloud 中,從左色選單找到 API 和服務頁面
3. 點擊 “+啟用 API 和服務”
4. 確認左上角的專案是 chat 專案
5. 於 “搜尋 API 和服務” 欄位中輸入 “Cloud Messaging” 點擊啟用
6. 再次於 iPhone 中傳送一則訊息,並查看紀錄檔
6. 於紀錄檔中看到 “Function execution took 1579 ms, finished with status: 'ok'” 即可於安卓模擬器中接收通知
7. 接著反過來將 iPhone 中的 chatapp 滑到後台
8. 開啟安卓模擬器模擬器的 chatapp 發送一則消息
9. 確認 iPhone 是否收到訊息
## 使用真實設備進行測試
1. 將 iPhone 使用 USB 的方式連結電腦,並依序點擊信任此裝置、輸入手機密碼等等
2. 進入 iPhone 的“設定>隱私權與安全性>開發者模式”將開發者模式打開,此時手機會要求你重新開機
3. 開啟電腦的 Xcode 應用程式,點擊 “Open a project or file” ,選擇想開啟的 Flutter 專案底下的 ios 資料夾
4. 在 Xcode 上方選單列中的 Device 中選擇你的 iPhone
5. 在 Xcode 左側選單中點選 Runner ,檢查 Signing 是否有選擇 Team (沒有的話就用自己的 apple ID 申請一個)
6. 在 Xcode 中點擊左上角的播放鍵進行 build
a. 假設出現 “build failed” ,請開啟終端機 cd 到想開啟的 Flutter 專案目錄中,執行 `flutter clean` 再執行 `flutter build ios`
b. 假設出現錯誤 “Error (Xcode): No profiles for 'com.example.xxxxApp' were found: Xcode couldn't find any iOS App Development” 則將 Team 下方的 Bundle Identifier 更新成唯一的值(比如加上自己的名字之類的),再重新執行一次 `flutter build ios`
6. 完成後手機會出現 Flutter APP 的 icon,但開啟失敗,並顯示提示 “開發者不受裝置信任的通知”,此時請到手機的 “設定>一般>VPN與裝置管理” 將自己的開發者帳號設為信任
7. 再次回到 Xcode 中點擊一次播放鍵,即可成功開啟 APP 進行實測
>假設出現錯誤顯示“無法打開 iproxy,因為無法驗證開發人員”可參考[此問答](https://github.com/flutter/flutter/issues/42969)進行處理