Laboratorio de programación de Android Cloud Firestore

1. Información general

Objetivos

En este laboratorio de código, creará una aplicación de recomendación de restaurantes en Android respaldada por Cloud Firestore. Aprenderás a:

  • Leer y escribir datos en Firestore desde una aplicación de Android
  • Escuche los cambios en los datos de Firestore en tiempo real
  • Utilice la autenticación de Firebase y las reglas de seguridad para proteger los datos de Firestore
  • Escribir consultas complejas de Firestore

requisitos previos

Antes de comenzar este codelab, asegúrese de tener:

  • Android Studio Flamingo o más reciente
  • Un emulador de Android con API 19 o superior
  • Node.js versión 16 o superior
  • Java versión 17 o superior

2. Crea un proyecto de Firebase

  1. Inicie sesión en la consola de Firebase con su cuenta de Google.
  2. En Firebase console , haga clic en Agregar proyecto .
  3. Como se muestra en la captura de pantalla a continuación, ingrese un nombre para su proyecto de Firebase (por ejemplo, "Friendly Eats") y haga clic en Continuar .

9d2f625aebcab6af.png

  1. Es posible que se le solicite que habilite Google Analytics; a los fines de este laboratorio de código, su selección no importa.
  2. Después de aproximadamente un minuto, su proyecto de Firebase estará listo. Haga clic en Continuar .

3. Configurar el proyecto de muestra

Descarga el código

Ejecute el siguiente comando para clonar el código de muestra para este laboratorio de código. Esto creará una carpeta llamada friendlyeats-android en su máquina:

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

Si no tiene git en su máquina, también puede descargar el código directamente desde GitHub.

Agregar configuración de Firebase

  1. En Firebase console , seleccione Descripción general del proyecto en el panel de navegación izquierdo. Haga clic en el botón de Android para seleccionar la plataforma. Cuando se le solicite un nombre de paquete, use com.google.firebase.example.fireeats

73d151ed16016421.png

  1. Haga clic en Registrar aplicación y siga las instrucciones para descargar el archivo google-services.json y muévalo a la carpeta app/ del código que acaba de descargar. Luego haga clic en Siguiente .

Importar el proyecto

Abra Android Studio. Haga clic en Archivo > Nuevo > Importar proyecto y seleccione la carpeta friendlyeats-android .

4. Configurar los emuladores de Firebase

En este laboratorio de código, usará Firebase Emulator Suite para emular localmente Cloud Firestore y otros servicios de Firebase. Esto proporciona un entorno de desarrollo local seguro, rápido y gratuito para crear su aplicación.

Instalar la CLI de Firebase

Primero deberá instalar Firebase CLI . Si está utilizando macOS o Linux, puede ejecutar el siguiente comando cURL:

curl -sL https://firebase.tools | bash

Si está utilizando Windows, lea las instrucciones de instalación para obtener un binario independiente o para instalar a través de npm .

Una vez que haya instalado la CLI, ejecutar firebase --version debería informar una versión de 9.0.0 o superior:

$ firebase --version
9.0.0

Acceso

Ejecute firebase login para conectar la CLI a su cuenta de Google. Esto abrirá una nueva ventana del navegador para completar el proceso de inicio de sesión. Asegúrate de elegir la misma cuenta que usaste al crear tu proyecto de Firebase anteriormente.

Desde la carpeta friendlyeats-android , ejecute firebase use --add para conectar su proyecto local a su proyecto de Firebase. Siga las indicaciones para seleccionar el proyecto que creó anteriormente y, si se le pide que elija un alias, ingrese default .

5. Ejecute la aplicación

Ahora es el momento de ejecutar Firebase Emulator Suite y la aplicación de Android FriendlyEats por primera vez.

Ejecutar los emuladores

En su terminal desde el directorio friendlyeats-android ejecute firebase emulators:start para iniciar Firebase Emulators. Deberías ver registros como este:

$ 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.

¡Ahora tiene un entorno de desarrollo local completo ejecutándose en su máquina! Asegúrese de dejar este comando ejecutándose durante el resto del laboratorio de código, su aplicación de Android deberá conectarse a los emuladores.

Conecte la aplicación a los emuladores

Abra los archivos util/FirestoreInitializer.kt y util/AuthInitializer.kt en Android Studio. Estos archivos contienen la lógica para conectar los SDK de Firebase a los emuladores locales que se ejecutan en su máquina, al iniciar la aplicación.

En el método create() de la clase FirestoreInitializer , examine este fragmento de código:

    // Use emulators only in debug builds
    if (BuildConfig.DEBUG) {
        firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
    }

Usamos BuildConfig para asegurarnos de que solo nos conectamos a los emuladores cuando nuestra aplicación se ejecuta en modo debug . Cuando compilamos la aplicación en modo release , esta condición será falsa.

