Cloud Firestore Android 程式碼研究室

1. 總覽

目標

在本程式碼研究室中,您將在 Android 上建構由 Cloud Firestore 支援的餐廳推薦應用程式。您將學習下列內容:

  • 從 Android 應用程式讀取資料並寫入至 Firestore
  • 即時監聽 Firestore 資料的變更
  • 使用 Firebase 驗證和安全性規則保護 Firestore 資料
  • 編寫複雜的 Firestore 查詢

事前準備

開始本程式碼研究室之前,請確認您具備下列項目:

  • Android Studio Flamingo 以上版本
  • 搭載 API 19 以上版本的 Android 模擬器
  • Node.js 16 以上版本
  • Java 版本 17 以上

2. 建立 Firebase 專案

  1. 使用 Google 帳戶登入 Firebase 主控台
  2. Firebase 控制台中,按一下「新增專案」
  3. 如以下螢幕截圖所示,請輸入 Firebase 專案名稱 (例如「Friendly Eats」),然後按一下「繼續」

9d2f625aebcab6af.png

  1. 系統可能會要求您啟用 Google Analytics,但在本程式碼研究室中,您的選擇不影響結果。
  2. 大約一分鐘後,您的 Firebase 專案就會準備就緒。按一下「繼續」

3. 設定範例專案

下載程式碼

執行下列指令,複製這個程式碼研究室的範例程式碼。這會在電腦上建立名為 friendlyeats-android 的資料夾:

$ git clone https://github.com/firebase/friendlyeats-android

如果您的電腦上沒有 Git,也可以直接從 GitHub 下載程式碼。

新增 Firebase 設定

  1. Firebase 控制台中,選取左側導覽面板中的「專案總覽」。按一下「Android」按鈕選取平台。當系統提示您輸入套件名稱時,請使用 com.google.firebase.example.fireeats

73d151ed16016421.png

  1. 按一下「註冊應用程式」,然後按照操作說明下載 google-services.json 檔案,並將該檔案移至剛才下載程式碼的 app/ 資料夾中。然後點選「下一步」

匯入專案

開啟 Android Studio。依序按一下「File」 >「New」 >「Import Project」,然後選取「friendlyeats-android」資料夾。

4. 設定 Firebase 模擬器

在本程式碼研究室中,您將使用 Firebase 模擬器套件,在本機模擬 Cloud Firestore 和其他 Firebase 服務。這可提供安全、快速且免費的本機開發環境,讓您建構應用程式。

安裝 Firebase CLI

首先,您必須安裝 Firebase CLI。如果您使用的是 macOS 或 Linux,可以執行下列 cURL 指令:

curl -sL https://firebase.tools | bash

如果您使用的是 Windows,請參閱安裝說明,取得獨立二進位檔或透過 npm 安裝。

安裝 CLI 後,執行 firebase --version 應會回報 9.0.0 以上版本:

$ firebase --version
9.0.0

登入

執行 firebase login,將 CLI 連結至您的 Google 帳戶。系統會開啟新的瀏覽器視窗,完成登入程序。請務必選擇先前用於建立 Firebase 專案的帳戶。

friendlyeats-android 資料夾中執行 firebase use --add,將本機專案連結至 Firebase 專案。按照提示選取先前建立的專案,如果系統要求您選擇別名,請輸入 default

5. 執行應用程式

接下來,我們要首次執行 Firebase Emulator Suite 和 FriendlyEats Android 應用程式。

執行模擬器

friendlyeats-android 目錄的終端機中執行 firebase emulators:start,即可啟動 Firebase Emulator。您應該會看到類似以下內容的記錄:

$ firebase emulators:start
i  emulators: Starting emulators: auth, firestore
i  firestore: Firestore Emulator logging to firestore-debug.log
i  ui: Emulator UI logging to ui-debug.log

┌─────────────────────────────────────────────────────────────┐
│ ✔  All emulators ready! It is now safe to connect your app. │
│ i  View Emulator UI at http://localhost:4000                │
└─────────────────────────────────────────────────────────────┘

