Google is committed to advancing racial equity for Black communities. See how.
Эта страница переведена с помощью Cloud Translation API.
Switch to English

Cloud Firestore Android Codelab

Цели

В этой лаборатории вы создадите приложение для рекомендации ресторанов на Android при поддержке Cloud Firestore. Вы научитесь:

  • Чтение и запись данных в Firestore из приложения Android
  • Слушайте изменения в данных Firestore в реальном времени
  • Используйте Firebase Authentication и правила безопасности для защиты данных Firestore
  • Написание сложных запросов Firestore

Предпосылки

Перед запуском этой кодовой лаборатории убедитесь, что у вас есть:

  • Android Studio 4.0 или выше
  • Эмулятор Android
  • Node.js версии 10 или выше
  • Java версии 8 или выше
  1. Войдите в консоль Firebase со своей учетной записью Google.
  2. В консоли Firebase нажмите Добавить проект .
  3. Как показано на снимке экрана ниже, введите имя проекта Firebase (например, «Friendly Eats») и нажмите « Продолжить» .

9d2f625aebcab6af.png

  1. Вас могут попросить включить Google Analytics, для целей этой кодовой таблицы ваш выбор не имеет значения.
  2. Примерно через минуту ваш проект Firebase будет готов. Щелкните Продолжить .

Скачать код

Выполните следующую команду, чтобы клонировать образец кода для этой codelab. На вашем компьютере будет создана папка friendlyeats-android :

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

Если на вашем компьютере нет git, вы также можете загрузить код прямо с GitHub.

Импортируйте проект в Android Studio. Вы, вероятно, увидите некоторые ошибки компиляции или, возможно, предупреждение об отсутствии файла google-services.json . Мы исправим это в следующем разделе.

Добавить конфигурацию Firebase

  1. В консоли Firebase выберите Обзор проекта в левой части навигации. Нажмите кнопку Android , чтобы выбрать платформу. При запросе имени пакета используйте com.google.firebase.example.fireeats

73d151ed16016421.png

  1. Нажмите « Зарегистрировать приложение» и следуйте инструкциям, чтобы загрузить файл google-services.json и переместить его в папку app/ образца кода. Затем нажмите « Далее» .

В этой лаборатории вы будете использовать Firebase Emulator Suite для локальной эмуляции Cloud Firestore и других сервисов Firebase. Это обеспечивает безопасную, быструю и бесплатную локальную среду разработки для создания вашего приложения.

Установите Firebase CLI

Сначала вам нужно будет установить Firebase CLI . Самый простой способ сделать это - использовать npm :

npm install -g firebase-tools

Если у вас нет npm или возникла ошибка, прочтите инструкции по установке, чтобы получить автономный двоичный файл для вашей платформы.

После установки CLI запуск firebase --version должен сообщить о версии 9.0.0 или выше:

$ firebase --version
9.0.0

Авторизоваться

Запустите firebase login в firebase login чтобы подключить интерфейс командной строки к вашей учетной записи Google. Откроется новое окно браузера для завершения процесса входа в систему. Обязательно выберите ту же учетную запись, которую вы использовали при создании проекта Firebase ранее.

Из папки friendlyeats-android запустите firebase use --add чтобы подключить локальный проект к проекту Firebase. Следуйте инструкциям по выбору проекта, который вы создали ранее, и если вас попросят выбрать псевдоним, введите default .

Пришло время впервые запустить Firebase Emulator Suite и Android-приложение FriendlyEats.

Запускаем эмуляторы

В вашем терминале из каталога friendlyeats-android запустите firebase emulators:start Firebase 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 в Android Studio. Этот файл содержит логику для подключения 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 поэтому мы уверены, что всегда подключаемся к эмулятору Firestore при работе в режиме debug .

Запустите приложение

Если вы правильно добавили файл google-services.json , проект должен скомпилироваться. В Android Studio нажмите « Сборка» > « Перестроить проект» и убедитесь, что ошибок нет.