Podemos ver que está usando el método useEmulator(host, port) para conectar el SDK de Firebase al emulador local de Firestore. A lo largo de la aplicación, usaremos FirebaseUtil.getFirestore() para acceder a esta instancia de FirebaseFirestore , por lo que estamos seguros de que siempre nos estamos conectando al emulador de Firestore cuando se ejecuta en modo de debug .

Ejecute la aplicación

Si agregó el archivo google-services.json correctamente, el proyecto ahora debería compilarse. En Android Studio, haga clic en Generar > Reconstruir proyecto y asegúrese de que no queden errores.

En Android Studio Ejecute la aplicación en su emulador de Android. Al principio se le presentará una pantalla de "Iniciar sesión". Puede usar cualquier correo electrónico y contraseña para iniciar sesión en la aplicación. Este proceso de inicio de sesión se conecta al emulador de autenticación de Firebase, por lo que no se transmiten credenciales reales.

Ahora abra la interfaz de usuario de los emuladores navegando a http://localhost:4000 en su navegador web. Luego haga clic en la pestaña Autenticación y debería ver la cuenta que acaba de crear:

Emulador de autenticación de Firebase

Una vez que haya completado el proceso de inicio de sesión, debería ver la pantalla de inicio de la aplicación:

de06424023ffb4b9.png

Pronto agregaremos algunos datos para completar la pantalla de inicio.

6. Escribir datos en Firestore

En esta sección, escribiremos algunos datos en Firestore para que podamos completar la pantalla de inicio actualmente vacía.

El objeto principal del modelo en nuestra aplicación es un restaurante (ver model/Restaurant.kt ). Los datos de Firestore se dividen en documentos, colecciones y subcolecciones. Almacenaremos cada restaurante como un documento en una colección de nivel superior llamada "restaurants" . Para obtener más información sobre el modelo de datos de Firestore, lea sobre documentos y colecciones en la documentación .

Para fines de demostración, agregaremos funcionalidad en la aplicación para crear diez restaurantes aleatorios cuando hagamos clic en el botón "Agregar elementos aleatorios" en el menú adicional. Abra el archivo MainFragment.kt y reemplace el contenido en el método onAddItemsClicked() con:

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

Hay algunas cosas importantes a tener en cuenta sobre el código anterior:

  • Comenzamos obteniendo una referencia a la colección "restaurants" . Las colecciones se crean implícitamente cuando se agregan documentos, por lo que no fue necesario crear la colección antes de escribir los datos.
  • Los documentos se pueden crear usando las clases de datos de Kotlin, que usamos para crear cada documento de restaurante.
  • El método add() agrega un documento a una colección con una identificación generada automáticamente, por lo que no necesitamos especificar una identificación única para cada restaurante.

Ahora ejecute la aplicación nuevamente y haga clic en el botón "Agregar elementos aleatorios" en el menú adicional (en la esquina superior derecha) para invocar el código que acaba de escribir:

95691e9b71ba55e3.png

Ahora abra la interfaz de usuario de los emuladores navegando a http://localhost:4000 en su navegador web. Luego haga clic en la pestaña Firestore y debería ver los datos que acaba de agregar:

Emulador de autenticación de Firebase

Estos datos son 100% locales para su máquina. De hecho, ¡su proyecto real ni siquiera contiene una base de datos de Firestore todavía! Esto significa que es seguro experimentar con la modificación y eliminación de estos datos sin consecuencias.

¡Felicitaciones, acabas de escribir datos en Firestore! En el siguiente paso, aprenderemos cómo mostrar estos datos en la aplicación.

7. Mostrar datos de Firestore

En este paso, aprenderemos cómo recuperar datos de Firestore y mostrarlos en nuestra aplicación. El primer paso para leer datos de Firestore es crear una Query . Abra el archivo MainFragment.kt y agregue el siguiente código al principio del método onViewCreated() :

        // Firestore
        firestore = Firebase.firestore

        // Get the 50 highest rated restaurants
        query = firestore.collection("restaurants")
            .orderBy("avgRating", Query.Direction.DESCENDING)
            .limit(LIMIT.toLong())

Ahora queremos escuchar la consulta, para obtener todos los documentos coincidentes y recibir notificaciones de futuras actualizaciones en tiempo real. Debido a que nuestro objetivo final es vincular estos datos a un RecyclerView , necesitamos crear una clase RecyclerView.Adapter para escuchar los datos.

Abra la clase FirestoreAdapter , que ya se implementó parcialmente. Primero, hagamos que el adaptador implemente EventListener y definamos la función onEvent para que pueda recibir actualizaciones de una consulta de 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()
    }
    
    // ...
}

En la carga inicial, el oyente recibirá un evento ADDED para cada documento nuevo. A medida que el conjunto de resultados de la consulta cambie con el tiempo, el oyente recibirá más eventos que contengan los cambios. Ahora terminemos de implementar el oyente. Primero agregue tres nuevos métodos: onDocumentAdded , onDocumentModified y 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)
    }

