1. Обзор
Цели
В этом практическом занятии вы создадите приложение для рекомендаций ресторанов на Android с использованием Cloud Firestore. Вы научитесь:
- Чтение и запись данных в Firestore из приложения для Android.
- Отслеживайте изменения в данных Firestore в режиме реального времени.
- Используйте правила аутентификации и безопасности Firebase для защиты данных Firestore.
- Пишите сложные запросы к Firestore.
Предварительные требования
Перед началом выполнения этого практического задания убедитесь, что у вас есть:
- Android Studio Flamingo или более поздняя версия
- Эмулятор Android с API 19 или выше.
- Node.js версии 16 или выше
- Версия Java 17 или выше
2. Создайте проект Firebase.
- Войдите в консоль Firebase, используя свою учетную запись Google.
- Нажмите кнопку, чтобы создать новый проект, а затем введите название проекта (например,
FriendlyEats). - Нажмите «Продолжить» .
- Если появится запрос, ознакомьтесь с условиями использования Firebase и примите их, после чего нажмите «Продолжить» .
- (Необязательно) Включите помощь ИИ в консоли Firebase (в Firebase она называется "Gemini").
- Для этого практического занятия вам не понадобится Google Analytics, поэтому отключите эту опцию.
- Нажмите «Создать проект» , дождитесь завершения подготовки проекта, а затем нажмите «Продолжить» .
3. Настройте тестовый проект.
Скачать код
Выполните следующую команду, чтобы клонировать пример кода для этого практического занятия. В результате на вашем компьютере будет создана папка с именем friendlyeats-android :
$ git clone https://github.com/firebase/friendlyeats-android
Если у вас нет Git на компьютере, вы также можете загрузить код напрямую с GitHub.
Добавить конфигурацию Firebase
- В консоли Firebase выберите «Обзор проекта» в левой панели навигации. Нажмите кнопку Android , чтобы выбрать платформу. При запросе имени пакета используйте
com.google.firebase.example.fireeats

- Нажмите «Зарегистрировать приложение» и следуйте инструкциям, чтобы загрузить файл
google-services.jsonи переместить его в папкуapp/загруженного вами кода. Затем нажмите «Далее» .
Импортируйте проект
Откройте Android Studio. Нажмите Файл > Создать > Импортировать проект и выберите папку friendlyeats-android .
4. Настройка эмуляторов Firebase
В этом практическом занятии вы будете использовать 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 и Android-приложение FriendlyEats.
Запустите эмуляторы
В терминале, находясь в каталоге friendlyeats-android , запустите firebase emulators:start , чтобы запустить Firebase Emulators. Вы должны увидеть примерно такие записи в логах:
$ 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-приложению потребуется подключиться к эмуляторам.
Подключите приложение к эмуляторам
Откройте файлы util/FirestoreInitializer.kt и util/AuthInitializer.kt в Android Studio. Эти файлы содержат логику для подключения SDK Firebase к локальным эмуляторам, работающим на вашем компьютере, при запуске приложения.
В методе create() класса FirestoreInitializer изучите следующий фрагмент кода:
// Use emulators only in debug builds
if (BuildConfig.DEBUG) {
firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
}
Мы используем BuildConfig , чтобы гарантировать подключение к эмуляторам только тогда, когда наше приложение работает в режиме debug . При компиляции приложения в режиме release это условие будет ложным.
Мы видим, что для подключения SDK Firebase к локальному эмулятору Firestore используется метод useEmulator(host, port) . На протяжении всего приложения мы будем использовать FirebaseUtil.getFirestore() для доступа к этому экземпляру FirebaseFirestore , чтобы быть уверенными, что при работе в режиме debug мы всегда подключаемся к эмулятору Firestore.
Запустите приложение
Если вы правильно добавили файл google-services.json , проект должен скомпилироваться. В Android Studio нажмите Build > Rebuild Project и убедитесь, что ошибок больше нет.
В Android Studio запустите приложение на эмуляторе Android. Сначала вам будет показан экран «Вход». Вы можете использовать любой адрес электронной почты и пароль для входа в приложение. Этот процесс входа подключается к эмулятору Firebase Authentication, поэтому реальные учетные данные не передаются.
Теперь откройте интерфейс эмулятора, перейдя по адресу http://localhost:4000 в вашем веб-браузере. Затем нажмите на вкладку «Аутентификация» , и вы увидите только что созданную учетную запись:

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