В Android Studio Запустите приложение на своем эмуляторе Android. Сначала вам будет представлен экран «Войти». Вы можете использовать любой адрес электронной почты и пароль для входа в приложение. Этот процесс входа подключается к эмулятору аутентификации Firebase, поэтому реальные учетные данные не передаются.

Теперь откройте пользовательский интерфейс эмуляторов, перейдя по адресу http: // localhost: 4000 в своем веб-браузере. Затем щелкните вкладку Authentication, и вы должны увидеть только что созданную учетную запись:

Эмулятор Firebase Auth

После завершения процесса входа вы должны увидеть главный экран приложения:

de06424023ffb4b9.png

Вскоре мы добавим данные для заполнения главного экрана.

В этом разделе мы напишем некоторые данные в 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, которые мы используем для создания каждого документа ресторана.
  • Метод add() добавляет документ в коллекцию с автоматически сгенерированным идентификатором, поэтому нам не нужно указывать уникальный идентификатор для каждого ресторана.

Теперь снова запустите приложение и нажмите кнопку «Добавить случайные элементы» в дополнительном меню, чтобы вызвать только что написанный код:

95691e9b71ba55e3.png

Теперь откройте пользовательский интерфейс эмуляторов, перейдя по адресу http: // localhost: 4000 в своем веб-браузере. Затем щелкните вкладку Firestore, и вы должны увидеть только что добавленные данные:

Эмулятор Firebase Auth

Эти данные на 100% локальны для вашей машины. Фактически, в вашем реальном проекте еще нет даже базы данных Firestore! Это означает, что можно безопасно экспериментировать с изменением и удалением этих данных без последствий.

Поздравляем, вы только что записали данные в Firestore! На следующем шаге мы узнаем, как отображать эти данные в приложении.

На этом этапе мы узнаем, как получить данные из Firestore и отобразить их в нашем приложении. Первым шагом к чтению данных из Firestore является создание Query . Измените метод 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 для каждого нового документа. Поскольку результирующий набор запроса со временем изменяется, слушатель будет получать больше событий, содержащих изменения. Теперь закончим реализацию слушателя. Сначала добавьте три новых метода: onDocumentAdded , onDocumentModified и 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 :

    @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

Теперь вернитесь в пользовательский интерфейс эмулятора в своем браузере и отредактируйте одно из названий ресторанов. Вы должны увидеть это в приложении почти мгновенно!

В настоящее время приложение отображает рестораны с самым высоким рейтингом по всей коллекции, но в реальном ресторанном приложении пользователь может сортировать и фильтровать данные. Например, приложение должно отображать «Лучшие рестораны морепродуктов в Филадельфии» или «Наименее дорогая пицца».

При нажатии на белую полосу в верхней части приложения открывается диалоговое окно фильтров. В этом разделе мы будем использовать запросы 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 , добавляя предложения where и orderBy для соответствия заданным фильтрам.

Снова запустите приложение и выберите следующий фильтр, чтобы показать самые популярные недорогие рестораны:

7a67a8a400c80c50.png

Теперь вы должны увидеть отфильтрованный список ресторанов, содержащий только варианты с низкими ценами:

a670188398c3c59.png

Если вы зашли так далеко, то теперь вы создали полнофункциональное приложение для просмотра рекомендаций ресторана в Firestore! Теперь вы можете сортировать и фильтровать рестораны в режиме реального времени. В следующих нескольких разделах мы публикуем обзоры и безопасность приложения.

В этом разделе мы добавим рейтинги приложению, чтобы пользователи могли оставлять отзывы о своих любимых (или наименее любимых) ресторанах.

Коллекции и субколлекции

Пока мы сохранили все данные о ресторанах в коллекции верхнего уровня под названием «рестораны». Когда пользователь оценивает ресторан, мы хотим добавить в рестораны новый объект « Rating ». Для этой задачи мы будем использовать подколлекцию. Подколлекцию можно рассматривать как коллекцию, прикрепленную к документу. Таким образом, каждый документ о ресторане будет иметь подколлекцию рейтингов, полную рейтинговых документов. Подколлекции помогают организовать данные, не увеличивая наши документы или не требуя сложных запросов.

Чтобы получить доступ к подколлекции, вызовите .collection() в родительском документе:

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