Luego llama a estos nuevos métodos desde 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()
    }

Finalmente, implemente el método startListening() para adjuntar el oyente:

    fun startListening() {
        if (registration == null) {
            registration = query.addSnapshotListener(this)
        }
    }

Ahora la aplicación está completamente configurada para leer datos de Firestore. Vuelva a ejecutar la aplicación y debería ver los restaurantes que agregó en el paso anterior:

9e45f40faefce5d0.png

Ahora regrese a la interfaz de usuario del emulador en su navegador y edite uno de los nombres de los restaurantes. ¡Deberías verlo cambiar en la aplicación casi al instante!

8. Ordenar y filtrar datos

Actualmente, la aplicación muestra los restaurantes mejor calificados en toda la colección, pero en una aplicación de restaurante real, el usuario querría ordenar y filtrar los datos. Por ejemplo, la aplicación debería poder mostrar "Los mejores restaurantes de mariscos en Filadelfia" o "La pizza más económica".

Al hacer clic en la barra blanca en la parte superior de la aplicación, aparece un cuadro de diálogo de filtros. En esta sección, usaremos consultas de Firestore para que este cuadro de diálogo funcione:

67898572a35672a5.png

Editemos el método onFilter() de MainFragment.kt . Este método acepta un objeto Filters que es un objeto auxiliar que creamos para capturar el resultado del cuadro de diálogo de filtros. Cambiaremos este método para construir una consulta a partir de los filtros:

    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
    }

En el fragmento anterior, construimos un objeto Query adjuntando las cláusulas where y orderBy para que coincida con los filtros dados.

Vuelva a ejecutar la aplicación y seleccione el siguiente filtro para mostrar los restaurantes de bajo precio más populares:

7a67a8a400c80c50.png

Ahora debería ver una lista filtrada de restaurantes que contienen solo opciones de bajo precio:

a670188398c3c59.png

Si ha llegado hasta aquí, ahora ha creado una aplicación de visualización de recomendaciones de restaurantes completamente funcional en Firestore. Ahora puede ordenar y filtrar restaurantes en tiempo real. En las próximas secciones agregaremos reseñas a los restaurantes y agregaremos reglas de seguridad a la aplicación.

9. Organizar datos en subcolecciones

En esta sección agregaremos calificaciones a la aplicación para que los usuarios puedan revisar sus restaurantes favoritos (o menos favoritos).

Colecciones y subcolecciones

Hasta ahora, hemos almacenado todos los datos de los restaurantes en una colección de alto nivel llamada "restaurantes". Cuando un usuario califica un restaurante, queremos agregar un nuevo objeto Rating a los restaurantes. Para esta tarea utilizaremos una subcolección. Puede pensar en una subcolección como una colección que se adjunta a un documento. Por lo tanto, cada documento de restaurante tendrá una subcolección de calificaciones llena de documentos de calificación. Las subcolecciones ayudan a organizar los datos sin inflar nuestros documentos ni requerir consultas complejas.

Para acceder a una subcolección, llama .collection() en el documento principal:

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

Puede acceder y consultar una subcolección como con una colección de nivel superior, no hay limitaciones de tamaño ni cambios de rendimiento. Puede leer más sobre el modelo de datos de Firestore aquí .

Escritura de datos en una transacción

Agregar una Rating a la subcolección adecuada solo requiere llamar .add() , pero también necesitamos actualizar la calificación promedio del objeto Restaurant y la cantidad de calificaciones para reflejar los nuevos datos. Si usamos operaciones separadas para hacer estos dos cambios, hay una serie de condiciones de carrera que podrían dar como resultado datos obsoletos o incorrectos.

Para garantizar que las calificaciones se agreguen correctamente, utilizaremos una transacción para agregar calificaciones a un restaurante. Esta transacción realizará algunas acciones:

  • Lee la calificación actual del restaurante y calcula la nueva
  • Agregar la calificación a la subcolección
  • Actualizar la calificación promedio del restaurante y el número de calificaciones

Abra RestaurantDetailFragment.kt e implemente la función 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
        }
    }

La función addRating() devuelve una Task que representa la transacción completa. En la función onRating() se agregan oyentes a la tarea para responder al resultado de la transacción.

Ahora ejecute la aplicación nuevamente y haga clic en uno de los restaurantes, lo que debería mostrar la pantalla de detalles del restaurante. Haz clic en el botón + para comenzar a agregar una reseña. Agrega una reseña eligiendo un número de estrellas e ingresando algún texto.

78fa16cdf8ef435a.png

Presionar Enviar iniciará la transacción. Cuando se complete la transacción, verá su reseña a continuación y una actualización del recuento de reseñas del restaurante:

