1. Code
  2. Coding Fundamentals
  3. Databases & SQL

Comenzando Con Cloud Firestore para Android

Cloud Firestore es una adición reciente a la familia de productos Firebase. Aunque aún está en beta, ya está siendo presentado por Google como una alternativa más flexible y rica en características a la Base de Datos en Tiempo Real Firebase.
Scroll to top

Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)

Cloud Firestore es una adición reciente a la familia de productos Firebase. Aunque aún está en beta, ya está siendo presentado por Google como una alternativa más flexible y rica en características a la Base de Datos en Tiempo Real Firebase.

Si alguna vez has usado la Base de Datos en Tiempo Real, probablemente sabes que es esencialmente un documento JSON grande más útil para almacenar solamente pares de llave-valor simples. Almacenar datos jerárquicos en esta de manera eficiente y segura, aunque es posible, es bastante complicado y requiere una estrategia bien pensada, la cual usualmente involucra aplanar los datos tanto como sea posible o desnormalizarla. Con tal estrategia, las consultas en la Base de Datos en Tiempo Real son propensas a consumir cantidades de ancho de banda innecesariamente grandes.

Cloud Firestore, siendo más parecido a bases de datos orientadas a documentos tales como MongoDB y CouchDB, no tiene tales problemas. Lo que es más, viene con una gran cantidad de características muy útiles, como soporte para operaciones en lote, escrituras atómicas y consultas indexadas.

En este tutorial, te ayudaré a comenzar a usar Cloud Firestore en la plataforma Android.

Prerrequisitos

Para poder seguir este tutorial, necesitarás:

  • la última versión de Android Studio
  • una cuenta Firebase
  • un dispositivo o emulador ejecutando Android 4.4 o superior

1. Creando un Proyecto Firebase

Antes de que uses productos Firebase en tu aplicación Android, debes crear un nuevo proyecto para esta en la consola Firebase. Para hacerlo, inicia sesión en la consola y presiona el botón Agregar Proyecto en la pantalla de bienvenida.

Welcome screen of Firebase consoleWelcome screen of Firebase consoleWelcome screen of Firebase console

En el diálogo que aparece, dale un nombre significativo al proyecto, de manera opcional dale un ID significativo, y presiona el botón Crear Proyecto.

Add a project dialogAdd a project dialogAdd a project dialog

Una vez que el proyecto ha sido creado, puedes establecer Firebase como su base de datos navegando a Desarrollo > Base de Datos y presionando el botón Probar Firebase Beta.

Database selection pageDatabase selection pageDatabase selection page

En la siguiente pantalla, asegúrate de elegir la opción Comenzar en modo de prueba y presiona el botón Habilitar.

Firestore security configuration pageFirestore security configuration pageFirestore security configuration page

En este punto, tendrás una base de datos Firestore vacía lista para ser usada en tu aplicación.

Empty Firestore databaseEmpty Firestore databaseEmpty Firestore database

2. Configurando el Proyecto Android

Tu proyecto Android Studio aún no sabe nada sobre el proyecto Firebase que creaste en el paso anterior. La manera más sencilla de establecer una conexión entre los dos es usar el Asistente Firebase de Android Studio.

Ve a Herramientas > Firebase para abrir el Asistente.

Firebase Assistant windowFirebase Assistant windowFirebase Assistant window

Debido a que Firestore aún está en beta, el Asistente no lo soporta aún. Aun así, agregando Firebase Analytics a tu aplicación, podrás automatizar la mayoría de los pasos requeridos de configuración.

Comienza dando clic en el enlace Registrar un Evento de Analítica bajo la sección de Analítica y presionando el botón Conectar a Firebase. Una nueva ventana de navegador debería aparecer preguntándote si quieres permitir que Android Studio, entre otras cosas, administre datos Firebase.

Android Studio requesting permissionsAndroid Studio requesting permissionsAndroid Studio requesting permissions

Presiona Permitir para continuar.

De vuelta en Android Studio, en el diálogo que aparece, selecciona la opción Elegir un proyecto Firebase o Google existente, selecciona el proyecto Firebase que creaste antes, y presiona el botón Conectar a Firebase.

Connect to Firebase dialogConnect to Firebase dialogConnect to Firebase dialog

Después, presiona el botón Agregar Analíticas a tu aplicación para agregar las dependencias Firebase principales a tu proyecto.

Finalmente, para agregar Firestore como una dependencia implementation, agrega la siguiente línea en el archivo build.gradle del módulo app.

1
implementation 'com.google.firebase:firebase-firestore:11.8.0'

No olvides presionar el botón Sincronizar Ahora para completar la configuración. Si encuentras cualquier error de conflicto de versión durante el proceso de sincronización, asegúrate de que las versiones de la dependencia Firestore y la dependencia Firebase Core sean idénticas y prueba de nuevo.

3. Entendiendo Documentos y Colecciones

Firestore es una base de datos NoSQL que te permite almacenar datos en la forma de documentos como JSON. Sin embargo, un documento almacenado en esta no puede existir de manera independiente. Siempre debe pertenecer a una colección. Como su nombre sugiere, una colección no es más que un montón de documentos.

