Leer y escribir datos en la Web

(Opcional) Prototipo y prueba con Firebase Local Emulator Suite

Antes de hablar sobre cómo su aplicación lee y escribe en Realtime Database, presentemos un conjunto de herramientas que puede usar para crear prototipos y probar la funcionalidad de Realtime Database: Firebase Local Emulator Suite. Si está probando diferentes modelos de datos, optimizando sus reglas de seguridad o trabajando para encontrar la forma más rentable de interactuar con el back-end, poder trabajar localmente sin implementar servicios en vivo puede ser una gran idea.

Un emulador de base de datos en tiempo real es parte de Local Emulator Suite, que permite que su aplicación interactúe con el contenido y la configuración de su base de datos emulada, así como, opcionalmente, con los recursos de su proyecto emulado (funciones, otras bases de datos y reglas de seguridad).

Usar el emulador de Realtime Database implica solo unos pocos pasos:

  1. Agregar una línea de código a la configuración de prueba de su aplicación para conectarse al emulador.
  2. Desde la raíz del directorio de su proyecto local, ejecute firebase emulators:start .
  3. Realizar llamadas desde el código prototipo de su aplicación usando un SDK de plataforma Realtime Database como de costumbre, o usando la API REST de Realtime Database.

Está disponible un tutorial detallado sobre la base de datos en tiempo real y las funciones de la nube . También deberías echar un vistazo a la introducción a Local Emulator Suite .

Obtener una referencia de base de datos

Para leer o escribir datos de la base de datos, necesita una instancia de firebase.database.Reference :

Web modular API

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web namespaced API

var database = firebase.database();

Escribir datos

Este documento cubre los conceptos básicos de la recuperación de datos y cómo ordenar y filtrar datos de Firebase.

Los datos de Firebase se recuperan adjuntando un detector asincrónico a firebase.database.Reference . El oyente se activa una vez para el estado inicial de los datos y nuevamente cada vez que cambian los datos.

Operaciones básicas de escritura

Para operaciones básicas de escritura, puede usar set() para guardar datos en una referencia específica, reemplazando cualquier dato existente en esa ruta. Por ejemplo, una aplicación de blogs sociales podría agregar un usuario con set() de la siguiente manera:

Web modular API

import { getDatabase, ref, set } from "firebase/database";