f9e670f40bd615b0.png

¡Felicitaciones! Ahora tiene una aplicación social, local y móvil de reseñas de restaurantes creada en Cloud Firestore. Escuché que esos son muy populares en estos días.

10. Proteja sus datos

Hasta ahora no hemos considerado la seguridad de esta aplicación. ¿Cómo sabemos que los usuarios solo pueden leer y escribir los datos propios correctos? Las bases de datos de Firestore están protegidas por un archivo de configuración llamado Reglas de seguridad .

Abra el archivo firestore.rules , debería ver lo siguiente:

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

Cambiemos estas reglas para evitar cambios o accesos de datos no deseados, abra el archivo firestore.rules y reemplace el contenido con lo siguiente:

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

Estas reglas restringen el acceso para garantizar que los clientes solo realicen cambios seguros. Por ejemplo, las actualizaciones de un documento de restaurante solo pueden cambiar las calificaciones, no el nombre ni ningún otro dato inmutable. Las calificaciones solo se pueden crear si la ID de usuario coincide con el usuario que inició sesión, lo que evita la suplantación de identidad.

Para leer más sobre las reglas de seguridad, visite la documentación .

11. Conclusión

Ahora ha creado una aplicación con todas las funciones además de Firestore. Aprendió sobre las características más importantes de Firestore, que incluyen:

  • Documentos y colecciones
  • Leer y escribir datos
  • Ordenar y filtrar con consultas
  • Subcolecciones
  • Actas

Aprende más

Para seguir aprendiendo sobre Firestore, estos son algunos buenos lugares para comenzar:

La aplicación de restaurante de este laboratorio de programación se basó en la aplicación de ejemplo "Friendly Eats". Puede buscar el código fuente de esa aplicación aquí .

Opcional: implementar en producción

Hasta ahora, esta aplicación solo ha utilizado Firebase Emulator Suite. Si desea obtener información sobre cómo implementar esta aplicación en un proyecto real de Firebase, continúe con el próximo paso.

12. (Opcional) Implemente su aplicación

Hasta ahora, esta aplicación ha sido completamente local, todos los datos están contenidos en Firebase Emulator Suite. En esta sección, aprenderá cómo configurar su proyecto de Firebase para que esta aplicación funcione en producción.

Autenticación de base de fuego

En Firebase console, vaya a la sección Autenticación y haga clic en Comenzar . Vaya a la pestaña Método de inicio de sesión y seleccione la opción Correo electrónico/Contraseña de Proveedores nativos .

Habilite el método de inicio de sesión con correo electrónico/contraseña y haga clic en Guardar .

proveedores-de-inicio.png

Firestore

Crear base de datos

Vaya a la sección Base de datos de Firestore de la consola y haga clic en Crear base de datos :

  1. Cuando se le pregunte sobre las reglas de seguridad, elija comenzar en modo de producción , actualizaremos esas reglas pronto.
  2. Elija la ubicación de la base de datos que le gustaría usar para su aplicación. Tenga en cuenta que seleccionar una ubicación de base de datos es una decisión permanente y para cambiarla deberá crear un nuevo proyecto. Para obtener más información sobre cómo elegir la ubicación de un proyecto, consulte la documentación .

Reglas de implementación

Para implementar las reglas de seguridad que escribió anteriormente, ejecute el siguiente comando en el directorio del laboratorio de código:

$ firebase deploy --only firestore:rules

Esto implementará el contenido de firestore.rules en su proyecto, que puede confirmar navegando a la pestaña Reglas en la consola.

Implementar índices

La aplicación FriendlyEats tiene una clasificación y filtrado complejos que requieren una serie de índices compuestos personalizados. Estos se pueden crear a mano en la consola de Firebase, pero es más sencillo escribir sus definiciones en el archivo firestore.indexes.json e implementarlos mediante la CLI de Firebase.

Si abre el archivo firestore.indexes.json , verá que ya se han proporcionado los índices necesarios:

{
  "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": []
}

Para implementar estos índices, ejecute el siguiente comando:

$ firebase deploy --only firestore:indexes

Tenga en cuenta que la creación de índices no es instantánea, puede monitorear el progreso en la consola de Firebase.

Configurar la aplicación

En los archivos util/FirestoreInitializer.kt y util/AuthInitializer.kt configuramos el SDK de Firebase para conectarse a los emuladores cuando está en modo de depuración:

    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
    }

Si desea probar su aplicación con su proyecto real de Firebase, puede:

  1. Cree la aplicación en modo de lanzamiento y ejecútela en un dispositivo.
  2. Reemplace temporalmente BuildConfig.DEBUG con false y vuelva a ejecutar la aplicación.

Tenga en cuenta que es posible que deba cerrar sesión en la aplicación e iniciar sesión nuevamente para conectarse correctamente a producción.