Google は、黒人コミュニティのための人種的公平の促進に取り組んでいます。詳細をご覧ください。
このページは Cloud Translation API によって翻訳されました。
Switch to English

Cloud Firestore Android Codelab

目標

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

  • AndroidアプリからFirestoreへのデータの読み取りと書き込み
  • Firestoreデータの変更をリアルタイムで聞く
  • Firebase認証とセキュリティルールを使用してFirestoreデータを保護する
  • 複雑なFirestoreクエリを作成する

前提条件

このコードラボを開始する前に、次のことを確認してください。

  • Android Studio4.0以降
  • Androidエミュレーター
  • Node.jsバージョン10以降
  • Javaバージョン8以降
  1. GoogleアカウントでFirebaseコンソールにログインします。
  2. Firebaseコンソールで、[プロジェクトの追加]クリックします。
  3. 以下のスクリーンキャプチャに示すように、Firebaseプロジェクトの名前(「FriendlyEats」など)を入力し、[続行]をクリックします。

9d2f625aebcab6af.png

  1. このコードラボの目的上、選択は重要ではないため、GoogleAnalyticsを有効にするように求められる場合があります。
  2. 1分ほどすると、Firebaseプロジェクトの準備が整います。 [続行]をクリックします。

コードをダウンロードする

次のコマンドを実行して、このコードラボのサンプルコードのクローンを作成します。これにより、お使いのマシンにfriendlyeats-androidというフォルダーが作成されます。

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

マシンにgitがない場合は、GitHubから直接コードをダウンロードすることもできます。

プロジェクトをAndroidStudioにインポートします。コンパイルエラーが発生したり、 google-services.jsonファイルが見つからないという警告が表示されたりする可能性があります。これは次のセクションで修正します。

Firebase構成を追加する

  1. Firebaseコンソールで、左側のナビゲーションで[プロジェクトの概要]を選択します。 Androidボタンをクリックしてプラットフォームを選択します。パッケージ名の入力を求められたら、 com.google.firebase.example.fireeats使用します

73d151ed16016421.png

  1. [アプリの登録]をクリックし、指示に従ってgoogle-services.jsonファイルをダウンロードして、サンプルコードのapp/フォルダーに移動します。次に、[次へ]をクリックします。

このコードラボでは、 Firebase Emulator Suiteを使用して、CloudFirestoreやその他のFirebaseサービスをローカルでエミュレートします。これにより、アプリを構築するための安全で高速な無料のローカル開発環境が提供されます。

FirebaseCLIをインストールします

まず、 FirebaseCLIをインストールする必要があります。これを行う最も簡単な方法は、 npmを使用することです。

npm install -g firebase-tools

npmがない場合、またはエラーが発生した場合は、インストール手順を読んで、プラットフォーム用のスタンドアロンバイナリを入手してください。

CLIをインストールしたら、 firebase --versionを実行すると、バージョン9.0.0以降が報告されます。

$ firebase --version
9.0.0

ログインする

firebase loginを実行して、CLIをGoogleアカウントに接続します。これにより、新しいブラウザウィンドウが開き、ログインプロセスが完了します。以前にFirebaseプロジェクトを作成したときに使用したものと同じアカウントを選択してください。

friendlyeats-androidフォルダー内からfirebase use --addを実行して、ローカルプロジェクトをFirebaseプロジェクトに接続します。プロンプトに従って、前に作成したプロジェクトを選択し、エイリアスを選択するように求められた場合は、 default入力しdefault

次に、Firebase EmulatorSuiteとFriendlyEatsAndroidアプリを初めて実行します。

エミュレーターを実行します

ターミナルで、 friendlyeats-androidディレクトリ内からfirebase emulators:start実行し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アプリはエミュレーターに接続する必要があります。

アプリをエミュレーターに接続します

FirebaseUtil.javaでファイルFirebaseUtil.javaを開きます。このファイルには、FirebaseSDKをマシンで実行されているローカルエミュレーターに接続するためのロジックが含まれています。

ファイルの先頭で、次の行を調べます。

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

BuildConfigを使用して、アプリがdebugモードで実行されている場合にのみエミュレーターに接続するようにしています。アプリをreleaseモードでコンパイルすると、この条件はfalseになります。

次に、 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)メソッドを使用してFirebaseSDKをローカルのFirestoreエミュレーターに接続していることがuseEmulator(host, port)ます。アプリを通じて、我々は、使用するFirebaseUtil.getFirestore()のこのインスタンスにアクセスするためにFirebaseFirestore我々は常にFirestoreに接続するエミュレータで実行しているときにしていることを確認しているので、 debugモード。

アプリを実行する

google-services.jsonファイルを適切に追加すると、プロジェクトがコンパイルされます。 Android Studioで、[ビルド]> [プロジェクトの再ビルド]をクリックし、エラーが残っていないことを確認します。

AndroidStudioでAndroidエミュレーターでアプリを実行します。最初に、「サインイン」画面が表示されます。任意のメールアドレスとパスワードを使用してアプリにサインインできます。このサインインプロセスはFirebaseAuthenticationエミュレーターに接続しているため、実際の認証情報は送信されていません。

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

Firebase Auth Emulator

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

de06424023ffb4b9.png

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

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

アプリの主なモデルオブジェクトはレストランです( model/Restaurant.java参照)。 Firestoreデータは、ドキュメント、コレクション、およびサブコレクションに分割されます。各レストランは、 "restaurants"と呼ばれるトップレベルのコレクションにドキュメントとして保存され"restaurants" 。 Firestoreデータモデルの詳細については、内のドキュメントやコレクションについて読んドキュメント