function writeUserData(userId, name, email, imageUrl) {
  const db = getDatabase();
  set(ref(db, 'users/' + userId), {
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

Web namespaced API

function writeUserData(userId, name, email, imageUrl) {
  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

El uso set() sobrescribe los datos en la ubicación especificada, incluidos los nodos secundarios.

leer datos

Escuche eventos de valor

Para leer datos en una ruta y escuchar cambios, use onValue() para observar eventos. Puede utilizar este evento para leer instantáneas estáticas del contenido en una ruta determinada, tal como existían en el momento del evento. Este método se activa una vez cuando se adjunta el oyente y nuevamente cada vez que cambian los datos, incluidos los secundarios. A la devolución de llamada del evento se le pasa una instantánea que contiene todos los datos en esa ubicación, incluidos los datos secundarios. Si no hay datos, la instantánea devolverá false cuando llame exists() y null cuando llame val() .

El siguiente ejemplo muestra una aplicación de blogs sociales que recupera el recuento de estrellas de una publicación de la base de datos:

Web modular API

import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const starCountRef = ref(db, 'posts/' + postId + '/starCount');
onValue(starCountRef, (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

Web namespaced API

var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

El oyente recibe una snapshot que contiene los datos en la ubicación especificada en la base de datos en el momento del evento. Puede recuperar los datos de la snapshot con el método val() .

Leer datos una vez

Leer datos una vez con get()

El SDK está diseñado para gestionar interacciones con servidores de bases de datos, ya sea que su aplicación esté en línea o fuera de línea.

Generalmente, debe utilizar las técnicas de eventos de valor descritas anteriormente para leer datos y recibir notificaciones sobre las actualizaciones de los datos desde el backend. Las técnicas de escucha reducen su uso y facturación y están optimizadas para brindar a sus usuarios la mejor experiencia cuando se conectan y desconectan.

Si necesita los datos solo una vez, puede usar get() para obtener una instantánea de los datos de la base de datos. Si por algún motivo get() no puede devolver el valor del servidor, el cliente sondeará el caché de almacenamiento local y devolverá un error si aún no se encuentra el valor.

El uso innecesario de get() puede aumentar el uso del ancho de banda y provocar una pérdida de rendimiento, lo que se puede evitar utilizando un escucha en tiempo real como se muestra arriba.

Web modular API

import { getDatabase, ref, child, get } from "firebase/database";

const dbRef = ref(getDatabase());
get(child(dbRef, `users/${userId}`)).then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

Web namespaced API

const dbRef = firebase.database().ref();
dbRef.child("users").child(userId).get().then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

Leer datos una vez con un observador.

En algunos casos, es posible que desee que el valor de la caché local se devuelva inmediatamente, en lugar de buscar un valor actualizado en el servidor. En esos casos, puede utilizar once() para obtener los datos del caché del disco local inmediatamente.

Esto es útil para datos que solo deben cargarse una vez y no se espera que cambien con frecuencia ni requieran una escucha activa. Por ejemplo, la aplicación de blogs de los ejemplos anteriores utiliza este método para cargar el perfil de un usuario cuando comienza a escribir una nueva publicación:

Web modular API

import { getDatabase, ref, onValue } from "firebase/database";
import { getAuth } from "firebase/auth";

const db = getDatabase();
const auth = getAuth();

const userId = auth.currentUser.uid;
return onValue(ref(db, '/users/' + userId), (snapshot) => {
  const username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
}, {
  onlyOnce: true
});

Web namespaced API

var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => {
  var username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
});

Actualizar o eliminar datos

Actualizar campos específicos

Para escribir simultáneamente en hijos específicos de un nodo sin sobrescribir otros nodos hijos, utilice el método update() .

Al llamar update() , puede actualizar los valores secundarios de nivel inferior especificando una ruta para la clave. Si los datos se almacenan en varias ubicaciones para escalar mejor, puede actualizar todas las instancias de esos datos mediante la distribución en abanico de datos .

Por ejemplo, una aplicación de blogs sociales podría crear una publicación y actualizarla simultáneamente a la fuente de actividad reciente y a la fuente de actividad del usuario que publica usando un código como este:

Web modular API

import { getDatabase, ref, child, push, update } from "firebase/database";

function writeNewPost(uid, username, picture, title, body) {
  const db = getDatabase();

  // A post entry.
  const postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  const newPostKey = push(child(ref(db), 'posts')).key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  const updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return update(ref(db), updates);
}

Web namespaced API

function writeNewPost(uid, username, picture, title, body) {
  // A post entry.
  var postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  var newPostKey = firebase.database().ref().child('posts').push().key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  var updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return firebase.database().ref().update(updates);
}

Este ejemplo usa push() para crear una publicación en el nodo que contiene publicaciones para todos los usuarios en /posts/$postid y simultáneamente recupera la clave. Luego, la clave se puede usar para crear una segunda entrada en las publicaciones del usuario en /user-posts/$userid/$postid .

Con estas rutas, puede realizar actualizaciones simultáneas en varias ubicaciones en el árbol JSON con una sola llamada a update() , como en este ejemplo se crea la nueva publicación en ambas ubicaciones. Las actualizaciones simultáneas realizadas de esta manera son atómicas: o todas las actualizaciones tienen éxito o todas fallan.

Agregar una devolución de llamada de finalización

Si desea saber cuándo se confirmaron sus datos, puede agregar una devolución de llamada de finalización. Tanto set() como update() reciben una devolución de llamada de finalización opcional que se llama cuando la escritura se ha confirmado en la base de datos. Si la llamada no tuvo éxito, a la devolución de llamada se le pasa un objeto de error que indica por qué ocurrió la falla.

Web modular API

import { getDatabase, ref, set } from "firebase/database";

const db = getDatabase();
set(ref(db, 'users/' + userId), {
  username: name,
  email: email,
  profile_picture : imageUrl
})
.then(() => {
  // Data saved successfully!
})
.catch((error) => {
  // The write failed...
});

Web namespaced API

firebase.database().ref('users/' + userId).set({
  username: name,
  email: email,
  profile_picture : imageUrl
}, (error) => {
  if (error) {
    // The write failed...
  } else {
    // Data saved successfully!
  }
});

Borrar datos

La forma más sencilla de eliminar datos es llamar remove() en una referencia a la ubicación de esos datos.

También puede eliminar especificando null como valor para otra operación de escritura como set() o update() . Puede utilizar esta técnica con update() para eliminar varios elementos secundarios en una sola llamada API.

Reciba una Promise

Para saber cuándo sus datos se envían al servidor de Firebase Realtime Database, puede utilizar una Promise . Tanto set() como update() pueden devolver una Promise que puede usar para saber cuándo se confirma la escritura en la base de datos.

Separar oyentes

Las devoluciones de llamada se eliminan llamando al método off() en la referencia de tu base de datos de Firebase.

Puede eliminar un único oyente pasándolo como parámetro a off() . Al off() en la ubicación sin argumentos se eliminan todos los oyentes en esa ubicación.

off() en un oyente principal no elimina automáticamente los oyentes registrados en sus nodos secundarios; También se debe invocar off() en cualquier oyente secundario para eliminar la devolución de llamada.

Guardar datos como transacciones

Cuando trabaje con datos que podrían dañarse debido a modificaciones simultáneas, como contadores incrementales, puede utilizar una operación de transacción . Puede darle a esta operación una función de actualización y una devolución de llamada de finalización opcional. La función de actualización toma el estado actual de los datos como argumento y devuelve el nuevo estado deseado que le gustaría escribir. Si otro cliente escribe en la ubicación antes de que su nuevo valor se escriba correctamente, se llama nuevamente a su función de actualización con el nuevo valor actual y se vuelve a intentar la escritura.

Por ejemplo, en la aplicación de blogs sociales de ejemplo, podría permitir a los usuarios destacar y quitar publicaciones y realizar un seguimiento de cuántas estrellas ha recibido una publicación de la siguiente manera:

Web modular API

import { getDatabase, ref, runTransaction } from "firebase/database";

function toggleStar(uid) {
  const db = getDatabase();
  const postRef = ref(db, '/posts/foo-bar-123');

  runTransaction(postRef, (post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

Web namespaced API

function toggleStar(postRef, uid) {
  postRef.transaction((post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

El uso de una transacción evita que el recuento de estrellas sea incorrecto si varios usuarios destacan la misma publicación al mismo tiempo o si el cliente tenía datos obsoletos. Si la transacción es rechazada, el servidor devuelve el valor actual al cliente, que ejecuta la transacción nuevamente con el valor actualizado. Esto se repite hasta que se acepta la transacción o usted cancela la transacción.

Incrementos atómicos del lado del servidor

En el caso de uso anterior, escribimos dos valores en la base de datos: el ID del usuario que destaca o quita la estrella de la publicación y el recuento de estrellas incrementado. Si ya sabemos que el usuario protagoniza la publicación, podemos usar una operación de incremento atómico en lugar de una transacción.

Web modular API

function addStar(uid, key) {
  import { getDatabase, increment, ref, update } from "firebase/database";
  const dbRef = ref(getDatabase());

  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = increment(1);
  update(dbRef, updates);
}

Web namespaced API

function addStar(uid, key) {
  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  firebase.database().ref().update(updates);
}

Este código no utiliza una operación de transacción, por lo que no se vuelve a ejecutar automáticamente si hay una actualización conflictiva. Sin embargo, dado que la operación de incremento ocurre directamente en el servidor de la base de datos, no hay posibilidad de que se produzca un conflicto.

Si desea detectar y rechazar conflictos específicos de una aplicación, como un usuario que destaca una publicación que ya destacó anteriormente, debe escribir reglas de seguridad personalizadas para ese caso de uso.

Trabajar con datos sin conexión

Si un cliente pierde su conexión de red, su aplicación seguirá funcionando correctamente.

Cada cliente conectado a una base de datos de Firebase mantiene su propia versión interna de cualquier dato activo. Cuando se escriben datos, primero se escriben en esta versión local. Luego, el cliente de Firebase sincroniza esos datos con los servidores de bases de datos remotos y con otros clientes al "mejor esfuerzo".

Como resultado, todas las escrituras en la base de datos desencadenan eventos locales inmediatamente, antes de que se escriba cualquier dato en el servidor. Esto significa que su aplicación sigue respondiendo independientemente de la latencia o la conectividad de la red.

Una vez que se restablece la conectividad, su aplicación recibe el conjunto apropiado de eventos para que el cliente se sincronice con el estado actual del servidor, sin tener que escribir ningún código personalizado.

Hablaremos más sobre el comportamiento fuera de línea en Obtenga más información sobre las capacidades en línea y fuera de línea .

Próximos pasos