Вскоре мы добавим некоторые данные для заполнения главного экрана.
6. Запись данных в Firestore.
В этом разделе мы запишем некоторые данные в Firestore, чтобы заполнить пустое сейчас главное окно экрана.
Основной объект модели в нашем приложении — это ресторан (см. model/Restaurant.kt ). Данные Firestore разделены на документы, коллекции и подколлекции. Мы будем хранить каждый ресторан как документ в коллекции верхнего уровня под названием "restaurants" . Чтобы узнать больше о модели данных Firestore, ознакомьтесь с информацией о документах и коллекциях в документации .
Для демонстрации мы добавим в приложение функцию создания десяти случайных ресторанов при нажатии кнопки «Добавить случайные элементы» в меню дополнительных элементов. Откройте файл 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()добавляет документ в коллекцию с автоматически сгенерированным идентификатором, поэтому нам не потребовалось указывать уникальный идентификатор для каждого ресторана.
Теперь снова запустите приложение и нажмите кнопку «Добавить случайные элементы» в меню дополнительных элементов (в правом верхнем углу), чтобы вызвать только что написанный вами код:

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

Эти данные на 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 и определил функцию onEvent , чтобы он мог получать обновления запроса Firestore:
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()
}
// ...
}
При первоначальной загрузке слушатель будет получать одно событие ADDED для каждого нового документа. По мере изменения результирующего набора запроса со временем слушатель будет получать больше событий, содержащих изменения. Теперь давайте завершим реализацию слушателя. Сначала добавим три новых метода: onDocumentAdded , onDocumentModified и 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. Запустите приложение снова, и вы должны увидеть рестораны, которые вы добавили на предыдущем шаге:

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

Давайте отредактируем метод onFilter() в файле MainFragment.kt . Этот метод принимает объект 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
}
В приведенном выше фрагменте кода мы создаем объект Query , добавляя условия where и orderBy для соответствия заданным фильтрам.
Запустите приложение еще раз и выберите следующий фильтр, чтобы отобразить самые популярные рестораны с низкими ценами:

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

Если вы дочитали до этого места, значит, вы создали полностью функциональное приложение для просмотра рекомендаций ресторанов в Firestore! Теперь вы можете сортировать и фильтровать рестораны в режиме реального времени. В следующих разделах мы добавим отзывы к ресторанам и правила безопасности в приложение.
9. Организуйте данные в подколлекции.
В этом разделе мы добавим в приложение рейтинги, чтобы пользователи могли оставлять отзывы о своих любимых (или наименее любимых) ресторанах.
Коллекции и подколлекции
До сих пор все данные о ресторанах хранились в коллекции верхнего уровня под названием «restaurants». Когда пользователь оценивает ресторан, мы хотим добавить новый объект Rating к списку ресторанов. Для этой задачи мы будем использовать подколлекцию. Подколлекцию можно рассматривать как коллекцию, прикрепленную к документу. Таким образом, каждый документ о ресторане будет иметь подколлекцию ratings, содержащую документы с оценками. Подколлекции помогают организовать данные, не перегружая документы и не требуя сложных запросов.
Для доступа к подколлекции вызовите метод .collection() для родительского документа:
val subRef = firestore.collection("restaurants")
.document("abc123")
.collection("ratings")
Вы можете получать доступ к подколлекции и выполнять запросы к ней так же, как и к коллекции верхнего уровня, без ограничений по размеру или изменений в производительности. Подробнее о модели данных Firestore можно прочитать здесь .
Запись данных в транзакции
Для добавления Rating в соответствующую подколлекцию достаточно вызвать метод .add() , но также необходимо обновить средний рейтинг и количество оценок объекта Restaurant в соответствии с новыми данными. Если использовать отдельные операции для внесения этих двух изменений, может возникнуть ряд состояний гонки, которые приведут к устаревшим или некорректным данным.
Для корректного добавления оценок мы будем использовать транзакцию. Эта транзакция выполнит несколько действий:
- Прочитайте текущий рейтинг ресторана и рассчитайте новый.
- Добавить рейтинг в подколлекцию
- Обновите средний рейтинг ресторана и количество оценок.
Откройте файл 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() к объекту Task добавляются слушатели, реагирующие на результат транзакции.
Теперь снова запустите приложение и нажмите на один из ресторанов, после чего должен открыться экран с подробной информацией о ресторане. Нажмите кнопку «+» , чтобы начать добавлять отзыв. Чтобы добавить отзыв, выберите количество звезд и введите текст.

