Bases de Datos de Firebase con Android

3
30632

Índice de contenidos

1. Introducción

En este tutorial explicaremos brevemente Firebase y veremos los dos tipos de bases de datos que nos proporciona: Realtime Database y Cloud Firestore. Además, aprenderemos cómo integrar Firebase con un proyecto de Android y un ejemplo básico para cada tipo de base de datos, los cuales estarán escritos en Kotlin.

2. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBook Pro 17’ (2,66 GHz Intel Core i7, 8GB DDR3)
  • Sistema operativo: macOS Sierra 10.13.6
  • Entorno de desarrollo: Android Studio 3.3
  • Versión SDK mínima: 16

3. ¿Qué es Firebase?

Firebase es una plataforma SaS (Software as Service) proporcionada por Google que ofrece varias funcionalidades:

  • Características para desarrollar aplicaciones como:
    • Almacenamiento en la nube
    • Servicios de autenticación, incluyendo registro con email o utilizando una cuenta de Google, Facebook, Github o Twitter.
    • Bases de Datos que es lo que cubriremos en este tutorial.
    • Reporte de errores.
  • Firebase Analytics: servicios de analítica del comportamiento de los usuarios de la aplicación
  • Recursos para el crecimiento
  • AdMob: herramienta para monetizar la aplicación.

Sus herramientas son fáciles de usar, por lo que simplifica el desarrollo de aplicaciones móviles o web y tiene tanto planes de pago como uno gratuito que sirve para desarrollar proyectos pequeños.

4. Bases de Datos en Firebase

En Firebase tenemos dos tipos de bases de datos: Realtime Database y Cloud Firestore. En la mayoría de los casos se recomienda usar la segunda, que además es más reciente, pero vamos a ver ambos casos ya que es posible que para un proyecto en concreto se prefiera la otra. Además, existe la posibilidad de utilizar ambas en un único proyecto de ser necesario.

Para empezar hay que destacar que las dos son bases de datos no relacionales (y, por tanto, NoSQL), pero su estructura es diferente. También las dos siguen el principio de mobile-first y admiten almacenamiento local de datos para Android e iOS. Además, Cloud Firestore admite también almacenamiento local para aplicaciones web.

4.1. Realtime Database

Vamos a describir las principales características de Realtime Database:

  • Los datos se almacenan en un único árbol de tipo JSON. Esto hace complicado organizar estructuras de datos complejas.
  • Se pueden definir reglas para la base de datos para otorgar permisos de lectura/escritura.
  • Las consultas son profundas, es decir, devuelven todo el sub-árbol de los nodos seleccionados. Esto implica permisos en cascada, por lo que hay que evitar anidar datos todo lo posible.
  • Permite filtrar u ordenar en cada consulta ejecutada (sólo una de las dos opciones).
  • Se permite la definición de reglas de validación para guardar la coherencia de los datos. Estas reglas no se aplican en cascada.
  • Son una solución regional, es decir, los datos se guardan en un único servidor regional. Esto conlleva una menor latencia, pero una disponibilidad más limitada.
  • Si la base de datos requiere muchas conexiones (más de cien mil simultáneas o más de mil lecturas/escrituras por segundas) es necesario particionarla en varias bases de datos.
  • Para opciones de pago, su coste se basa en el almacenamiento y el ancho de banda usado.

 

4.2. Cloud Firestore

Las principales características de Cloud Firestore son las siguientes:

  • Se trata de una base de datos basada en documentos similares a JSON.
    • Permite estructuras más complejas con sub-colecciones.
    • Los documentos se pueden acceder por referencia, por lo que al borrar uno de ellos sus sub-colecciones pueden seguir siendo accesibles.
  • Permite filtrar y ordenar en las consultas, así como devolver una sub-colección en un documento en vez de todo el sub-árbol.
  • El rendimiento se basa en el tamaño del resultado, no del total de datos.
  • Las transacciones se repiten hasta completarse.
  • Es una solución multiregional, es decir, los datos se almacenan en varios centros de datos.
    • La idea es garantizar que, aunque un centro se desconecte, los datos sigan accesibles.
    • Se garantiza la consistencia entre los datos de todos los centros.
  • La validación de datos es automática.
  • Se pueden realizar consultas más complejas sobre muchos datos.
  • Se permite establecer reglas para otorgar permisos sin que se otorguen en cascada por defecto.
  • Escala automáticamente con un límite de un millón de conexiones simultáneas o diez mil lecturas/escrituras, aunque hay planes de ampliar este límite.
  • Para opciones de pago, el coste se basa principalmente en las operaciones realizadas (lectura, escritura y borrado) y en menor medida en el ancho de banda y el almacenamiento usado.
    • Con este sistema, un gran número de pequeñas operaciones puede resultar más caro con Cloud Firebase.
    • Se puede establecer un límite de coste diario.

