Cloud Firestore Android 代碼實驗室

一、概述

目標

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

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

先決條件

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

  • Android Studio 4.0或更高版本
  • 具有 API 19或更高版本的 Android 模擬器
  • Node.js 版本10或更高版本
  • Java 版本8或更高版本

2. 創建一個 Firebase 項目

  1. 使用您的 Google 帳戶登錄Firebase 控制台
  2. Firebase 控制台中,單擊添加項目
  3. 如下面的屏幕截圖所示,輸入您的 Firebase 項目的名稱(例如“Friendly Eats”),然後點擊Continue

9d2f625aebcab6af.png

  1. 出於此 Codelab 的目的,您可能會被要求啟用 Google Analytics,您的選擇並不重要。
  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/文件夾中。然後單擊下一步

導入項目

打開安卓工作室。單擊文件>新建>導入項目並選擇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 和 FriendlyEats 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 中打開文件FirebaseUtil.java 。此文件包含將 Firebase SDK 連接到您機器上運行的本地模擬器的邏輯。

在文件的頂部,檢查這一行:

    /** Use emulators only in debug builds **/
    private static final boolean sUseEmulators = BuildConfig.DEBUG;

我們使用BuildConfig來確保我們僅在我們的應用程序在debug模式下運行時連接到模擬器。當我們在release模式下編譯應用程序時,這個條件將是錯誤的。

現在看一下getFirestore()方法:

    public static FirebaseFirestore getFirestore() {
        if (FIRESTORE == null) {
            FIRESTORE = FirebaseFirestore.getInstance();

            // Connect to the Cloud Firestore emulator when appropriate. The host '10.0.2.2' is a
            // special IP address to let the Android emulator connect to 'localhost'.
            if (sUseEmulators) {
                FIRESTORE.useEmulator("10.0.2.2", 8080);
            }
        }

        return FIRESTORE;
    }

我們可以看到它正在使用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打開 Emulators UI。然後單擊身份驗證選項卡,您應該會看到剛剛創建的帳戶:

Firebase 身份驗證模擬器

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

de06424023ffb4b9.png

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

6. 將數據寫入 Firestore

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

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

出於演示目的,當我們單擊溢出菜單中的“添加隨機項目”按鈕時,我們將在應用程序中添加創建十個隨機餐廳的功能。打開文件MainActivity.java並將onAddItemsClicked()方法中的內容替換為:

    private void onAddItemsClicked() {
        // Get a reference to the restaurants collection
        CollectionReference restaurants = mFirestore.collection("restaurants");

        for (int i = 0; i < 10; i++) {
            // Get a random Restaurant POJO
            Restaurant restaurant = RestaurantUtil.getRandom(this);

            // Add a new document to the restaurants collection
            restaurants.add(restaurant);
        }
    }

關於上面的代碼,有一些重要的事情需要注意:

  • 我們首先參考了"restaurants"系列。添加文檔時會隱式創建集合,因此無需在寫入數據之前創建集合。
  • 可以使用 POJO(普通舊 Java 對象)創建文檔,我們使用它來創建每個餐廳文檔。
  • add()方法將文檔添加到具有自動生成 ID 的集合中,因此我們不需要為每個餐廳指定唯一 ID。

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

95691e9b71ba55e3.png

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

Firebase 身份驗證模擬器

此數據 100% 位於您的機器本地。事實上,您的真實項目甚至還沒有包含 Firestore 數據庫!這意味著可以安全地嘗試修改和刪除這些數據而不會產生任何後果。

恭喜,您剛剛將數據寫入 Firestore!在下一步中,我們將學習如何在應用程序中顯示這些數據。

7. 顯示來自 Firestore 的數據

在這一步中,我們將學習如何從 Firestore 中檢索數據並將其顯示在我們的應用程序中。從 Firestore 讀取數據的第一步是創建一個Query 。打開文件MainActivity.java並將以下代碼添加到onCreate()方法的開頭:

        mFirestore = FirebaseUtil.getFirestore();

        // Get the 50 highest rated restaurants
        mQuery = mFirestore.collection("restaurants")
                .orderBy("avgRating", Query.Direction.DESCENDING)
                .limit(LIMIT);

現在我們要監聽查詢,以便我們獲取所有匹配的文檔並實時通知未來的更新。因為我們的最終目標是將此數據綁定到RecyclerView ,所以我們需要創建一個RecyclerView.Adapter類來監聽數據。

