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

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

por sgoliver
firebase-database-5

[mensaje-curso]

En los artículos anteriores de esta serie nos hemos centrado principalmente en los distintos mecanismos que nos ofrece Firebase para leer o consultar la información almacenada en la base de datos desde nuestras aplicaciones Android. Aprendimos a leer datos individuales y listas de elementos, vimos cómo suscribirnos a una determina ruta de la base de datos para ser notificados cuando haya cambios en la información, aprendimos a utilizar la librería FirebaseUI para vincular controles Android de tipo lista (ListView/RecyclerView) a la base de datos, y descubrimos cómo filtrar y ordenar la información recuperada.

Sin embargo no solo de leer vive el hombre, y habitualmente también nos será necesario escribir o actualizar información en la base de datos para ponerla a buen recaudo y/o hacerla disponible a otros clientes implicados (recordemos que con Firebase, cada vez que escribamos algo en la base de datos, dicha información se sincronizará automáticamente y al instante en todos los clientes suscritos). Éste será el tema central de este nuevo artículo de la serie.

Empezaremos por la operación más básica, la de escribir un dato sencillo (clave + valor simple) a nuestra base de datos. Para hacer esto, simplemente tendremos que construir una referencia, como siempre de tipo DatabaseReference, a la clave que queremos escribir (ya existente o no) y utilizar sobre ella el método setValue(valor) para establecer su valor. Así, siguiendo con el ejemplo de los días de la semana utilizado en artículos anteriores, si quisiéramos añadir un nuevo elemento a nuestra lista de días de la semana podríamos hacer lo siguiente:

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

dbRef.child("dia7").setValue("domingo");

Como podéis comprobar, la referencia sobre la que vamos a escribir podemos obtenerla directamente al crear el objeto DatabaseReference, o bien «navegando» con el método child() sobre una referencia obtenida previamente.

Hay que tener muy en cuenta que con setValue() podemos tanto insertar nuevos elementos como actualizar los ya existentes. Así, si la clave sobre la que escribimos no existe estaremos insertando un nuevo nodo, y si ya existía estaremos sobrescribiendo su valor (y ojo, esto incluye la eliminación de todos sus elementos descendientes si los tenía).

Los posibles valores que acepta el método setValue() son los siguientes:

  • String
  • Long
  • Double
  • Boolean
  • Map<String, Object>
  • List<Object>
  • Objeto Java personalizado

Los cuatro primeros no necesitan mayor explicación, son tipos sencillos, por lo que nos detendremos tan solo en los tres siguientes.

El método setValue() puede recibir como parámetro un objeto de tipo Map que contenga una serie de pares clave-valor que se insertarán como hijos de la clave sobre la que se está escribiendo. Esto nos puede facilitar la vida a la hora de escribir estructuras de árbol complejas, ya que nos evita tener que escribir nodo a nodo. Así, imaginemos por ejemplo que quisiéramos insertar en nuestra lista de días de la semana un nodo de este tipo:

  • «dia7»:
    • «periodo-1»: «domingo-mañana»
    • «periodo-2»: «domingo-tarde»
    • «periodo-3»: «domingo-noche»

Podríamos crear los tres nodos hijo previamente en forma de objeto Map e insertarlos de una sola vez utilizando el método setValue().

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

Map<String, String> domingo = new HashMap<>();
domingo.put("periodo-1", "domingo-mañana");
domingo.put("periodo-2", "domingo-tarde");
domingo.put("periodo-3", "domingo-noche");

dbRef.child("dia7").setValue(domingo);

Si consultamos el nuevo nodo creado en la consola de Firebase veremos que efectivamente el resultado ha sido el esperado:

escribir-map

El siguiente tipo admitido es una lista de objetos de tipo List. Si pasamos a setValue() una lista de objetos de este tipo, Firebase creará por nosotros una lista de elementos donde cada uno de ellos tendrá como clave su posición en la lista y como valor el dato de la lista situado en dicha posición. Veamos un ejemplo:

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

List<String> domingo = new LinkedList<>();
domingo.add("mañana");
domingo.add("tarde");
domingo.add("noche");

dbRef.child("dia7").setValue(domingo);

Consultando esta información desde la consola de Firebase veremos nuestra nueva lista en el formato indicado:

escribir-list

La última opción disponible es utilizar objetos Java personalizados. En un artículo pasado vimos como Firebase nos permite recuperar información de la base de datos y mapearla directamente sobre un objeto Java propio que tenga la misma estructura. Pues bien, a la hora de escribir información sobre la base de datos vamos a disponer de las mismas facilidades. El único requisito será como siempre que nuestra clase Java personalizada tenga un constructor por defecto y disponga de getters y setters públicos para los datos que queremos escribir a la base de datos. Siguiendo con el ejemplo de las predicciones meteorológicas, recordemos que ya construimos la siguiente clase para encapsular nuestros datos:

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