┌────────────────┬────────────────┬─────────────────────────────────┐
│ Emulator       │ Host:Port      │ View in Emulator UI             │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Authentication │ localhost:9099 │ http://localhost:4000/auth      │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Firestore      │ localhost:8080 │ http://localhost:4000/firestore │
└────────────────┴────────────────┴─────────────────────────────────┘
  Emulator Hub running at localhost:4400
  Other reserved ports: 4500

Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.

您現在已在電腦上執行完整的本機開發環境!請務必在程式碼研究室的後續部分繼續執行這項指令,您的 Android 應用程式需要連線至模擬器。

將應用程式連結至模擬器

在 Android Studio 中開啟 util/FirestoreInitializer.ktutil/AuthInitializer.kt 檔案。這些檔案包含邏輯,可在應用程式啟動時,將 Firebase SDK 連結至電腦上執行的本機模擬器。

FirestoreInitializer 類別的 create() 方法中,檢查這段程式碼:

    // Use emulators only in debug builds
    if (BuildConfig.DEBUG) {
        firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
    }

我們使用 BuildConfig,確保應用程式只在 debug 模式下執行時才會連線至模擬器。在 release 模式下編譯應用程式時,這個條件會為 false。

我們可以看到,它使用 useEmulator(host, port) 方法將 Firebase SDK 連結至本機 Firestore 模擬器。在整個應用程式中,我們會使用 FirebaseUtil.getFirestore() 存取這個 FirebaseFirestore 例項,確保在 debug 模式下執行時,一律連線至 Firestore 模擬器。

執行應用程式

如果您已正確新增 google-services.json 檔案,專案現在應該會編譯。在 Android Studio 中,依序按一下「Build」 >「Rebuild Project」,並確認沒有任何錯誤。

在 Android Studio 中,在 Android 模擬器上執行應用程式。一開始會顯示「登入」畫面。您可以使用任何電子郵件和密碼登入應用程式。這個登入程序會連線至 Firebase 驗證模擬器,因此不會傳送任何實際的憑證。

接著,在網路瀏覽器中前往 http://localhost:4000,開啟模擬器 UI。接著按一下「Authentication」分頁標籤,您應該會看到剛剛建立的帳戶:

Firebase Auth 模擬器

完成登入程序後,您應該會看到應用程式的主畫面:

de06424023ffb4b9.png

我們很快就會新增一些資料,填入主畫面。

6. 將資料寫入 Firestore

在本節中,我們會將部分資料寫入 Firestore,以便填入目前空白的主畫面。

應用程式的主要模型物件是餐廳 (請參閱 model/Restaurant.kt)。Firestore 資料會分割為文件、集合和子集合。我們會將每間餐廳儲存為文件,並儲存在名為 "restaurants" 的頂層集合中。如要進一步瞭解 Firestore 資料模型,請參閱說明文件中的文件和集合。

為了示範,我們會在應用程式中新增功能,讓使用者在溢位選單中點選「Add Random Items」按鈕時,隨機建立十家餐廳。開啟檔案 MainFragment.kt,並將 onAddItemsClicked() 方法中的內容替換為:

    private fun onAddItemsClicked() {
        val restaurantsRef = firestore.collection("restaurants")
        for (i in 0..9) {
            // Create random restaurant / ratings
            val randomRestaurant = RestaurantUtil.getRandom(requireContext())

            // Add restaurant
            restaurantsRef.add(randomRestaurant)
        }
    }

請注意以下幾點:

  • 我們先取得 "restaurants" 集合的參照。系統會在新增文件時隱含建立集合,因此您不需要在寫入資料前建立集合。
  • 您可以使用 Kotlin 資料類別建立文件,我們會使用這些類別建立每個餐廳文件。
  • add() 方法會將文件新增至集合,並自動產生 ID,因此我們不需要為每間餐廳指定專屬 ID。