打開已經部分實現的FirestoreAdapter類。首先,讓我們讓適配器實現EventListener並定義onEvent函數,以便它可以接收對 Firestore 查詢的更新:

public abstract class FirestoreAdapter<VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH>
        implements EventListener<QuerySnapshot> { // Add this "implements"

    // ...

    // Add this method
    @Override
    public void onEvent(QuerySnapshot documentSnapshots,
                        FirebaseFirestoreException e) {

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

        // Dispatch the event
        for (DocumentChange change : documentSnapshots.getDocumentChanges()) {
            // Snapshot of the changed document
            DocumentSnapshot snapshot = change.getDocument();

            switch (change.getType()) {
                case ADDED:
                    // TODO: handle document added
                    break;
                case MODIFIED:
                    // TODO: handle document modified
                    break;
                case REMOVED:
                    // TODO: handle document removed
                    break;
            }
        }

        onDataChanged();
    }

  // ...
}

在初始加載時,偵聽器將為每個新文檔接收一個ADDED事件。隨著查詢的結果集隨時間變化,偵聽器將收到更多包含更改的事件。現在讓我們完成監聽器的實現。首先添加三個新方法: onDocumentAddedonDocumentModifiedonDocumentRemoved

    protected void onDocumentAdded(DocumentChange change) {
        mSnapshots.add(change.getNewIndex(), change.getDocument());
        notifyItemInserted(change.getNewIndex());
    }

    protected void onDocumentModified(DocumentChange change) {
        if (change.getOldIndex() == change.getNewIndex()) {
            // Item changed but remained in same position
            mSnapshots.set(change.getOldIndex(), change.getDocument());
            notifyItemChanged(change.getOldIndex());
        } else {
            // Item changed and changed position
            mSnapshots.remove(change.getOldIndex());
            mSnapshots.add(change.getNewIndex(), change.getDocument());
            notifyItemMoved(change.getOldIndex(), change.getNewIndex());
        }
    }

    protected void onDocumentRemoved(DocumentChange change) {
        mSnapshots.remove(change.getOldIndex());
        notifyItemRemoved(change.getOldIndex());
    }

然後從onEvent調用這些新方法:

    @Override
    public void onEvent(QuerySnapshot documentSnapshots,
                        FirebaseFirestoreException e) {

        // ...

        // Dispatch the event
        for (DocumentChange change : documentSnapshots.getDocumentChanges()) {
            // Snapshot of the changed document
            DocumentSnapshot snapshot = change.getDocument();

            switch (change.getType()) {
                case ADDED:
                    onDocumentAdded(change); // Add this line
                    break;
                case MODIFIED:
                    onDocumentModified(change); // Add this line
                    break;
                case REMOVED:
                    onDocumentRemoved(change); // Add this line
                    break;
            }
        }

        onDataChanged();
    }

最後實現startListening()方法來附加監聽器:

    public void startListening() {
        if (mQuery != null && mRegistration == null) {
            mRegistration = mQuery.addSnapshotListener(this);
        }
    }

現在該應用程序已完全配置為從 Firestore 讀取數據。再次運行應用程序,您應該會看到您在上一步中添加的餐廳:

9e45f40faefce5d0.png

現在返回瀏覽器中的模擬器 UI 並編輯其中一個餐廳名稱。您應該幾乎立即在應用程序中看到它發生變化!

8. 排序和過濾數據

該應用程序當前顯示整個集合中評分最高的餐廳,但在真正的餐廳應用程序中,用戶可能希望對數據進行排序和過濾。例如,該應用程序應該能夠顯示“費城頂級海鮮餐廳”或“最便宜的披薩”。

單擊應用程序頂部的白條會彈出一個過濾器對話框。在本節中,我們將使用 Firestore 查詢來使此對話框正常工作:

67898572a35672a5.png

讓我們編輯MainActivity.javaonFilter()方法。此方法接受一個Filters對象,它是我們創建的一個幫助對象,用於捕獲過濾器對話框的輸出。我們將更改此方法以從過濾器構造查詢:

    @Override
    public void onFilter(Filters filters) {
        // Construct basic query
        Query query = mFirestore.collection("restaurants");

        // Category (equality filter)
        if (filters.hasCategory()) {
            query = query.whereEqualTo("category", filters.getCategory());
        }

        // City (equality filter)
        if (filters.hasCity()) {
            query = query.whereEqualTo("city", filters.getCity());
        }

        // Price (equality filter)
        if (filters.hasPrice()) {
            query = query.whereEqualTo("price", filters.getPrice());
        }

        // Sort by (orderBy with direction)
        if (filters.hasSortBy()) {
            query = query.orderBy(filters.getSortBy(), filters.getSortDirection());
        }

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

        // Update the query
        mQuery = query;
        mAdapter.setQuery(query);

        // Set header
        mCurrentSearchView.setText(Html.fromHtml(filters.getSearchDescription(this)));
        mCurrentSortByView.setText(filters.getOrderDescription(this));

        // Save filters
        mViewModel.setFilters(filters);
    }