デモンストレーションの目的で、オーバーフローメニューの[ランダムアイテムの追加]ボタンをクリックすると、アプリに10個のランダムレストランを作成する機能を追加します。 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を使用して作成できます。
  • add()メソッドは、自動生成されたIDを使用してドキュメントをコレクションに追加するため、レストランごとに一意のIDを指定する必要はありませんでした。

次に、アプリを再度実行し、オーバーフローメニューの[ランダムアイテムの追加]ボタンをクリックして、作成したコードを呼び出します。

95691e9b71ba55e3.png

次に、Webブラウザーでhttp:// localhost:4000に移動して、エミュレーターUIを開きます。次に、[ Firestore ]タブをクリックすると、追加したデータが表示されます。

Firebase Auth Emulator

このデータは、マシンに対して100%ローカルです。実際、実際のプロジェクトにはまだFirestoreデータベースが含まれていません。これは、結果なしにこのデータを変更および削除して実験しても安全であることを意味します。

おめでとうございます。Firestoreにデータを書き込んだだけです。次のステップでは、このデータをアプリに表示する方法を学習します。

このステップでは、Firestoreからデータを取得してアプリに表示する方法を学習します。 Firestoreからデータを読み取るための最初のステップは、 Queryを作成することです。 onCreate()メソッドを変更し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を実装させ、Firestoreクエリの更新を受信できるようにonEvent関数を定義しましょう。

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();
    }

  // ...
}

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

    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からこれらの新しいメソッドを呼び出し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に戻り、レストラン名の1つを編集します。あなたはそれがアプリでほぼ瞬時に変化するのを見るはずです!

このアプリは現在、コレクション全体でトップクラスのレストランを表示していますが、実際のレストランアプリでは、ユーザーはデータを並べ替えてフィルタリングする必要があります。たとえば、アプリは「フィラデルフィアのトップシーフードレストラン」または「最も安価なピザ」を表示できる必要があります。

アプリの上部にある白いバーをクリックすると、フィルターダイアログが表示されます。このセクションでは、Firestoreクエリを使用してこのダイアログを機能させます。

67898572a35672a5.png

レッツ・編集onFilter()のメソッドMainActivity.java 。このメソッドは、フィルターダイアログの出力をキャプチャするために作成したヘルパーオブジェクトであるFiltersオブジェクトを受け入れます。このメソッドを変更して、フィルターからクエリを作成します。

    @Override
    public void onFilter(Filters filters) {
        // Construct query 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);
    }

上記のスニペットでは、構築するQuery取り付けることによって、オブジェクトをwhereorderBy与えられたフィルタに一致するように句を。

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

7a67a8a400c80c50.png

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

a670188398c3c59.png

ここまで進んだら、Firestoreで完全に機能するレストランおすすめの表示アプリを作成しました。レストランをリアルタイムで並べ替えてフィルタリングできるようになりました。次のいくつかのセクションでは、レビューとセキュリティをアプリに投稿します。

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

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

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

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

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

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

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

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

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

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

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

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

78fa16cdf8ef435a.png

[送信]をクリックすると、トランザクションが開始されます。トランザクションが完了すると、レビューが下に表示され、レストランのレビュー数が更新されます。

f9e670f40bd615b0.png

おめでとうございます!これで、CloudFirestore上に構築されたソーシャルのローカルモバイルレストランレビューアプリができました。最近はとても人気があるそうです。

これまでのところ、このアプリケーションのセキュリティについては考慮していません。ユーザーが正しい自分のデータしか読み書きできないことをどうやって知ることができますか? 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がサインインしたユーザーと一致する場合にのみ作成できます。これにより、なりすましが防止されます。

セキュリティルールの詳細については、ドキュメントにアクセスしてください。

これで、Firestore上にフル機能のアプリが作成されました。次のような最も重要なFirestoreの機能について学びました。

  • ドキュメントとコレクション
  • データの読み取りと書き込み
  • クエリによる並べ替えとフィルタリング
  • サブコレクション
  • トランザクション

もっと詳しく知る

Firestoreについて学び続けるために、ここに始めるためのいくつかの良い場所があります:

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

オプション:本番環境にデプロイする

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

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

Firebase認証

Firebaseコンソールで、[認証]セクションに移動し、 [サインインプロバイダー]タブに移動します

電子メールのサインイン方法を有効にします。

334ef7f6ff4da4ce.png

ファイヤーストア

データベースを作成する

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

  1. セキュリティルールがロックモードで開始することを選択することについてプロンプトが表示されたら、すぐにそれらのルールを更新します。
  2. アプリに使用するデータベースの場所を選択します。データベースの場所の選択は永続的な決定であり、それを変更するには、新しいプロジェクトを作成する必要があることに注意してください。プロジェクトの場所の選択の詳細については、ドキュメントを参照してください。

ルールの展開

以前に作成したセキュリティルールを展開するには、codelabディレクトリで次のコマンドを実行します。

$ firebase deploy --only firestore:rules

これにより、 firestore.rulesのコンテンツがプロジェクトにデプロイされます。これは、コンソールの[ルール]タブに移動して確認できます。

インデックスの展開

FriendlyEatsアプリには複雑な並べ替えとフィルタリングがあり、多数のカスタム複合インデックスが必要です。これらはFirebaseコンソールで手動で作成できますが、 firestore.indexes.jsonファイルに定義を記述し、FirebaseCLIを使用してデプロイする方が簡単です。

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クラスでは、デバッグモードのときにエミュレーターに接続するようにFirebaseSDKを構成しました。

public class FirebaseUtil {

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

    // ...
}

実際のFirebaseプロジェクトでアプリをテストする場合は、次のいずれかを実行できます。

  1. リリースモードでアプリをビルドし、デバイスで実行します。
  2. 一時的にsUseEmulatorsfalse変更し、アプリを再実行します。

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