現在請再次執行應用程式,然後按一下右上角溢位選單中的「Add Random Items」(新增隨機項目) 按鈕,以叫用您剛才編寫的程式碼:

95691e9b71ba55e3.png

接著,在網路瀏覽器中前往 http://localhost:4000,開啟模擬器 UI。接著點選「Firestore」分頁,您應該會看到剛新增的資料:

Firebase Auth 模擬器

這項資料 100% 是本機電腦的本機資料。事實上,您的實際專案甚至還沒有包含 Firestore 資料庫!也就是說,您可以放心嘗試修改及刪除這類資料,不會造成任何後果。

恭喜,您剛剛將資料寫入 Firestore!在下一個步驟中,我們將說明如何在應用程式中顯示這項資料。

7. 顯示 Firestore 中的資料

在這個步驟中,我們將瞭解如何從 Firestore 擷取資料,並在應用程式中顯示資料。從 Firestore 讀取資料的第一步,就是建立 Query。開啟檔案 MainFragment.kt,然後在 onViewCreated() 方法的開頭加入下列程式碼:

        // Firestore
        firestore = Firebase.firestore

        // Get the 50 highest rated restaurants
        query = firestore.collection("restaurants")
            .orderBy("avgRating", Query.Direction.DESCENDING)
            .limit(LIMIT.toLong())

我們現在要監聽查詢,以便取得所有符合條件的文件,並即時收到日後的更新通知。由於最終目標是將這項資料繫結至 RecyclerView,因此我們需要建立 RecyclerView.Adapter 類別來監聽資料。

開啟已部分實作的 FirestoreAdapter 類別。首先,讓轉接器實作 EventListener,並定義 onEvent 函式,以便接收 Firestore 查詢的更新內容:

abstract class FirestoreAdapter<VH : RecyclerView.ViewHolder>(private var query: Query?) :
        RecyclerView.Adapter<VH>(),
        EventListener<QuerySnapshot> { // Add this implements
    
    // ...

    // Add this method
    override fun onEvent(documentSnapshots: QuerySnapshot?, e: FirebaseFirestoreException?) {
        
        // Handle errors
        if (e != null) {
            Log.w(TAG, "onEvent:error", e)
            return
        }

        // Dispatch the event
        if (documentSnapshots != null) {
            for (change in documentSnapshots.documentChanges) {
                // snapshot of the changed document
                when (change.type) {
                    DocumentChange.Type.ADDED -> {
                        // TODO: handle document added
                    }
                    DocumentChange.Type.MODIFIED -> {
                        // TODO: handle document changed
                    }
                    DocumentChange.Type.REMOVED -> {
                        // TODO: handle document removed
                    }
                }
            }
        }

        onDataChanged()
    }
    
    // ...
}

在初始載入時,事件監聽器會為每份新文件接收一個 ADDED 事件。隨著查詢的結果集隨時間變動,監聽器會收到更多包含變更的事件。現在,讓我們完成事件監聽器的實作。首先新增三個方法:onDocumentAddedonDocumentModifiedonDocumentRemoved

    private fun onDocumentAdded(change: DocumentChange) {
        snapshots.add(change.newIndex, change.document)
        notifyItemInserted(change.newIndex)
    }

    private fun onDocumentModified(change: DocumentChange) {
        if (change.oldIndex == change.newIndex) {
            // Item changed but remained in same position
            snapshots[change.oldIndex] = change.document
            notifyItemChanged(change.oldIndex)
        } else {
            // Item changed and changed position
            snapshots.removeAt(change.oldIndex)
            snapshots.add(change.newIndex, change.document)
            notifyItemMoved(change.oldIndex, change.newIndex)
        }
    }

    private fun onDocumentRemoved(change: DocumentChange) {
        snapshots.removeAt(change.oldIndex)
        notifyItemRemoved(change.oldIndex)
    }