Firebase nos va a permitir construir un objeto de este tipo y pasarlo directamente al método setValue() para utilizarlo como valor de la clave a escribir.

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

Prediccion pred =
    new Prediccion("01/12/2016", "Despejado", 29, 35);

dbRef.child("20161201").setValue(pred);

Podemos comprobar en la consola de Firebase que el nodo insertado es el que esperábamos.

escribir-objeto

El método setValue() es la forma más sencilla de insertar o actualizar información de la base de datos, pero nos permite actualizar sólo una clave por operación. Firebase nos ofrece un método alternativo que nos permite actualizar varias claves al mismo tiempo, y además de forma atómica, es decir, que nos garantizará que se realizan correctamente todas las actualizaciones o bien no se realizará ninguna. Este método se llama updateChildren() y recibe como parámetro un objeto de tipo Map con una serie de pares clave-valor donde cada clave es la ruta a una de las referencias que queremos actualizar y el valor será el valor (valga la redundancia) que queremos asignar a dicha referencia.

Así, si por ejemplo queremos actualizar de forma simultánea la temperatura y humedad de una predicción de nuestra lista, podríamos hacer lo siguiente:

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

Map<String, Object> actualizacion = new HashMap<>();
actualizacion.put("/temperatura", 29);
actualizacion.put("/humedad", 34);

dbRef.child("20161120")
    .updateChildren(actualizacion);

Del ejemplo anterior hacer notar que las diferentes rutas indicadas deben comenzar por el caracter «/». También importante conocer que las rutas incluidas no tienen por qué pertenecer al mismo nodo, sino que pueden indicarse claves de diferentes nodos de nuestra base de datos. Por ejemplo podríamos actualizar de forma simultánea la temperatura de un día y la humedad de otro distinto:

DatabaseReference dbRef =
    FirebaseDatabase.getInstance().getReference();

Map<String, Object> actualizacion2 = new HashMap<>();
actualizacion2.put("/20161120/temperatura", 25);
actualizacion2.put("/20161121/humedad", 31);

dbRef.child("predicciones")
    .updateChildren(actualizacion2);

Tanto el método setValue() como updateChildren() pueden recibir de forma opcional un segundo parámetro con un listener de tipo CompletionListener, cuyo método onComplete() se llamará automáticamente cuando la operación se dé por finalizada (correctamente o con algún error). Este método recibe como primer parámetro un objeto de tipo DatabaseError que contendrá el mensaje de error en caso de que la operación no haya finalizado correctamente, o será null en caso de haberse finalizado sin errores. Veamos un ejemplo:

Prediccion pred = 
    new Prediccion("20161201", "Despejado", 29, 35);

dbRef.child("predicciones")
    .setValue(pred, new DatabaseReference.CompletionListener(){
        public void onComplete(DatabaseError error, DatabaseReference ref) {
            if(error == null)
                Log.i(LOGTAG, "Operación OK");
            else
                Log.e(LOGTAG, "Error: " + error.getMessage());
        }
});

Vamos ahora como otro tema importante que habíamos dejado pendiente en artículos anteriores. Hasta ahora, cada vez que hemos creado o insertado nuevos datos en una lista de elementos en nuestra base de datos Firebase hemos utilizado claves arbitrarias elegidas o construidas por nosotros, por ejemplo: «dia1», «dia2», … en nuestro ejemplo de los días de la semana, o «20161122», «20161123», … en el ejemplo de las predicciones. Aunque esto es perfectamente válido, en la práctica nos obliga a mantener cierto control sobre las claves ya utilizadas para poder asegurar que no utilizamos claves duplicadas o incorrectas en los nuevos elementos que añadamos a la lista. También nos obliga a implementar la lógica necesaria para generar dichas claves personalizadas.

Firebase nos ofrece un método alternativo que nos facilitará las cosas cuando no necesitemos personalizar a tal nivel las claves de los elementos de nuestras listas. Para ello nos ofrece el método push() que generará por nosotros una clave única y nos devolverá una referencia a ella para que podamos establecer su valor. Estas claves automáticas se basan en el timestamp actual (fecha-hora) por lo que nos asegurará que los elementos de la lista quedarán ordenados por clave de forma cronológica. Veamos un ejemplo para ver que aspecto tienen las claves generadas, vamos a insertar nuevas predicciones a nuestra lista utilizando el método push().

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

Prediccion p1 = new Prediccion("02/12/2016", "Soleado", 29, 35);
Prediccion p2 = new Prediccion("03/12/2016", "Nublado", 23, 52);

dbPredicciones.push().setValue(p1);
dbPredicciones.push().setValue(p2);

