Inicio Android Firebase para Android: Base de Datos en Tiempo Real (3)

Firebase para Android: Base de Datos en Tiempo Real (3)

por sgoliver
firebase-database-3

[mensaje-curso]

En el último artículo sobre la base de datos en tiempo real de Firebase ya aprendimos a suscribirnos a un nodo de nuestra base de datos para conocer su valor y ser notificado de sus cambios. Sin embargo nos dejamos en el tintero algo muy importante. ¿Qué ocurre cuando el contenido de un nodo de la base de datos no almacena datos independientes (como el ejemplo que utilizamos de cielo, temperatura y humedad) sino una lista de elementos (normalmente del mismo tipo)?

Con la técnica que ya aprendimos podríamos suscribirnos al nodo padre de la lista de elementos, pero cada vez que se añadiera, modificara o eliminara un elemento de la lista recibiríamos en el evento onDataChange() la lista completa de datos, lo que nos dificultaría saber el cambio concreto que se ha producido, además de incrementar enormemente el tráfico de información necesaria entre Firebase y nuestra aplicación.

Para solucionar esto, Firebase nos ofrece un método alternativo de suscribirnos a un nodo de la base de datos cuando queremos tratar el contenido de dicho nodo como una lista de elementos. Para ello utilizaremos un listener de tipo ChildEventListener (en vez del ValueEventListener que ya conocíamos). Con un listener de este tipo seremos notificados de 5 posibles eventos:

  • onChildAdded(). Se lanzará cada vez que se añada un nuevo elemento a la lista, y recibe como parámetro únicamente la información del nuevo elemento añadido. Adicionalmente, también se lanzará en el momento de suscribirnos a la lista, una vez por cada elemento que contenga la lista, de forma que podamos conocer el estado inicial de nuestra lista.
  • onChildChanged(). Se lanzará cada vez que un elemento de la lista (incluidos sus subelementos) sea modificado. Recibe como parámetro únicamente la información del elemento que ha sido modificado.
  • onChildRemoved(). Se lanzará cada vez que se elimine un elemento de la lista. Recibe como parámetro la información del elemento eliminado.
  • onChildMoved(). Se lanzará cuando un elemento de una lista ordenada cambie de posición. No nos preocuparemos por ahora de este evento, volveremos a él más adelante.
  • onCancelled(). Se lanzará cuando se produzca cualquier error en la lectura de los datos.

Con estos eventos nos aseguramos de minimizar la información recibida desde Firebase, que se limitará a únicamente los nodos de la lista que cambien, y no la lista completa cada vez que se produce alguna actualización. Aclarar además, que la información recibida en estos eventos nos llegará como siempre en forma de objeto DataSnapshot, que ya aprendimos a gestionar en el artículo pasado.

Veamos un ejemplo sencillo. Usaré como base los proyectos de Firebase y Android que ya creamos en el primer artículo sobre Firebase. Vamos a crear desde la consola de Firebase una lista muy sencilla de elementos:

datos-dias-semana

Para acceder a esta lista desde nuestra aplicación Android el procedimiento sería muy similar al ya visto en el artículo anterior, con la única salvedad de que utilizaremos un listener de tipo ChildEventListener. Crearemos primero la referencia a la base de datos y posteriormente asignaremos el listener a la referencia implementando la lógica necesaria dentro de cada evento:

DatabaseReference dbDiasSemana =
    FirebaseDatabase.getInstance().getReference()
        .child("dias-semana");

ChildEventListener childEventListener = new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAGLOG, "onChildAdded: {" + dataSnapshot.getKey() + ": " + dataSnapshot.getValue() + "}");
    }

    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAGLOG, "onChildChanged: {" + dataSnapshot.getKey() + ": " + dataSnapshot.getValue() + "}");
    }

    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {
        Log.d(TAGLOG, "onChildRemoved: {" + dataSnapshot.getKey() + ": " + dataSnapshot.getValue() + "}");
    }

    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAGLOG, "onChildMoved: " + dataSnapshot.getKey());
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.e(TAGLOG, "Error!", databaseError.toException());
    }
};

dbDiasSemana.addChildEventListener(childEventListener);

Para este primer ejemplo utilizaré simplemente mensajes de log para mostrar los resultados.

Si ejecutamos la aplicación y revisamos el log desde Android Studio podremos comprobar cómo tras suscribirnos por primera vez a la lista recibiremos un evento onChildAdded() por cada elemento que contiene inicialmente la lista:

log-dias-semana-1