然後從 onEvent 呼叫這些新方法:

    override fun onEvent(documentSnapshots: QuerySnapshot?, e: FirebaseFirestoreException?) {

        // Handle errors
        if (e != null) {
            Log.w(TAG, "onEvent:error", e)
            return
        }

        // Dispatch the event
        if (documentSnapshots != null) {
            for (change in documentSnapshots.documentChanges) {
                // snapshot of the changed document
                when (change.type) {
                    DocumentChange.Type.ADDED -> {
                        onDocumentAdded(change) // Add this line
                    }
                    DocumentChange.Type.MODIFIED -> {
                        onDocumentModified(change) // Add this line
                    }
                    DocumentChange.Type.REMOVED -> {
                        onDocumentRemoved(change) // Add this line
                    }
                }
            }
        }

        onDataChanged()
    }

最後,實作 startListening() 方法來附加事件監聽器:

    fun startListening() {
        if (registration == null) {
            registration = query.addSnapshotListener(this)
        }
    }

應用程式現在已完全設定,可讀取 Firestore 中的資料。再次執行應用程式,您應該會看到先前步驟中新增的餐廳:

9e45f40faefce5d0.png

現在,請返回瀏覽器中的模擬器 UI,並編輯其中一個餐廳名稱。你應該會在應用程式中立即看到變更!

8. 排序及篩選資料

應用程式目前會顯示整個資料集內評分最高的餐廳,但在實際的餐廳應用程式中,使用者會想要排序及篩選資料。舉例來說,應用程式應能顯示「費城最佳海鮮餐廳」或「最便宜的披薩」。

按一下應用程式頂端的白色列,即可顯示篩選器對話方塊。在本節中,我們將使用 Firestore 查詢來讓這個對話方塊運作:

67898572a35672a5.png

讓我們編輯 MainFragment.ktonFilter() 方法。這個方法會接受 Filters 物件,這是我們建立的輔助物件,用於擷取篩選器對話方塊的輸出內容。我們會變更這個方法,以便從篩選條件建構查詢:

    override fun onFilter(filters: Filters) {
        // Construct query basic query
        var query: Query = firestore.collection("restaurants")

        // Category (equality filter)
        if (filters.hasCategory()) {
            query = query.whereEqualTo(Restaurant.FIELD_CATEGORY, filters.category)
        }

        // City (equality filter)
        if (filters.hasCity()) {
            query = query.whereEqualTo(Restaurant.FIELD_CITY, filters.city)
        }

        // Price (equality filter)
        if (filters.hasPrice()) {
            query = query.whereEqualTo(Restaurant.FIELD_PRICE, filters.price)
        }

        // Sort by (orderBy with direction)
        if (filters.hasSortBy()) {
            query = query.orderBy(filters.sortBy.toString(), filters.sortDirection)
        }

        // Limit items
        query = query.limit(LIMIT.toLong())

        // Update the query
        adapter.setQuery(query)

        // Set header
        binding.textCurrentSearch.text = HtmlCompat.fromHtml(
            filters.getSearchDescription(requireContext()),
            HtmlCompat.FROM_HTML_MODE_LEGACY
        )
        binding.textCurrentSortBy.text = filters.getOrderDescription(requireContext())

        // Save filters
        viewModel.filters = filters
    }

在上方程式碼片段中,我們會附加 whereorderBy 子句,以符合指定的篩選器,藉此建構 Query 物件。

再次執行應用程式,然後選取下列篩選器,顯示最受歡迎的低價餐廳:

7a67a8a400c80c50.png

你現在應該會看到篩選過的餐廳清單,只包含低價餐廳:

a670188398c3c59.png

到目前為止,您已在 Firestore 上建構出功能完備的餐廳推薦內容查看應用程式!你現在可以即時排序及篩選餐廳。在接下來的幾個章節中,我們會為餐廳新增評論,並在應用程式中新增安全性規則。

9. 整理子集合中的資料

在本節中,我們會在應用程式中新增評分功能,讓使用者評論自己最喜歡 (或最不喜歡) 的餐廳。

集合和子集合