Si observamos los nuevos datos insertados desde la consola de Firebase veremos lo siguiente:

escribir-push

Como podéis comprobar, las claves generadas no nos dicen nada a simple vista, pero nos aseguran que si ordenamos la lista por clave (consulta los métodos de ordenación de Firebase) los elementos quedarán ordenados en orden cronológico de forma ascendente.

En ocasiones puede ser útil conocer la clave que ha asignado Firebase al nuevo elemento insertado, por ejemplo para insertar referencias a dicho elemento en otros lugares de la base de datos. Para ello podemos aprovechar que el método push() devuelve una referencia al nuevo elemento creado, por lo que podemos utilizar su método getKey() para conocer la clave que le ha asignado. Por ejemplo:

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

Prediccion p3 = new Prediccion("02/12/2016", "Tormenta", 20, 54);

String claveP3 = dbPredicciones.push(p3).getKey();

Y nos queda un último tema pendiente en relación a la actualización de información sobre la base de datos, la eliminación de contenido. Para eliminar un nodo de nuestra base de datos tenemos dos posibilidades:

  • Utilizar los métodos setValue() o updateChildren() para asignarle el valor null.
  • Utilizar el método removeValue() sobre la referencia que queremos eliminar.

De esta forma, eliminar por ejemplo uno de nuestros días de la semana sería tan sencillo como:

DatabaseReference dbRef = 
    FirebaseDatabase.getInstance().getReference();

//Alternativa 1
dbRef.child("dias-semana").child("dia6").setValue(null);

//Alternativa 2
dbRef.child("dias-semana").child("dia7").removeValue();

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 11/12/2016 - 20:12

[…] Base de datos Firebase en Android (5) [Diciembre 2016] […]

Responder
Alvaro Neira Rojas 17/12/2016 - 6:32

Obtener la key del ultimo valor ingresado no funciona
String claveP3 = dbPredicciones.push(p3).getKey();
alguna sugerencia?

Responder
David 18/12/2016 - 20:06

Cuando nos enseña a «utilizar su método getKey() para conocer la clave que le ha asignado.», pone un ejemplo que luego no esta subido al Git.
Lo he intentado practicar pero no conseguia recuperar la clave timestamp correctamente. Finalmente creo que lo he resuelto. Lo importante es seguir un correcto orden de ejecución. Primero obtener la referencia con push y depues «setear» el objeto (y si se quiere con la referencia incorporada a uno de sus campos)

Ejemplo:

// 1) Obtener la referencia Firebase a la que queremos asignar el objeto.

DatabaseReference dbPronostico=
FirebaseDatabase.getInstance().getReference().child(«predicciones»);

// 2) Generar espacio «dentro del child» con push() y obtener la clave con getKey().

String key = dbPronostico.child(«predicciones»).push().getKey();

// 3) Utilizamos la clave en el constructor del objeto (de la clase Pronostico creada en archivo java aparte).
Pronostico ap=new Pronostico(29,1,user, key);

// 4) se rellena con valores la posición ocupada por la clave con el objeto ap con el metodo setValue y no con push.
dbPronostico.child(key).setValue(ap);

No se si habrá algún error pues he adaptado mi código al ejemplo de
Predicciones. A mi me funciona.

Encontré debate sobre este asunto aqui:

http://stackoverflow.com/questions/37094631/get-the-pushed-id-for-specific-value-in-firebase-android/39492322#39492322

Pero creo que esta solución es distinta a lo que ahí se dice.

Espero que sirva para otros como su curso me está sirviendo a mi. Aprovecho para darle las gracias.

Responder
Juanma 05/01/2017 - 1:18

Hola, soy estudiante de segundo año, me gustaria saber si con Firebase, se podria hacer un servicio de login tipo por ejemplo Instagram, gracias y gran tutorial.

Responder
Hans Rolando Ramos Aguilar 02/04/2017 - 4:07

Si tienen problema con el obtener el uid del nodo solo usen databaserefenrece.getreference(position)
esto le retornara el uid del nodo que se muestra en un recyclerview

Responder
J Pablo 03/07/2017 - 14:15

Saludos y buen tutorial,
Al ejemplo de las predicciones le agregué un CheckBox al item_list.xml, cuando carga el recycler me muestra las predicciones en la lista más el CkeckBox a la derecha. Mi pregunta es, como podría modificar el cielo entre Nublado/Soleado al activar/desactivar el CheckBox?. Agradezco mucho su ayuda.

Responder
lujan 27/06/2018 - 19:17

Hola, como seria para borrar un item(a poder ser por valor y no por index) de un array en database?

Responder
david 09/03/2019 - 1:23

Hola muy buena explicación se entiende

Quisiera que me explicaras como puedo leer lo que tengo en la base de datos firebase y guardar la info en una llista enlazada
gracias

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