5. Crear un proyecto de Firebase

Antes de iniciar nuestros ejemplos, vamos a ver cómo crear un proyecto de Firebase y añadirlo a nuestro proyecto de Android. Android Studio permite hacerlo de dos maneras, por lo que vamos a verlas.

5.1. Usando el asistente de Firebase en Android Studio

Para usar el asistente de Firebase en Android Studio seleccionamos Tools > Firebase. Después de eso, nos aparecerá una lista en la cual buscamos el servicio que vayamos a usar (por ejemplo Realtime Database) y hacemos click en el link que aparece. Se nos abrirá una nueva pantalla en la cual presionaremos el botón «Connect to Firebase» que nos redirigirá a la página de Firebase, solicitándonos los permisos necesarios para poder realizar este procedimiento. Asistente de Firebase en Tools

Lista del asistente de Firebase

Una vez tenga permisos, en Android Studio nos debe aparecer una pantalla para crear un nuevo proyecto de Firebase o para escoger uno ya existente. Por último, tan sólo tenemos que añadir las dependencias con el botón del segundo paso, con lo que se modificará nuestra configuración de Gradle y se sincronizará nuestro proyecto.

Realtime Database en el asistente de Firebase

Sin embargo, lo más probable es que nos falte añadir la dependencia básica de Firebase, por lo que, en ese caso, tendremos que añadirla nosotros al fichero de configuración de Gradle (Module app).

dependencies {
    ...
    implementation 'com.google.firebase:firebase-core:16.0.7'
}

Al hacer esto es posible que tengamos problemas con las versiones, ya que Android Studio puede que indique una versión diferente a la del código que tienes justo encima. Si es así, es posible que tengas que cambiar el número de versión en la configuración de Gradle tanto a nivel de módulo como de proyecto. Para evitar complicaciones recomiendo utilizar la última versión disponible de las dependencias de Firebase, a no ser que quieras emplear alguna en concreto.

5.2. Añadir Firebase a Android manualmente

Para añadir Firebase a nuestro proyecto de Android manualmente lo primero que vamos a hacer es conectarnos a la consola de Firebase con una cuenta de Google, la cual puedes encontrar en este enlace. A continuación añade un nuevo proyecto, indicando un nombre y las ubicaciones de las analíticas y del servidor en el que se alojarán nuestros datos de Cloud Firestore si lo fuéramos a usar.

Página de inicio de la consola de Firebase

Nuevo proyecto de FirebaseUna vez ya tengamos nuestro proyecto de Firebase creado, vamos a pulsar el icono de Android para poder añadirlo a nuestra aplicación Android. A continuación nos saldrá un pequeño formulario en el que tenemos que indicar el nombre del paquete de nuestro proyecto Android. Una vez completado, podemos descargar el fichero google-services.json, el cual hay que mover a la carpeta app del proyecto.

Proyecto creado de Firebase

Añadir Firebase a un proyecto de Android

El último paso es añadir las dependencias de Firebase a Gradle. A nivel de proyecto hay que añadir una:

dependencies {
    ...
    classpath 'com.google.gms:google-services:4.2.0'
}

Y a nivel de módulo tenemos que añadir dos líneas: una es una dependencia y otra, que debe ir al final del fichero.

dependencies {
    ...
    implementation 'com.google.firebase:firebase-core:16.0.7'
}
...
apply plugin: 'com.google.gms.google-services'

Para terminar, tan sólo hay que sincronizar los cambios. Al hacer esto es posible que nos dé errores con otras librerías por su versión. Si es así, una forma de solucionarlo es declarar explícitamente la versión de las librerías conflictivas. Por ejemplo, en mi caso he incluido la siguiente dependencia dentro de la configuración a nivel de módulo:

implementation 'com.android.support:support-media-compat:28.0.0'

6. Ejemplo con Realtime Database

Como ejemplo sencillo para ver cómo podemos usar Realtime Database vamos a crear una aplicación que nos permita añadir notas de alumnos y listarlos en tiempo real.

6.1. Preparando las bases

Para comenzar, vamos a crear una base de datos en la consola de Firebase (la página web). Seleccionamos Database en el menú y pulsamos Create Database. Aunque se muestre como si estuviéramos creando Cloud Firestore realmente tendremos disponibles ambas opciones. Respecto a las reglas de seguridad escoge indistintamente el modo locked o test, ya que en este punto no nos interesa cuáles use Cloud Firestore.

Crear las bases de datos en la consola de Firebase