目前,我們已將所有餐廳資料儲存在名為「restaurants」的頂層集合中。當使用者為餐廳評分時,我們希望為餐廳新增 Rating 物件。在這個工作中,我們會使用子集合。您可以將子集合視為附加至文件的集合。因此,每份餐廳文件都會有一個包含評分文件的評分子集合。子集合可協助整理資料,不必讓文件變得臃腫或需要複雜的查詢。

如要存取子集合,請在父項文件上呼叫 .collection()

val subRef = firestore.collection("restaurants")
        .document("abc123")
        .collection("ratings")

您可以存取及查詢子集合,就像使用頂層集合一樣,沒有大小限制或效能變化。如要進一步瞭解 Firestore 資料模型,請參閱本文

在交易中寫入資料

只要呼叫 .add(),即可將 Rating 新增至適當的子集合,但我們也需要更新 Restaurant 物件的平均評分和評分數量,以反映新資料。如果我們使用不同的作業來進行這兩項變更,就會出現許多競爭狀態,導致資料過時或不正確。

為確保評分正確新增,我們會使用交易為餐廳新增評分。這筆交易會執行以下幾項動作:

  • 讀取餐廳目前的評分,並計算新的評分
  • 將評分新增至子集
  • 更新餐廳的平均評分和評分數量

開啟 RestaurantDetailFragment.kt 並實作 addRating 函式:

    private fun addRating(restaurantRef: DocumentReference, rating: Rating): Task<Void> {
        // Create reference for new rating, for use inside the transaction
        val ratingRef = restaurantRef.collection("ratings").document()

        // In a transaction, add the new rating and update the aggregate totals
        return firestore.runTransaction { transaction ->
            val restaurant = transaction.get(restaurantRef).toObject<Restaurant>()
                ?: throw Exception("Restaurant not found at ${restaurantRef.path}")

            // Compute new number of ratings
            val newNumRatings = restaurant.numRatings + 1

            // Compute new average rating
            val oldRatingTotal = restaurant.avgRating * restaurant.numRatings
            val newAvgRating = (oldRatingTotal + rating.rating) / newNumRatings

            // Set new restaurant info
            restaurant.numRatings = newNumRatings
            restaurant.avgRating = newAvgRating

            // Commit to Firestore
            transaction.set(restaurantRef, restaurant)
            transaction.set(ratingRef, rating)

            null
        }
    }

addRating() 函式會傳回代表整個交易的 Task。在 onRating() 函式中,事件監聽器會新增至工作,以回應交易結果。

現在請再次執行應用程式,然後點選其中一個餐廳,系統應會顯示餐廳詳細資料畫面。按一下「+」按鈕,即可開始新增評論。選取星級並輸入文字,即可新增評論。

78fa16cdf8ef435a.png

按下「提交」即可啟動交易。交易完成後,你會看到下方顯示的評論,以及餐廳的評論數量更新:

f9e670f40bd615b0.png

恭喜!您現在已擁有以 Cloud Firestore 建構的行動社交餐廳評論應用程式。聽說這些產品現在很受歡迎。

10. 保護資料安全

目前我們尚未考量這項應用程式的安全性。我們如何知道使用者只能讀取及寫入正確的自身資料?Firestore 資料庫的安全性由名為「安全性規則」的設定檔維護。

開啟 firestore.rules 檔案,並將內容替換為以下內容:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Determine if the value of the field "key" is the same
    // before and after the request.
    function isUnchanged(key) {
      return (key in resource.data)
        && (key in request.resource.data)
        && (resource.data[key] == request.resource.data[key]);
    }

    // Restaurants
    match /restaurants/{restaurantId} {
      // Any signed-in user can read
      allow read: if request.auth != null;

      // Any signed-in user can create
      // WARNING: this rule is for demo purposes only!
      allow create: if request.auth != null;

      // Updates are allowed if no fields are added and name is unchanged
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys())
                    && isUnchanged("name");

      // Deletes are not allowed.
      // Note: this is the default, there is no need to explicitly state this.
      allow delete: if false;

      // Ratings
      match /ratings/{ratingId} {
        // Any signed-in user can read
        allow read: if request.auth != null;

        // Any signed-in user can create if their uid matches the document
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;

        // Deletes and updates are not allowed (default)
        allow update, delete: if false;
      }
    }
  }
}