在上面的代碼片段中,我們通過附加whereorderBy子句來匹配給定的過濾器來構建一個Query對象。

再次運行應用程序並選擇以下過濾器以顯示最受歡迎的低價餐廳:

7a67a8a400c80c50.png

您現在應該會看到僅包含低價選項的經過篩選的餐廳列表:

a670188398c3c59.png

如果您已經做到了這一點,那麼您現在已經在 Firestore 上構建了一個功能齊全的餐廳推薦查看應用程序!您現在可以實時對餐廳進行排序和過濾。在接下來的幾節中,我們將為餐廳添加評論並向應用程序添加安全規則。

9. 在子集合中組織數據

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

集合和子集合

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

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

CollectionReference subRef = mFirestore.collection("restaurants")
        .document("abc123")
        .collection("ratings");

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

在事務中寫入數據

Rating添加到適當的子集合只需要調用.add() ,但我們還需要更新Restaurant對象的平均評分和評分數以反映新數據。如果我們使用單獨的操作來進行這兩個更改,則會出現許多可能導致數據陳舊或不正確的競爭條件。

為確保正確添加評分,我們將使用交易為餐廳添加評分。此事務將執行一些操作:

  • 閱讀餐廳的當前評分併計算新評分
  • 將評分添加到子集合
  • 更新餐廳的平均評分和評分數

打開RestaurantDetailActivity.java並實現addRating函數:

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

        // In a transaction, add the new rating and update the aggregate totals
        return mFirestore.runTransaction(new Transaction.Function<Void>() {
            @Override
            public Void apply(Transaction transaction)
                    throws FirebaseFirestoreException {

                Restaurant restaurant = transaction.get(restaurantRef)
                        .toObject(Restaurant.class);

                // Compute new number of ratings
                int newNumRatings = restaurant.getNumRatings() + 1;

                // Compute new average rating
                double oldRatingTotal = restaurant.getAvgRating() *
                        restaurant.getNumRatings();
                double newAvgRating = (oldRatingTotal + rating.getRating()) /
                        newNumRatings;

                // Set new restaurant info
                restaurant.setNumRatings(newNumRatings);
                restaurant.setAvgRating(newAvgRating);

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

                return null;
            }
        });
    }

addRating()函數返回一個代表整個事務的Task 。在onRating()函數中,監聽器被添加到任務中以響應事務的結果。

現在再次運行應用程序並單擊其中一家餐廳,這將顯示餐廳詳細信息屏幕。單擊+按鈕開始添加評論。通過選擇一些星星並輸入一些文本來添加評論。

78fa16cdf8ef435a.png

點擊提交將啟動交易。交易完成後,您將在下方看到您的評論以及餐廳評論計數的更新:

f9e670f40bd615b0.png

恭喜!您現在擁有基於 Cloud Firestore 構建的社交、本地、移動餐廳評論應用。我聽說這些天很受歡迎。

10. 保護您的數據

到目前為止,我們還沒有考慮過這個應用程序的安全性。我們怎麼知道用戶只能讀寫正確的自己的數據? Firestore 數據庫由名為Security Rules的配置文件保護。

打開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 。導航到登錄方法選項卡並從本地提供者中選擇電子郵件/密碼選項。

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

登錄-providers.png

火庫

創建數據庫

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

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

部署規則

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

$ 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 控制台中監控進度。

配置應用程序

FirebaseUtil類中,我們將 Firebase SDK 配置為在調試模式下連接到模擬器:

public class FirebaseUtil {

    /** Use emulators only in debug builds **/
    private static final boolean sUseEmulators = BuildConfig.DEBUG;

    // ...
}

如果您想使用真實的 Firebase 項目測試您的應用,您可以:

  1. 在發布模式下構建應用程序並在設備上運行它。
  2. 暫時將sUseEmulators更改為false並再次運行應用程序。

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