Los documentos dentro de una colección son obviamente hermanos. Si quieres establecer relaciones padre-hijo entre ellos, debes usar sub-colecciones. Una sub-colección es solo una colección que pertenece a un documento. Por defecto, un documento se vuelve automáticamente el padre de todos los documentos que pertenecen a sus sub-colecciones.

También vale la pena notar que Firestore administra la creación y borrado de ambas colecciones y sub-colecciones por sí misma. Siempre que intentas agregar un documento a una colección no existente, esta crea la colección. De manera similar, una vez que borras todos los documentos de una colección, esta la borra.

4. Creando Documentos

Para poder escribir la base de datos Firestore desde tu aplicación Android, debes primero obtener la referencia a esta llamando el método getInstance() de la clase FirebaseFirestore.

1
val myDB = FirebaseFirestore.getInstance()

Después, debes ya sea crear una nueva colección y obtener una referencia a una colección existente, llamando el método collection(). Por ejemplo, en una base de datos vacía, el siguiente código crea una nueva colección llamada solar_system:

1
val solarSystem = myDB.collection("solar_system")

Una vez que tienes una referencia a una colección, puedes comenzar a agregar documentos a esta llamando su método add(), el cual espera un mapa como su argumento.

1
// Add a document

2
solarSystem.add(mapOf(
3
        "name" to "Mercury",
4
        "number" to 1,
5
        "gravity" to 3.7
6
))
7
8
// Add another document

9
solarSystem.add(mapOf(
10
        "name" to "Venus",
11
        "number" to 2,
12
        "gravity" to 8.87
13
))

El método add() genera y asigna de forma automática un identificador alfanumérico único a todos los documentos que crea. Si quieres que tus documentos tengan tus propios IDs personalizados, primero debes crear de manera manual esos documentos llamando al método document(), el cuál toma una cadena ID única como su entrada. Después puedes poblar el comento llamando al método set(), el cual,  como el método add, espera un mapa como su único argumento.

Por ejemplo, el siguiente código crea y puebla un nuevo documento llamado PLANET_EARTH:

1
solarSystem.document("PLANET_EARTH")
2
        .set(mapOf(
3
                "name" to "Earth",
4
                "number" to 3,
5
                "gravity" to 9.807
6
        ))

Si vas a la consola Firebase y echas un vistazo a los contenidos de la base de datos, podrás detectar el ID personalizado fácilmente.

New entries in the Firestore databaseNew entries in the Firestore databaseNew entries in the Firestore database

Ten en cuenta que si el ID personalizado que pasas al método document() ya existe en la base de datos, el método set() sobreescribirá el documento asociado.

5. Creando Sub-colecciones

El soporte para sub-colecciones es una de las características más poderosas de Firestore y es lo que lo hace marcadamente diferente de la Base de Datos en Tiempo Real Firebase. Usando sub-colecciones, no solo puedes agregar fácilmente estructuras anidadas a tus datos sino que también puedes estar seguro de que tus consultas consumirán cantidades mínimas de ancho de banda.

Crear una sub-colección es como crear una colección. Todo lo que necesitas hacer es llamar al método collection() en un objeto DocumentReference y pasarle una cadena, que será usada como el nombre de la sub-colección.

Por ejemplo, el siguiente código crea una sub-colección llamada satellites y la asocia con el documento PLANET_EARTH:

1
val satellitesOfEarth = solarSystem.document("PLANET_EARTH")
2
                                   .collection("satellites")

Una vez que tienes una referencia a una sub-colección, eres libre de llamar a los métodos add() o set() para agregar documentos a esta.

1
satellitesOfEarth.add(mapOf(
2
        "name" to "The Moon",
3
        "gravity" to 1.62,
4
        "radius" to 1738
5
))

Después de que ejecutas el código de arriba, el documento PLANET_EARTH lucirá como esto en la consola Firebase:

Subcollection of a documentSubcollection of a documentSubcollection of a document

6. Ejecutando Consultas

Realizar una operación de lectura en tu base de datos Firestore es muy sencillo si conoces el ID del documento que quieres leer. ¿Por qué? Debido a que puedes obtener una referencia directamente al documento llamando a los métodos collection() y document(). Por ejemplo, aquí está cómo puedes obtener una referencia al documento PLANET_EARTH que pertenece a la colección solar_system:

1
val planetEarthDoc = myDB.collection("solar_system")
2
                         .document("PLANET_EARTH")

Para realmente leer los contenidos del documento, debes llamar al método asíncrono get(), el cuál devuelve un Task. Agregando un OnSuccessListener a este, puedes ser notificado cuando la operación de lectura se complete de manera satisfactoria.

El resultado de la operación de lectura es un objeto DocumentSnapshot, el cual contiene pares llave-valor presentes en el documento. Usando su método get(), puedes obtener el valor de cualquier llave válida. El siguiente ejemplo te muestra cómo:

