Cloud Firestore Android コードラボ

1。概要

目標

このコードラボでは、Cloud Firestore を利用した Android 上でレストランおすすめアプリを構築します。次の方法を学習します:

  • Android アプリから Firestore へのデータの読み取りと書き込み
  • Firestore データの変更をリアルタイムでリッスンする
  • Firebase Authentication とセキュリティ ルールを使用して 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. 1 分ほどすると、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を開きます。 [ファイル] > [新規] > [プロジェクトのインポート]をクリックし、 friendeats-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 エミュレータ スイートと 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.

これで、マシン上で完全なローカル開発環境が実行されました。コードラボの残りの部分では必ずこのコマンドを実行したままにしてください。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 Authentication エミュレータに接続しているため、実際の認証情報は送信されません。

Web ブラウザでhttp://localhost:4000に移動して、エミュレータ UI を開きます。次に、 「認証」タブをクリックすると、作成したばかりのアカウントが表示されるはずです。

Firebase 認証エミュレータ

サインイン プロセスが完了すると、アプリのホーム画面が表示されます。

de06424023ffb4b9.png

すぐに、ホーム画面に表示するデータを追加します。

6. Firestoreにデータを書き込む

このセクションでは、現在空のホーム画面にデータを入力できるように、Firestore にデータを書き込みます。

私たちのアプリの主要なモデル オブジェクトはレストランです ( model/Restaurant.ktを参照)。 Firestore データは、ドキュメント、コレクション、サブコレクションに分割されます。各レストランを"restaurants"という最上位のコレクションにドキュメントとして保存します。 Firestore データ モデルの詳細については、ドキュメントのドキュメントとコレクションについてお読みください。

デモンストレーションの目的で、オーバーフロー メニューの [ランダム アイテムを追加] ボタンをクリックしたときに 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 データ クラスを使用して作成できます。これは各レストラン ドキュメントの作成に使用されます。
  • 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を実装させ、Firestore クエリの更新を受信できるようにonEvent関数を定義しましょう。

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()
    }
    
    // ...
}

初期ロード時に、リスナーは新しいドキュメントごとに 1 つのADDEDイベントを受け取ります。クエリの結果セットが時間の経過とともに変化するため、リスナーは変更を含むさらに多くのイベントを受信します。それでは、リスナーの実装を完了しましょう。まず、3 つの新しいメソッド、 onDocumentAddedonDocumentModified 、およびonDocumentRemovedを追加します。

    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 に戻り、レストラン名の 1 つを編集します。ほぼ即座にアプリ内で変更が表示されるはずです。

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
    }

上記のスニペットでは、指定されたフィルターに一致するwhere句とorderBy句を接続してQueryオブジェクトを構築します。

アプリを再度実行し、次のフィルターを選択して、最も人気のある低価格レストランを表示します。

7a67a8a400c80c50.png

低価格のオプションのみを含むフィルタリングされたレストランのリストが表示されます。

a670188398c3c59.png

ここまで進めば、完全に機能するレストランのおすすめ表示アプリが Firestore 上に構築されたことになります。リアルタイムでレストランを並べ替えたりフィルタリングしたりできるようになりました。次のいくつかのセクションでは、レストランにレビューを追加し、アプリにセキュリティ ルールを追加します。

9. サブコレクション内のデータを整理する

このセクションでは、ユーザーがお気に入りの (またはあまり好きではない) レストランをレビューできるように、アプリに評価を追加します。

コレクションとサブコレクション

これまでのところ、すべてのレストラン データを「レストラン」と呼ばれる最上位のコレクションに保存してきました。ユーザーがレストランを評価するときは、レストランに新しいRatingオブジェクトを追加します。このタスクでは、サブコレクションを使用します。サブコレクションは、ドキュメントに添付されたコレクションと考えることができます。したがって、各レストランのドキュメントには、評価ドキュメントが満載された評価サブコレクションが含まれます。サブコレクションは、ドキュメントを肥大化させたり、複雑なクエリを要求したりすることなく、データを整理するのに役立ちます。

サブコレクションにアクセスするには、親ドキュメントで.collection()を呼び出します。

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

最上位のコレクションと同様に、サブコレクションにアクセスしてクエリを実行できます。サイズの制限やパフォーマンスの変更はありません。 Firestore データ モデルの詳細については、こちらをご覧ください。

トランザクションでのデータの書き込み

適切なサブコレクションにRatingを追加するには、 .add()を呼び出すだけですが、新しいデータを反映するためにRestaurantオブジェクトの平均評価と評価数を更新する必要もあります。これら 2 つの変更を別々の操作で行うと、多数の競合状態が発生し、データが古くなったり、不正確になったりする可能性があります。

評価が適切に追加されることを確認するために、トランザクションを使用してレストランに評価を追加します。このトランザクションはいくつかのアクションを実行します。

  • レストランの現在の評価を読み取り、新しい評価を計算します
  • 評価をサブコレクションに追加します
  • レストランの平均評価と評価数を更新します

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()関数では、トランザクションの結果に応答するリスナーがタスクに追加されます。

次に、アプリを再度実行し、レストランの 1 つをクリックすると、レストランの詳細画面が表示されます。 +ボタンをクリックしてレビューの追加を開始します。星の数を選択し、テキストを入力してレビューを追加します。

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 について学び続けるために、始めるのに適した場所をいくつか紹介します。

このコードラボのレストラン アプリは、「Friendly Eats」サンプル アプリケーションに基づいています。そのアプリのソース コードはここで参照できます。

オプション: 本番環境へのデプロイ

これまでのところ、このアプリは Firebase Emulator Suite のみを使用しています。このアプリを実際の Firebase プロジェクトにデプロイする方法を知りたい場合は、次のステップに進んでください。

12. (オプション) アプリをデプロイします

これまでのところ、このアプリは完全にローカルであり、すべてのデータは Firebase エミュレータ スイートに含まれています。このセクションでは、このアプリが本番環境で動作するように Firebase プロジェクトを構成する方法を学習します。

Firebase認証

Firebase コンソールで[認証]セクションに移動し、 [開始する]をクリックします。 [サインイン方法]タブに移動し、 [ネイティブ プロバイダー]から[電子メール/パスワード]オプションを選択します。

[電子メール/パスワード]サインイン方法を有効にして、 [保存]をクリックします。

サインインプロバイダー.png

ファイヤーストア

データベースの作成

コンソールのFirestore データベースセクションに移動し、 [データベースの作成]をクリックします。

  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 コンソールで監視できます。

アプリを設定する

util/FirestoreInitializer.ktファイルとutil/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に置き換えて、アプリを再度実行します。

実稼働環境に適切に接続するには、アプリからサインアウトして再度サインインする必要がある場合があることに注意してください。