Si añadimos ahora un nuevo elemento desde la consola de Firebase, por ejemplo {dia6: «Sábado»}, veremos cómo inmediatamente recibimos un nuevo evento onChildAdded() en nuestra aplicación:

log-dias-semana-2

Igualmente, al modificar o eliminar cualquier dato de la lista veremos los cambios reflejados en el log:

log-dias-semana-3

Vemos por tanto lo sencillo que resulta gestionar desde nuestras aplicaciones Android listas de elementos almacenadas en la base de datos de Firebase. Con la información recibida en estos eventos podríamos realizar cualquier tarea relacionada con los elementos de la lista. Lo más habitual será por supuesto mostrar esta información al usuario, normalmente en controles de tipo ListView o RecyclerView. Para ello tendríamos que mantener actualizado el contenido de la lista en alguna estructura de datos interna (tipo array, lista, …) a partir de la información recibida en los eventos del ChildEventListener, construir los adaptadores necesarios, y asignarlos al ListView/RecyclerView encargado de mostrar los datos al usuario. Como parte del curso tenéis disponibles varios artículos sobre cómo utilizar los controles ListView y RecyclerView. Aunque no sería excesivamente complicado hacerlo de esta forma, afortunadamente Firebase nos proporciona una librería auxiliar, llamada FirebaseUI, con la que (entre otras cosas) podremos ahorrarnos gran parte del código necesario para hacer esto.

FirebaseUI nos permite asociar fácilmente a un control ListView o RecyclerView una referencia a una lista de elementos de una base de datos Firebase. De esta forma, el control se actualizará automáticamente cada vez que se produzca cualquier cambio en la lista, sin tener que gestionar manualmente por nuestra parte los eventos de la lista, ni tener que almacenar en una estructura paralela la información, ni tener que construir gran parte de los adaptadores necesarios… en resumen, ahorrando muchas líneas de código y evitando muchos posibles errores.

Para utilizar FirebaseUI lo primero que tendremos que hacer será añadir la referencia a la librería en el fichero build.gradle de nuestro módulo principal. Hay que tener en cuenta que cada versión de FirebaseUI es compatible únicamente con una versión concreta de Firebase, por lo que debemos asegurar que las versiones utilizadas de ambas librerías son coherentes. En la página de FirebaseUI tenéis disponible la tabla de compatibilidades entre versiones. En mi caso particular utilizaré la versión 1.0.0 de FirebaseUI, por lo que utilizaré la versión 9.8.0 de las librerías de Firebase:

dependencies {

    //...

    //Librería de Firebase (para Base de Datos)
    compile 'com.google.firebase:firebase-database:9.8.0'

    //Librería de FirebaseUI (para Base de Datos)
    compile 'com.firebaseui:firebase-ui-database:1.0.0'
}

Hecho esto, ya podríamos empezar a utilizar las distintas funcionalidades ofrecidas por la librería. Pero para no utilizar un ejemplo tan sencillo como el de los días de la semana, vamos a continuar con nuestra fantástica aplicación de predicciones meteorológicas que ya comenzamos en el artículo anterior. Vamos a reestructurar nuestros datos desde la consola de Firebase para almacenar la información meteorológica de varios días, en vez de solo el día actual como teníamos antes. Para ello incluiremos la fecha como parte de cada predicción:

datos-predicciones

Algo importante y que antes no comentamos es que cada elemento de la lista debe tener una clave única. En la lista anterior utilizamos claves del tipo «dia1«, «dia2«, … y en este nuevo ejemplo estoy utilizando como clave de cada elemento la fecha del día codificada en formato «yyyymmdd«.  Por ahora no nos preocuparemos mucho de esto ya que nos estamos limitando a leer la información de la base de datos, pero cuando veamos cómo escribir tendremos que tener en cuenta este tema. Pero no adelantemos acontecimientos, por ahora nos conformamos con saber que debe tratarse de una clave única.

Ya tenemos los datos, ahora debemos decidir cómo queremos mostrarlos al usuario. Para el ejemplo no voy a complicar mucho este tema. Mostraremos los datos en una lista donde cada elemento estará formado por dos líneas de texto: la superior mostrará la fecha y la inferior los datos de la predicción (cielo, temperatura, humedad) uno tras otro. Empezaremos creando el layout XML para mostrar cada elemento de la lista de la forma indicada:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView android:id="@+id/lblFecha"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <TextView android:id="@+id/lblCielo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="5dp" />

        <TextView android:id="@+id/lblTemperatura"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="5dp" />

        <TextView android:id="@+id/lblHumedad"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</LinearLayout>