這些規則會限制存取權,確保用戶端只進行安全的變更。舉例來說,餐廳文件的更新內容只能變更評分,而無法變更名稱或任何其他不可變更的資料。只有在使用者 ID 與已登入使用者相符的情況下,才能建立評分,這可防止冒用行為。

如要進一步瞭解安全性規則,請參閱說明文件

11. 結論

您現在已在 Firestore 上建立功能完整的應用程式。您已瞭解最重要的 Firestore 功能,包括:

  • 文件和集合
  • 讀取及寫入資料
  • 使用查詢排序及篩選
  • 子集合
  • 交易

瞭解詳情

如要進一步瞭解 Firestore,請參閱以下資源:

本程式碼研究室中的餐廳應用程式是以「Friendly Eats」範例應用程式為基礎。您可以前往這個頁面瀏覽該應用程式的原始碼。

選用:部署至正式環境

到目前為止,這個應用程式只使用 Firebase 模擬器套件。如要瞭解如何將此應用程式部署至實際的 Firebase 專案,請繼續進行下一個步驟。

12. (選用) 部署應用程式

到目前為止,這個應用程式完全在本機執行,所有資料都包含在 Firebase 模擬器套件中。在本節中,您將瞭解如何設定 Firebase 專案,讓這個應用程式能在正式環境中運作。

Firebase 驗證

在 Firebase 控制台中前往「驗證」專區,然後按一下「開始使用」。前往「登入方式」分頁,然後從「原生供應商」選取「電子郵件/密碼」選項。

啟用「電子郵件/密碼」登入方式,然後按一下「儲存」

sign-in-providers.png

Firestore

建立資料庫

前往控制台的「Firestore 資料庫」專區,然後按一下「建立資料庫」

  1. 當系統提示您選擇以「正式版模式」啟動安全性規則時,我們會盡快更新這些規則。
  2. 選擇要用於應用程式的資料庫位置。請注意,選取資料庫位置是永久性決定,如要變更,您必須建立新專案。如要進一步瞭解如何選擇專案位置,請參閱說明文件

部署規則

如要部署先前編寫的安全性規則,請在程式碼研究室目錄中執行下列指令:

$ firebase deploy --only firestore:rules

這會將 firestore.rules 的內容部署至專案,您可以前往控制台的「規則」分頁確認。

部署索引

FriendlyEats 應用程式有複雜的排序和篩選功能,因此需要多個自訂複合式索引。您可以手動在 Firebase 控制台中建立這些定義,但在 firestore.indexes.json 檔案中編寫定義,然後使用 Firebase CLI 部署定義,會更簡單。

開啟 firestore.indexes.json 檔案後,您會看到已提供必要的索引:

{
  "indexes": [
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "price", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "price", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "price", "mode": "ASCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "price", "mode": "ASCENDING" }
      ]
    }
  ],
  "fieldOverrides": []
}

如要部署這些索引,請執行下列指令:

$ firebase deploy --only firestore:indexes

請注意,建立索引並非一蹴可幾,您可以在 Firebase 主控台中監控進度。

設定應用程式

util/FirestoreInitializer.ktutil/AuthInitializer.kt 檔案中,我們將 Firebase SDK 設為在偵錯模式下連線至模擬器:

    override fun create(context: Context): FirebaseFirestore {
        val firestore = Firebase.firestore
        // Use emulators only in debug builds
        if (BuildConfig.DEBUG) {
            firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
        }
        return firestore
    }

如要使用實際的 Firebase 專案測試應用程式,您可以:

  1. 在版本模式中建構應用程式,並在裝置上執行該應用程式。
  2. BuildConfig.DEBUG 暫時替換為 false,然後再次執行應用程式。

請注意,您可能需要登出應用程式,然後重新登入,才能正確連線至正式版。