Нажатие кнопки «Отправить» запустит транзакцию. После завершения транзакции ваш отзыв отобразится ниже, а также обновится количество отзывов о ресторане:

Поздравляю! Теперь у вас есть социальное мобильное приложение для отзывов о ресторанах, созданное на платформе 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;
}
}
}
}
Эти правила ограничивают доступ, чтобы гарантировать, что клиенты вносят только безопасные изменения. Например, внесение изменений в документ, касающийся ресторана, может касаться только рейтингов, а не названия или любых других неизменяемых данных. Рейтинги могут быть созданы только в том случае, если идентификатор пользователя совпадает с идентификатором вошедшего в систему пользователя, что предотвращает подмену.
Чтобы узнать больше о правилах безопасности, посетите документацию .
11. Заключение
Вы создали полноценное приложение на базе Firestore. Вы изучили наиболее важные функции Firestore, в том числе:
- Документы и коллекции
- Чтение и запись данных
- Сортировка и фильтрация с помощью запросов
- Подколлекции
- Транзакции
Узнать больше
Чтобы продолжить изучение Firestore, вот несколько хороших мест, с которых можно начать:
Приложение для ресторана, представленное в этом практическом задании, было создано на основе примера приложения "Friendly Eats". Вы можете ознакомиться с исходным кодом этого приложения здесь .
Необязательно: Развертывание в производственной среде
На данный момент это приложение использует только Firebase Emulator Suite. Если вы хотите узнать, как развернуть это приложение в реальном проекте Firebase, перейдите к следующему шагу.
12. (Необязательно) Разверните ваше приложение
До сих пор это приложение работало полностью локально, все данные хранились в Firebase Emulator Suite. В этом разделе вы узнаете, как настроить ваш проект Firebase, чтобы это приложение работало в продакшене.
Аутентификация Firebase
В консоли Firebase перейдите в раздел «Аутентификация» и нажмите «Начать» . Перейдите на вкладку «Метод входа» и выберите вариант «Электронная почта/Пароль» из списка доступных провайдеров .
Включите вход по электронной почте/паролю и нажмите «Сохранить» .

Магазин огней
Создать базу данных
Перейдите в раздел «База данных Firestore» в консоли и нажмите «Создать базу данных» :
- При появлении запроса о правилах безопасности выберите запуск в производственном режиме ; мы скоро обновим эти правила.
- Выберите местоположение базы данных, которую вы хотите использовать для своего приложения. Обратите внимание, что выбор местоположения базы данных является окончательным решением, и для его изменения вам потребуется создать новый проект. Для получения дополнительной информации о выборе местоположения проекта см. документацию .
Правила развертывания
Чтобы развернуть написанные вами ранее правила безопасности, выполните следующую команду в каталоге 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 мы настроили SDK Firebase для подключения к эмуляторам в режиме отладки:
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, вы можете сделать следующее:
- Соберите приложение в режиме выпуска и запустите его на устройстве.
- Временно замените
BuildConfig.DEBUGнаfalseи снова запустите приложение.
Обратите внимание, что для корректного подключения к рабочей среде вам может потребоваться выйти из приложения и войти снова.