Lo siguiente será decidir qué tipo de control vamos a utilizar para mostrar los datos. FirebaseUI ofrece soporte tanto para el control ListView como para el más actual RecyclerView. En mi caso de ejemplo utilizaré RecyclerView, aunque en la web de FirebaseUI podéis encontrar el procedimiento para utilizar un ListView, que será muy similar al que veremos ahora.

Para utilizar un RecyclerView vamos a empezar creando un ViewHolder personalizado que gestione los datos de nuestras predicciones. Si no has oido hablar antes del término ViewHolder o quieres repasar cómo funciona en general un control RecyclerView te recomiendo consultar, antes de seguir con este tema, el capítulo del curso dedicado al control RecyclerView.

public class PrediccionHolder extends RecyclerView.ViewHolder {
    private View mView;

    public PrediccionHolder(View itemView) {
        super(itemView);
        mView = itemView;
    }

    public void setFecha(String fecha) {
        TextView field = (TextView) mView.findViewById(R.id.lblFecha);
        field.setText(fecha);
    }

    public void setCielo(String cielo) {
        TextView field = (TextView) mView.findViewById(R.id.lblCielo);
        field.setText(cielo);
    }

    public void setTemperatura(String temp) {
        TextView field = (TextView) mView.findViewById(R.id.lblTemperatura);
        field.setText(temp);
    }

    public void setHumedad(String hum) {
        TextView field = (TextView) mView.findViewById(R.id.lblHumedad);
        field.setText(hum);
    }
}

En este ViewHolder, además de su constructor, implementaremos un método set para cada uno de nuestros datos de la predicción. Cada uno de estos métodos recuperarán una referencia a su control correspondiente del layout que hemos creado antes para los elementos de la lista y asignarán su contenido a partir del dato recibido como parámetro.

El siguiente paso será crear un adaptador para nuestro RecyclerView. FirebaseUI nos facilita este paso proporcionándonos una clase genérica, llamada FirebaseRecyclerAdapter, que podemos utilizar con nuestro ViewHolder personalizado que acabamos de crear y la clase java que utilicemos para encapsular la información de cada elemento de la lista. Recordemos que en el artículo anterior ya creamos esta clase, llamada Prediccion, con los tres datos necesarios. Para este ejemplo la ampliaremos con el nuevo dato de fecha.

public class Prediccion {
    private String cielo;
    private long temperatura;
    private double humedad;
    private String fecha;

    public Prediccion() {
        //Es obligatorio incluir constructor por defecto
    }

    public Prediccion(String fecha, String cielo, long temperatura, double humedad)
    {
        this.fecha = fecha;
        this.cielo = cielo;
        this.temperatura = temperatura;
        this.humedad = humedad;
    }

    public String getFecha() {
        return fecha;
    }

    public void setFecha(String fecha) {
        this.fecha = fecha;
    }

    public String getCielo() {
        return cielo;
    }

    public void setCielo(String cielo) {
        this.cielo = cielo;
    }

    public long getTemperatura() {
        return temperatura;
    }

    public void setTemperatura(long temperatura) {
        this.temperatura = temperatura;
    }

    public double getHumedad() {
        return humedad;
    }

    public void setHumedad(double humedad) {
        this.humedad = humedad;
    }

    @Override
    public String toString() {
        return "Prediccion{" +
                "fecha='" + fecha + '\'' +
                ", cielo='" + cielo + '\'' +
                ", temperatura=" + temperatura +
                ", humedad=" + humedad +
                '}';
    }
}

Con esto ya podemos crear un objeto de tipo FirebaseRecyclerAdapter, personalizada para nuestras clases Prediccion y PrediccionHolder,  que recibirá como parámetros el objeto clase de nuestra Predicción (Prediccion.class), el ID del layout de los elementos de la lista (en mi caso lo llamé item_lista.xml), el objeto clase de nuestro ViewHolder (PrediccionHolder.class) y la referencia al nodo de la base de datos que contiene la lista de elementos que queremos mostrar en el control. Adicionalmente, sobrescribiremos el método populateViewHolder() para asignar cada uno de los datos de la predicción con su método correspondiente del ViewHolder. Por último asignaremos el adaptador al control RecyclerView.

Veamos el código completo, que es mucho más sencillo de escribir que de explicar:

DatabaseReference dbPredicciones =
    FirebaseDatabase.getInstance().getReference()
        .child("predicciones");

RecyclerView recycler = (RecyclerView) findViewById(R.id.lstPredicciones);
recycler.setHasFixedSize(true);
recycler.setLayoutManager(new LinearLayoutManager(this));