Вы можете получить доступ и запросить вложенную коллекцию, как и в случае с коллекцией верхнего уровня, без ограничений по размеру или изменений производительности. Вы можете узнать больше о модели данных Firestore здесь .

Запись данных в транзакцию

Для добавления Rating в соответствующую подколлекцию требуется только вызов .add() , но нам также необходимо обновить средний рейтинг и количество оценок объекта Restaurant , чтобы отразить новые данные. Если мы используем отдельные операции для внесения этих двух изменений, возникает ряд условий гонки, которые могут привести к устаревшим или неверным данным.

Чтобы обеспечить правильное добавление оценок, мы будем использовать транзакцию для добавления оценок ресторану. Эта транзакция выполнит несколько действий:

  • Прочтите текущий рейтинг ресторана и рассчитайте новый
  • Добавить рейтинг в подколлекцию
  • Обновите средний рейтинг ресторана и количество оценок

Откройте RestaurantDetailActivity.java и addRating функцию 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. Я слышал, в наши дни они очень популярны.

Пока мы не рассматривали безопасность этого приложения. Как мы узнаем, что пользователи могут только читать и записывать правильные собственные данные? Базы данных 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;
      }
    }
  }
}

Эти правила ограничивают доступ, чтобы клиенты могли вносить только безопасные изменения. Например, обновления документа о ресторане могут изменять только рейтинги, но не название или любые другие неизменяемые данные. Рейтинги могут быть созданы только в том случае, если идентификатор пользователя совпадает с зарегистрированным пользователем, что предотвращает подделку.

Чтобы узнать больше о правилах безопасности, посетите документацию .

Вы создали полнофункциональное приложение поверх Firestore. Вы узнали о наиболее важных функциях Firestore, включая:

  • Документы и коллекции
  • Чтение и запись данных
  • Сортировка и фильтрация по запросам
  • Подколлекции
  • Сделки

Узнать больше

Чтобы продолжить изучение Firestore, вот несколько хороших мест для начала:

Приложение «Ресторан» в этой лаборатории кода было основано на примере приложения «Friendly Eats». Вы можете просмотреть исходный код этого приложения здесь .

Необязательно: развертывание в производственной среде

Пока что это приложение использовало только Firebase Emulator Suite. Если вы хотите узнать, как развернуть это приложение в реальном проекте Firebase, перейдите к следующему шагу.

Пока это приложение было полностью локальным, все данные содержались в Firebase Emulator Suite. В этом разделе вы узнаете, как настроить проект Firebase, чтобы это приложение работало в производственной среде.

Проверка подлинности Firebase

В консоли Firebase перейдите в раздел « Аутентификация » и перейдите на вкладку «Поставщики входа» .

Включите метод входа по электронной почте:

334ef7f6ff4da4ce.png

Пожарный магазин

Создать базу данных

Перейдите в раздел консоли Firestore и нажмите Create Database :

  1. Когда будет предложено выбрать правила безопасности для запуска в заблокированном режиме , мы скоро обновим эти правила.
  2. Выберите расположение базы данных, которое вы хотите использовать для своего приложения. Обратите внимание, что выбор местоположения базы данных является постоянным решением, и для его изменения вам потребуется создать новый проект. Дополнительную информацию о выборе места для проекта см. В документации .

Правила развертывания

Чтобы развернуть правила безопасности, которые вы написали ранее, выполните следующую команду в каталоге codelab:

$ firebase deploy --only firestore:rules

Это развернет содержимое firestore.rules в вашем проекте, что вы можете подтвердить, перейдя на вкладку Rules в консоли.

Развернуть индексы

Приложение FriendlyEats имеет сложную сортировку и фильтрацию, для которой требуется ряд настраиваемых составных индексов. Их можно создать вручную в консоли Firebase, но проще записать их определения в файл firestore.indexes.json и развернуть их с помощью интерфейса командной строки Firebase.

Если вы откроете файл 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 и снова запустите приложение.

Обратите внимание , что вам может понадобиться выйти из приложения и снова войти в систему для того , чтобы правильно подключить к производству.