Una vez creada, deberemos seleccionar Realtime Database en el menú desplegable superior y acceder a la pestaña de Rules. Como no vamos a tratar la autenticación, vamos a otorgar permisos a cualquier conexión a la base de datos cambiando las reglas de write y read a true. Ten en cuenta que en un proyecto real esto supone un grave problema de seguridad. Por último, guardamos los cambios pulsando en Publish.

Reglas de Realtime Database

El siguiente paso es añadir la dependencia de Realtime Database a nuestro proyecto Android. Para ello vamos a Android Studio, abrimos el fichero de configuración de Gradle (Module: app) y sincronizamos el proyecto después de añadir la siguiente dependencia:

implementation 'com.google.firebase:firebase-database:16.0.6'

6.2. Usando Realtime Database en nuestro proyecto

Ahora vamos a crear una clase llamada Mark que representará las puntuaciones. Fíjate en que los atributos son públicos y están inicializados. Esto es para permitir la serialización y deserialización al tener acceso a tales atributos y a un constructor por defecto. También sobrescribirmos el método toString para mostrar las puntuaciones.

data class Mark(val name: String = "", val subject: String = "", val mark: Double = 0.0) {

    override fun toString() = name + "\t" + subject + "\t" + mark
}

Ahora vamos a modificar el layout activity_main.xml que puedes encontrar en res>layout. En él vamos a incluir los campos de texto necesarios y un botón para añadir la nota. Debajo de todo esto vamos a contar con un TextView en el que escribir las puntuaciones de la base de datos. En una aplicación real sería recomendable no listarlo con un TextView, pero aquí lo vamos a usar por simplificar el tutorial.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/title_textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/save_button"
        android:textAlignment="center"
        android:textSize="40sp"
        android:text="Notas de Alumnos"
        app:layout_constraintTop_toTopOf="parent"/>

    <EditText
        android:id="@+id/name_editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPersonName"
        android:hint="Nombre"
        app:layout_constraintTop_toBottomOf="@id/title_textView"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

    <EditText
        android:id="@+id/subject_editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPersonName"
        android:hint="Asignatura"
        app:layout_constraintTop_toBottomOf="@id/name_editText"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

    <EditText
        android:id="@+id/mark_editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="numberDecimal"
        android:hint="Nota"
        app:layout_constraintTop_toBottomOf="@id/subject_editText"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

    <Button
        android:id="@+id/save_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Guardar"
        app:layout_constraintTop_toBottomOf="@id/mark_editText"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

    <TextView
        android:id="@+id/list_textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/save_button"
        android:textAlignment="center"/>

</android.support.constraint.ConstraintLayout>

Lo último que nos queda por modificar es el MainActivity. Vamos a añadir un listener al botón para recuperar los datos del formulario e insertarlos en la base de datos y otro listener a la base de datos para escuchar cuando se añadan nuevos registros. Como nuestra aplicación no va a permitir borrar o modificar registros con esto sería suficiente. Además, si te das cuenta, con este listener nos es suficiente para listar el contenido existente en la base de datos al iniciar la aplicación, sin necesidad de añadir código adicional.

A parte de lo mencionado, voy a centrar la atención en el método push() que utilizamos al insertar una nueva nota. Este método crea un registro nuevo con una clave única y es recomendable utilizarlo para evitar errores y mejorar el rendimiento.

class MainActivity : AppCompatActivity() {

     private val marksRef = FirebaseDatabase.getInstance().getReference("marks")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        save_button.setOnClickListener { saveMarkFromForm() }

        marksRef.addChildEventListener(object : ChildEventListener {
            override fun onCancelled(databaseError: DatabaseError) {}
            override fun onChildMoved(dataSnapshot: DataSnapshot, previousName: String?) {}
            override fun onChildChanged(dataSnapshot: DataSnapshot, previousName: String?) {}
            override fun onChildRemoved(dataSnapshot: DataSnapshot) {}

            override fun onChildAdded(dataSnapshot: DataSnapshot, p1: String?) {
                val mark = dataSnapshot.getValue(Mark::class.java)
                if (mark != null) writeMark(mark)
            }
        })
    }

    private fun saveMarkFromForm() {
        val mark = Mark(
            name_editText.text.toString(),
            subject_editText.text.toString(),
            mark_editText.text.toString().toDouble()
        )
        marksRef.push().setValue(mark)
    }

    private fun writeMark(mark: Mark) {
        val text = list_textView.text.toString() + mark.toString() + "\n"
        list_textView.text = text
    }
}

Llegados a este punto, ya puedes iniciar la aplicación, añadir notas a alumnos y verificar en la consola que se añaden los registros.