FirebaseRecyclerAdapter mAdapter =
    new FirebaseRecyclerAdapter<Prediccion, PrediccionHolder>(
        Prediccion.class, R.layout.item_lista, PrediccionHolder.class, dbPredicciones) {

    @Override
    public void populateViewHolder(PrediccionHolder predViewHolder, Prediccion pred, int position) {
        predViewHolder.setFecha(pred.getFecha());
        predViewHolder.setCielo(pred.getCielo());
        predViewHolder.setTemperatura(pred.getTemperatura() + "ºC");
        predViewHolder.setHumedad(pred.getHumedad() + "%");
    }
};

recycler.setAdapter(mAdapter);

Un último detalle a tener en cuenta es que, antes de finalizar nuestra actividad debemos indicar a Firebase que ya no queremos seguir recibiendo los eventos asociados a nuestra lista. Para ello, utilizaremos el método cleanup() sobre el adaptador creado, por ejemplo dentro del evento onDestroy() de nuestra actividad principal.

@Override
protected void onDestroy() {
    super.onDestroy();
    mAdapter.cleanup();
}

Y listo, ya habríamos finalizado los pasos necesarios para mostrar y mantener actualizados los datos de la lista de predicciones en nuestra aplicación Android. Si ejecutamos la aplicación debemos ver inmediatamente los elementos actuales de la lista, y si realizamos cualquier cambio en los datos desde la consola de Firebase éstos deberían reflejarse directamente en la aplicación.

demo-android-firebase-ui

Con esto finalizaríamos este tercer artículo de la serie sobre la base de datos de Firebase, el segundo dedicado a la lectura de datos desde una aplicación Android. En breve seguiremos con más temas interesantes sobre Firebase.

Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la página del curso en GitHub.

[mensaje-curso]

También te puede interesar

8 comentarios

Firebase para Android [Serie] | sgoliver.net 21/11/2016 - 19:50

[…] Base de datos Firebase en Android (3) [Noviembre 2016] […]

Responder
Alvaro Neira 22/11/2016 - 4:29

Excelentes tutoriales! muy bien explicados… crearas algún tutorial para subir información a la base de datos desde la misma app? seria bastante bueno.

Responder
martin 25/11/2016 - 16:55

Hola, felicitaciones por el tutorial. Ya que has incluido fecha, cual te parece la mejor practica para el manejo de fechas dentro de firebase?

Timestamp, long, un hashmap?

Responder
Ambra 01/12/2016 - 11:56

Muchas gracias por tantísimo contenido de tantísima calidad, todo este trabajo nos supone una ayuda enorme a tus lectores. Soy estudiante de desarrollo de software y mi profesor de Android te menciona siempre (ya desde el primer día de clase nos dio tu web como referencia absoluta y nos dijo que no encontraríamos nada mejor), ahora estoy pensando en la opción Firebase para una app que será mi proyecto final y lo primero que he hecho es venir a ver qué tenías tú que decir sobre el tema. Tus primeros artículos me han aclarado mucho las cosas y ya estoy deseando aprender más, ¡gracias de nuevo!

Responder
junior 03/12/2016 - 8:56

Hola, felicitarte por tan excelente tutorial.
me pregunto como se harían búsquedas?, digamos que tenemos un Search y que este busque todos los «SOLEADOS» de tu bd y que a su ves los muestre en un RecyclerView.

Responder
Firebase para Android: Base de Datos en Tiempo Real (4) | sgoliver.net 06/12/2016 - 20:18

[…] los dos artículos anteriores (parte 2 y parte 3) de la serie, nos hemos ocupado de repasar las diferentes formas que tenemos disponibles para leer […]

Responder
hieudev 25/06/2017 - 13:44

gracias por tus tutoriales!

me surgen unas dudas con respecto a las bd firebase.

Y si tengo que buscar entre una lista grande (millones) de ubicaciones las ubicaciones más cercanas? cual sería el procedimiento más rápido y eficiente?

*cada usuario de la app ha añadido su ubicación en la bd.

Responder
hernan 19/03/2018 - 15:11

Hola, queria hacer una pregunta, usar firebase como almacenamiento, crea una base de datos por cada usuario, o una base de datos de todos los usuarios de la aplicacion?
Es decir, yo necesito guardar datos personales de cada usuario y no cargar una base de datos general con todos los usuarios y recuperarlos con un campo clave.
No se si se entendio.

Responder

Dejar un comentario

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información. Aceptar Más Información

Política de Privacidad y Cookies