1
planetEarthDoc.get().addOnSuccessListener {
2
    println(
3
       "Gravity of ${it.get("name")} is ${it.get("gravity")} m/s/s"
4
    )
5
}
6
7
// OUTPUT:

8
// Gravity of Earth is 9.807 m/s/s

Si no conoces el ID del documento que quieres leer, tendrás que ejecutar una consulta tradicional sobre una colección entera. La API Firestore proporciona métodos filtro nombrados de forma intuitiva tales como whereEqualTo(), whereLessThan(), y whereGreaterThan(). Debido a que los métodos filtro pueden devolver múltiples documentos como sus resultados, necesitarás un ciclo dentro de tu OnSuccessListener para manejar cada resultado.

Por ejemplo, para obtener los contenidos del documento para el planeta Venus, el cuál agregamos en un paso anterior, podrías usar el siguiente código:

1
myDB.collection("solar_system")
2
  .whereEqualTo("name", "Venus") 
3
  .get().addOnSuccessListener {
4
      it.forEach {
5
        println(
6
          "Gravity of ${it.get("name")} is ${it.get("gravity")} m/s/s"
7
        )
8
      }
9
  }
10
11
// OUTPUT:

12
// Gravity of Venus is 8.87 m/s/s

Por último, si estás interesado en leer todos los documentos que pertenecen a una colección, puedes llamar directamente al método get() sobre la colección. Por ejemplo, aquí está cómo puedes listar todos los planetas presentes en la colección solar_system:

1
myDB.collection("solar_system")
2
        .get().addOnSuccessListener {
3
            it.forEach {
4
                println(it.get("name"))
5
            }
6
        }
7
8
// OUTPUT:

9
// Earth

10
// Venus

11
// Mercury

Nota que, por defecto, no hay orden definitivo en el cual los resultados son devueltos. Si quieres ordenarlos basados en una llave que está presente en todos los resultados, puedes hacer uso del método orderBy(). El siguiente código ordena los resultados basado en el valor del number de la llave:

1
myDB.collection("solar_system")
2
                .orderBy("number")
3
                .get().addOnSuccessListener {
4
            it.forEach {
5
                println(it.get("name"))
6
            }
7
        }
8
9
// OUTPUT:

10
// Mercury

11
// Venus

12
// Earth

7. Borrando Datos

Para borrar un documento con un ID conocido, todo lo que necesitas hacer es obtener una referencia a este y después llamar al método delete().

1
myDB.collection("solar_system")
2
    .document("PLANET_EARTH")
3
    .delete()

Borrar múltiples documentos----documentos que obtienes como el resultado de una consulta---es ligeramente más complicado porque no hay un método integrado para hacerlo. Hay dos aproximaciones diferentes que puedes seguir.

La más aproximación más sencilla e intuitiva---aunque solo es útil para un número muy pequeño de documentos---es ciclar a través de los resultados, obtener una referencia a cada documento, y después llamar al método delete(). Aquí está cómo puedes usar la aproximación para borrar todos los documentos en la colección solar_system:

1
myDB.collection("solar_system")
2
        .get().addOnSuccessListener {
3
            it.forEach {
4
                it.reference.delete()
5
            }
6
        }

Una aproximación más eficiente y escalable es usar una operación en lote. Las operaciones en lote no solo pueden borrar múltiples documentos de manera automática sino que también reduce significativamente el número de conexiones de red requeridas.

Para crear un nuevo lote, debes llamar al método batch() de tu base de datos, el cuál devuelve una instancia de la clase WriteBatch. Después, puedes ciclar a través de los resultados de la consulta y marcarlos para borrado pasándoles el método delete() del objeto WriteBatch. Finalmente, para comenzar realmente el proceso de borrado, puedes llamar al método commit(). El siguiente código te muestra como:

1
myDB.collection("solar_system")
2
        .get().addOnSuccessListener {
3
4
    // Create batch

5
    val myBatch = myDB.batch()
6
7
    // Add documents to batch

8
    it.forEach {
9
        myBatch.delete(it.reference)
10
    }
11
12
    // Run batch

13
    myBatch.commit()
14
}

Nota que intentar agregar demasiados documentos a una sola operación de lote puede llevar a errores de insuficiencia de memoria. De ahí que su tu consulta probablemente devuelva un gran número de documentos, debes asegurar que los partes en múltiples lotes.

Conclusión

En este tutorial de introducción, aprendiste cómo realizar operaciones de lectura y escritura en Google Cloud Firestore. Sugiero que comiences a utilizarlo en tus proyectos Android de inmediato. Hay una buena posibilidad de que reemplace a la Base de Datos en Tiempo Real en el futuro. De hecho, Google dice que cuando salga de beta, será mucho más confiable y escalable que la Base de Datos en Tiempo Real.

Para aprender más sobre Cloud Firestore, puedes referirte a su documentación oficial.

Y mientras estás aquí, ¡revisa algunos de nuestros otros tutoriales sobre Firebase y desarrollo de aplicaciones Android!