Aplicación de ejemplo de Realtime DatabaseDatos insertados en la aplicación de ejemplo

7. Ejemplo con Cloud Firestore

Para el ejemplo de Cloud Firestore vamos a usar un ejemplo parecido al utilizado para Realtime Database. Vamos a crear una aplicación que nos permita añadir notas para alumnos, pero en vez de asignatura vamos a guardar el grupo al que están asignados.

Lo primero que vamos a hacer es añadir la dependencia necesaria a la configuración de Gradle a nivel de módulo y a sincronizar el proyecto.

implementation 'com.google.firebase:firebase-firestore:18.0.1'

A continuación, vamos a crear una clase Mark que represente la información a tratar. Nuevamente, los atributos los vamos a establecer como públicos e inicializados para proporcionar acceso a ellos y a un constructor por defecto que permita la serialización y deserialización.

class Mark(val name:String = "", val group:String = "", val mark:Double = 0.0){

    override fun toString()= "$name $group $mark"
}

El siguiente paso es modificar el layout main_activity.xml que se encuentra en res>layout para añadir los campos necesarios del formulario, el botón para guardarlo y un TextView que vamos a utilizar para listar las notas en la base de datos.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <EditText
            android:id="@+id/name_editText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:hint="Nombre"
            android:ems="10"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
    />

    <EditText
            android:id="@+id/group_editText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="text"
            android:hint="Grupo"
            android:ems="10"
            app:layout_constraintTop_toBottomOf="@id/name_editText"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
    />

    <EditText
            android:id="@+id/mark_editText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="numberDecimal"
            android:hint="Nota"
            android:ems="10"
            app:layout_constraintTop_toBottomOf="@id/group_editText"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
    />
    <Button
            android:id="@+id/save_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/mark_editText"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="Guardar"
    />
    <TextView 
            android:id="@+id/markList_textView"
            android:layout_width="match_parent" 
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/save_button"
            android:textAlignment="center"
    />
</android.support.constraint.ConstraintLayout>

Nuestro MainActivity debe añadir un listener al botón del layout para que se guarde la nota escrita al pulsarlo y otro a la colección de la base de datos para tratar cuando se añadan nuevos registros a la base de datos. Igual que pasaba con Realtime Database, este listener nos sirve también para añadir las notas ya existentes en la base de datos al iniciar la aplicación.

class MainActivity : AppCompatActivity() {

    private val marksCollection: CollectionReference

    init {
        FirebaseApp.initializeApp(this)
        marksCollection = FirebaseFirestore.getInstance().collection("marks")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        save_button.setOnClickListener {
            saveMark(
                Mark(
                    name_editText.text.toString(),
                    group_editText.text.toString(),
                    mark_editText.text.toString().toDouble()
                )
            )
        }

        addMarksListener()
    }

    private fun saveMark(mark: Mark) {
        marksCollection.add(mark).addOnSuccessListener {
            Toast.makeText(this, "Regsistro guardado", Toast.LENGTH_SHORT).show()
        }.addOnFailureListener {
            Toast.makeText(this, "Error guardando el registro", Toast.LENGTH_SHORT).show()
        }
    }

    private fun addMarksListener() {
        marksCollection.addSnapshotListener { snapshots, error ->
            if (error == null) {
                val changes = snapshots?.documentChanges
                if (changes != null) {
                    addChanges(changes)
                }
            } else {
                Toast.makeText(this, "Ha ocurrido un error leyendo las notas", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun addChanges(changes: List<DocumentChange>) {
        for (change in changes) {
            if (change.type == DocumentChange.Type.ADDED) {
                addToList(change.document.toObject(Mark::class.java))
            }
        }
    }

    private fun addToList(mark: Mark) {
        var text = markList_textView.text.toString()
        text += mark.toString() + "\n"
        markList_textView.text = text
    }
}

Una vez tenemos el MainActivity, ya podemos ejecutar nuestra aplicación y ver cómo se añaden los registros que insertemos.

Si a la hora de ejecutar la aplicación te encuentras problemas y has usado la integración automática de Firebase con Android Studio, es posible que se deba a la posición de la línea apply plugin: ‘com.google.gms.google-services’ en la configuración de Gradle. Por defecto Android Studio la coloca en la parte superior del fichero y ésta debe aparecer al final del mismo.

Aplicación de ejemplo de Cloud Firestore

¡Muchas gracias por haber leído hasta aquí!

8. Referencias

3 COMENTARIOS

  1. hola esta genial solo tengo una duda como vez los datos en la aplicación, como se hace la pantalla de vista de datos, o como consulto mi tabla de firebase sin ir a la consola

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad