Cloud Firestore Android 程式碼研究室

1. 總覽

目標

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

  • 從 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 專案名稱 (例如「友善餐點」),然後按一下「繼續」

9d2f625aebcab6af.png

  1. 系統可能會請您啟用 Google Analytics,因此在本程式碼研究室中,您的選擇無關緊要。
  2. 大約一分鐘後,即可準備 Firebase 專案。按一下 [繼續]

3. 設定範例專案

下載程式碼

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

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

如果您的機器中沒有 Git,您也可以直接從 GitHub 下載程式碼。

新增 Firebase 設定

  1. Firebase 控制台,選取左側導覽列的「Project Overview」。按一下「Android」按鈕選取平台。系統提示您輸入套件名稱時,使用 com.google.firebase.example.fireeats

73d151ed16016421.png

  1. 按一下「Register App」,然後按照操作說明下載 google-services.json 檔案,然後移至剛才下載程式碼的 app/ 資料夾。接著點選「下一步」

匯入專案

開啟 Android Studio。按一下「檔案」>新增 >匯入專案並選取 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 套件和 SessionsEats 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.

現在電腦上有了執行的完整本機開發環境!請讓這個指令在程式碼研究室的其他部分保持執行,您的 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 驗證模擬器

完成登入程序後,應用程式主畫面會顯示:

de06424023ffb4b9.png

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

6. 將資料寫入 Firestore

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

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

為了示範,我們會在點選「Add Random Items」時,在應用程式中加入新功能,隨機建立 10 家餐廳按鈕。開啟 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 資料類別建立文件,並使用 Kotlin 資料類別建立每份餐廳的說明文件。
  • add() 方法會使用自動產生的 ID,將文件新增至含有自動產生的 ID 的集合,因此我們不需要為每個餐廳指定專屬 ID。

現在,請再次執行應用程式,並按一下「Add Random Items」溢位選單的右上角按鈕,叫用您剛才編寫的程式碼:

95691e9b71ba55e3.png

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

Firebase 驗證模擬器

這項資料完全來自您的電腦。事實上,您的真實專案甚至還沒有 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

現在,請在瀏覽器中返回 Android Emulator 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. 保護您的資料

目前為止,我們尚未考慮這個應用程式的安全性。Google 如何得知使用者只能讀取及寫入正確的資料?由名為「安全性規則」的設定檔保護 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,可以先從以下這些優質位置著手:

本程式碼研究室中的餐廳應用程式是以「友善美食」為基礎應用程式範例您可以在這裡瀏覽該應用程式的原始碼。

選用:部署至正式環境

這個應用程式目前只使用 Firebase Emulator 套件。如要瞭解如何將這個應用程式部署至實際的 Firebase 專案,請繼續下一個步驟。

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

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

Firebase 驗證

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

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

sign-in-providers.png

Firestore

建立資料庫

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

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

部署規則

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

$ firebase deploy --only firestore:rules

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

部署索引

SessionsEats 應用程式提供複雜的排序和篩選功能,需要使用多個自訂化合物索引。您可在 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,然後再次執行應用程式。

請注意,您可能需要登出應用程式並再次登入,才能正確連線至正式版。