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並再次運行應用程式。

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