Cloud Firestore Android Codelab

1. 概要

目標

この Codelab では、Cloud Firestore をベースとした Android のレストランおすすめアプリを構築します。ここでは以下について学びます。

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

前提条件

この Codelab を開始する前に、次のものがあることを確認してください。

  • Android Studio Flamingo 以降
  • API 19 以降を搭載した Android Emulator
  • Node.js バージョン 16 以降
  • Java バージョン 17 以降

2. Firebase プロジェクトを作成する

  1. Google アカウントで Firebase コンソールにログインします。
  2. Firebase コンソールで [プロジェクトを追加] をクリックします。
  3. 下のスクリーンショットに示すように、Firebase プロジェクトの名前(「Friendly Eats」など)を入力し、[続行] をクリックします。

9d2f625aebcab6af.png

  1. Google アナリティクスを有効にするよう求められる場合があります。このコードラボでは、どちらを選択してもかまいません。
  2. 約 1 分後に 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 を開きます。[File] > [New] > [Import Project] をクリックし、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 Emulator を起動します。次のようなログが表示されます。

$ 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.kt ファイルと util/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 Emulator でアプリを実行します。最初に [ログイン] 画面が表示されます。任意のメールアドレスとパスワードを使用してアプリにログインできます。このログイン プロセスは Firebase Authentication エミュレータに接続しているため、実際の認証情報は送信されません。

ウェブブラウザで http://localhost:4000 に移動して、Emulators UI を開きます。[Authentication] タブをクリックすると、先ほど作成したアカウントが表示されます。

Firebase Auth エミュレータ

ログイン手続きが完了すると、アプリのホーム画面が表示されます。

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 を指定する必要はありません。

アプリをもう一度実行し、右上のオーバーフロー メニューにある [Add Random Items] ボタンをクリックして、先ほど作成したコードを呼び出します。

95691e9b71ba55e3.png

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

Firebase Auth エミュレータ

このデータは、マシンに完全にローカルです。実際のプロジェクトには、まだ 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 イベントを受信します。クエリの結果セットが時間とともに変化すると、リスナーは変更を含むイベントをさらに受信します。リスナーの実装を完了しましょう。まず、onDocumentAddedonDocumentModifiedonDocumentRemoved の 3 つの新しいメソッドを追加します。

    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     } 

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

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

7a67a8a400c80c50.png

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

a670188398c3c59.png

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

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

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

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

これまで、すべてのレストランデータは「restaurants」というトップレベルのコレクションに保存してきました。ユーザーがレストランに評価を付けると、レストランに新しい Rating オブジェクトが追加されます。このタスクでは、サブコレクションを使用します。サブコレクションは、ドキュメントに関連付けられたコレクションと考えることができます。各レストラン ドキュメントには、評価ドキュメントがすべて含まれる ratings サブコレクションがあります。サブコレクションを使用すると、ドキュメントの肥大化や複雑なクエリの必要性を回避しながらデータを整理できます。

サブコレクションにアクセスするには、親ドキュメントで .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() 関数では、トランザクションの結果に応答するリスナーがタスクに追加されます。

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

78fa16cdf8ef435a.png

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

f9e670f40bd615b0.png

お疲れさまでした。これで、Cloud Firestore 上に構築されたソーシャルでローカルなモバイル レストラン レビューアプリが完成しました。最近は人気があるようですね。

10. データを保護する

これまで、このアプリケーションのセキュリティについては考慮していません。ユーザーが自分の正しいデータのみを読み取り、書き込みできることをどのように確認すればよいですか?Firestore データベースは、セキュリティ ルールという構成ファイルによって保護されます。

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 Authentication

Firebase コンソールで [Authentication] セクションに移動し、[使ってみる] をクリックします。[ログイン方法] タブに移動し、[ネイティブ プロバイダ] から [メール/パスワード] オプションを選択します。

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

sign-in-providers.png

Firestore

データベースの作成

コンソールの [Firestore 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 コンソールで進行状況をモニタリングできます。

アプリを構成する

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 に置き換えて、アプリをもう一度実行します。

本番環境に正しく接続するには、アプリからログアウトして再度ログインすることが必要になる場合があります。