Cloud Firestore Android 代碼實驗室

1. 概述

目標

在此 Codelab 中,您將在 Cloud Firestore 支持的 Android 上構建一個餐廳推薦應用。你將學到如何:

  • 從 Android 應用讀取數據並將其寫入 Firestore
  • 實時監聽 Firestore 數據的變化
  • 使用 Firebase 身份驗證和安全規則來保護 Firestore 數據
  • 編寫複雜的 Firestore 查詢

先決條件

在開始此 Codelab 之前,請確保您已:

  • 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,就本 Codelab 而言,您的選擇並不重要。
  2. 大約一分鐘後,您的 Firebase 項目將準備就緒。單擊繼續

3. 設置示例項目

下載代碼

運行以下命令來克隆此 Codelab 的示例代碼。這將在您的計算機上創建一個名為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。單擊“文件” > “新建” > “導入項目” ,然後選擇“Friendlyeats-android”文件夾。

4. 設置 Firebase 模擬器

在此 Codelab 中,您將使用Firebase Emulator Suite在本地模擬 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 和 Friendship Android 應用了。

運行模擬器

在終端的friendlyeats-android目錄中運行firebase emulators:start來啟動Firebase模擬器。您應該看到如下日誌:

$ 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.

現在,您的計算機上運行了一個完整的本地開發環境!確保在 Codelab 的其餘部分中保持此命令運行,您的 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 身份驗證模擬器,因此不會傳輸真正的憑據。

現在,通過在 Web 瀏覽器中導航到http://localhost:4000打開模擬器 UI。然後單擊“身份驗證”選項卡,您應該會看到剛剛創建的帳戶:

Firebase 身份驗證模擬器

完成登錄過程後,您應該會看到應用程序主屏幕:

de06424023ffb4b9.png

很快我們將添加一些數據來填充主屏幕。

6. 將數據寫入Firestore

在本節中,我們將向 Firestore 寫入一些數據,以便可以填充當前空的主屏幕。

我們應用程序中的主要模型對像是一家餐廳(請參閱model/Restaurant.kt )。 Firestore 數據分為文檔、集合和子集合。我們將每個餐廳作為文檔存儲在名為"restaurants"的頂級集合中。要了解有關 Firestore 數據模型的更多信息,請閱讀文檔中有關文檔和集合的信息。

出於演示目的,我們將在應用程序中添加功能,以便在單擊溢出菜單中的“添加隨機項目”按鈕時創建十個隨機餐廳。打開文件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 數據類創建文檔,我們用它來創建每個 Restaurant 文檔。
  • add()方法使用自動生成的 ID 將文檔添加到集合中,因此我們不需要為每個餐廳指定唯一的 ID。

現在再次運行應用程序,然後單擊溢出菜單(右上角)中的“添加隨機項”按鈕以調用您剛剛編寫的代碼:

95691e9b71ba55e3.png

現在,通過在 Web 瀏覽器中導航到http://localhost:4000打開模擬器 UI。然後單擊Firestore選項卡,您應該會看到剛剛添加的數據:

Firebase 身份驗證模擬器

此數據 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. 在子集合中組織數據

在本節中,我們將向應用程序添加評級,以便用戶可以評論他們最喜歡(或最不喜歡)的餐廳。

集合和子集合

到目前為止,我們已將所有餐廳數據存儲在名為“餐廳”的頂級集合中。當用戶對餐廳進行評分時,我們希望向餐廳添加一個新的Rating對象。對於此任務,我們將使用子集合。您可以將子集合視為附加到文檔的集合。因此,每個餐廳文檔都會有一個充滿評級文檔的評級子集合。子集合有助於組織數據,而不會導致文檔膨脹或需要復雜的查詢。

要訪問子集合,請在父文檔上調用.collection()

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

您可以像訪問頂級集合一樣訪問和查詢子集合,沒有大小限製或性能變化。您可以在此處閱讀有關 Firestore 數據模型的更多信息。

在事務中寫入數據

Rating添加到正確的子集合只需要調用.add() ,但我們還需要更新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 {
    match /{document=**} {
      //
      // WARNING: These rules are insecure! We will replace them with
      // more secure rules later in the codelab
      //
      allow read, write: if request.auth != null;
    }
  }
}

讓我們更改這些規則以防止不需要的數據訪問或更改,打開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,可以從以下一些不錯的起點開始:

此 Codelab 中的餐廳應用程序基於“Friendly Eats”示例應用程序。您可以在此處瀏覽該應用程序的源代碼。

可選:部署到生產環境

到目前為止,該應用程序僅使用了 Firebase Emulator Suite。如果您想了解如何將此應用部署到真正的 Firebase 項目,請繼續執行下一步。

12.(可選)部署您的應用程序

到目前為止,這個應用程序完全是本地的,所有數據都包含在 Firebase Emulator Suite 中。在本部分中,您將了解如何配置 Firebase 項目,以便該應用程序能夠在生產環境中運行。

Firebase 身份驗證

在 Firebase 控制台中,轉到Authentication部分,然後單擊Get started 。導航到“登錄方法”選項卡,然後從本機提供商中選擇“電子郵件/密碼”選項。

啟用電子郵件/密碼登錄方法並單擊保存

登錄提供商.png

火庫

創建數據庫

導航到控制台的Firestore Database部分,然後單擊Create Database

  1. 當提示有關安全規則時,選擇以生產模式啟動,我們將很快更新這些規則。
  2. 選擇您想要用於您的應用程序的數據庫位置。請注意,選擇數據庫位置是一個永久性決定,要更改它,您將必須創建一個新項目。有關選擇項目位置的更多信息,請參閱文檔

部署規則

要部署您之前編寫的安全規則,請在 codelab 目錄中運行以下命令:

$ firebase deploy --only firestore:rules

這會將firestore.rules的內容部署到您的項目,您可以通過導航到控制台中的“規則”選項卡來確認。

部署索引

Friendship 應用程序具有復雜的排序和過濾功能,需要大量自定義復合索引。這些可以在 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並再次運行應用程序。

請注意,您可能需要退出應用程序並再次登錄才能正確連接到生產環境。