Crea prototipos y realiza pruebas con Firebase Local Emulator Suite (opcional)
Antes de analizar cómo la app realiza las operaciones de lectura y escritura en Realtime Database, veamos un conjunto de herramientas que puedes usar para crear prototipos y probar la funcionalidad de Realtime Database: Firebase Local Emulator Suite. Si quieres probar diferentes modelos de datos, optimizar tus reglas de seguridad o encontrar la forma más rentable de interactuar con el backend, recomendamos que trabajes a nivel local sin implementar servicios en ejecución.
Un emulador de Realtime Database forma parte de Local Emulator Suite, lo que permite que tu app interactúe con el contenido y la configuración emulados de la base de datos y, si lo deseas, con los recursos emulados del proyecto (funciones, otras bases de datos y reglas de seguridad).
El uso del emulador de Realtime Database solo requiere algunos pasos sencillos:
- Agrega una línea de código a la configuración de prueba de tu app para conectarte al emulador.
- Desde la raíz del directorio de tu proyecto local, ejecuta
firebase emulators:start
. - Realiza llamadas desde el código prototipo de tu app con un SDK de la plataforma de Realtime Database como de costumbre, o bien usa la API de REST de Realtime Database.
Hay una explicación detallada sobre Realtime Database y Cloud Functions disponible. También deberías consultar la introducción a Local Emulator Suite.
Obtén una referencia a una base de datos
Para leer o escribir en la base de datos, necesitas una instancia de
firebase.database.Reference
:
Web
import { getDatabase } from "firebase/database"; const database = getDatabase();
Web
var database = firebase.database();
Escribe datos
En este documento, se abarcan los conceptos básicos de la recuperación de datos y cómo ordenarlos y filtrarlos en Firebase.
Para recuperar los datos de Firebase, se debe adjuntar un objeto de escucha asíncrono a firebase.database.Reference
. El objeto de escucha se activa una vez para el estado inicial de los datos y nuevamente cada vez que cambian los datos.
Operaciones básicas de escritura
Si quieres ejecutar operaciones básicas de escritura, puedes usar set()
para guardar datos en una referencia específica y reemplazar todos los datos en esa ruta de acceso. Por ejemplo, una aplicación social de blogs puede agregar un usuario con set()
de la siguiente manera:
Web
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
function writeUserData(userId, name, email, imageUrl) { firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }); }
El uso de set()
reemplaza los datos en la ubicación especificada, incluidos los nodos
secundarios.
Lee datos
Detecta eventos de valores
Para leer datos de una ruta de acceso y detectar cambios, usa onValue()
a fin
de observar los eventos. Puedes usar este evento para leer instantáneas estáticas del contenido de una
ruta de acceso determinada y ver cómo se encontraba en el momento del evento. Este método
se inicia cuando se adjunta el objeto de escucha y nuevamente cada vez que cambian los datos, incluidos los secundarios. La devolución de llamada del evento recibe una instantánea que contiene
todos los datos de esa ubicación, incluidos los datos secundarios. Si no hay datos, la
instantánea mostrará el valor false
cuando llames a exists()
y null
cuando llames a val()
.
En el siguiente ejemplo, se demuestra una aplicación social de blogs que recupera el recuento de estrellas de una entrada desde la base de datos:
Web
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
var starCountRef = firebase.database().ref('posts/' + postId + '/starCount'); starCountRef.on('value', (snapshot) => { const data = snapshot.val(); updateStarCount(postElement, data); });
El objeto de escucha recibe una snapshot
que contiene los datos de la ubicación específica
en la base de datos en el momento en que ocurrió el evento. Puedes recuperar los datos de la snapshot
con el método val()
.
Lee los datos una sola vez
Lee datos una vez con get()
El SDK se diseñó para administrar interacciones con servidores de bases de datos, sin importar si tu app está en línea o sin conexión.
En general, debes usar las técnicas de eventos de valor que se describieron antes a fin de leer datos para recibir notificaciones sobre las actualizaciones de los datos del backend. Las técnicas de los objetos de escucha reducen el uso y la facturación, y están optimizadas para brindar a los usuarios la mejor experiencia en línea y sin conexión.
Si necesitas los datos solo una vez, puedes usar get()
para obtener una instantánea de la base de datos. Si, por algún motivo, get()
no puede mostrar el valor del servidor, el cliente sondeará la caché de almacenamiento local y mostrará un error si de todos modos no encuentra el valor.
El uso innecesario de get()
puede aumentar la utilización del ancho de banda y reducir el
rendimiento. Esto se puede evitar usando un objeto de escucha en tiempo real, como se muestra arriba.
Web
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
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); });
Lee datos una sola vez con un observador
En algunos casos, es recomendable que se muestre de inmediato el valor de la caché local, en lugar de buscar un valor actualizado en el servidor. En esos casos, puedes usar once()
para obtener los datos de la caché de disco local inmediatamente.
Esto resulta útil para los datos que solo se deben cargar una vez y que no se espera que cambien con frecuencia ni necesiten una escucha activa. Por ejemplo, en la app de blogs de los ejemplos anteriores se usa este método para cargar el perfil de un usuario cuando este comienza a crear una publicación nueva:
Web
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
var userId = firebase.auth().currentUser.uid; return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => { var username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; // ... });
Actualiza o borra datos
Actualiza campos específicos
Para escribir de forma simultánea en elementos secundarios específicos de un nodo sin reemplazar otros nodos secundarios, usa el método update()
.
Cuando llamas a update()
, puedes especificar una ruta de acceso de la clave para actualizar valores secundarios de nivel inferior. Si se almacenan datos en varias ubicaciones para obtener un mejor escalamiento, puedes actualizar todas las instancias de esos datos mediante fan-out de datos.
Por ejemplo, es posible que una app social de blogs cree una entrada y que esta se actualice de forma simultánea en el feed de actividad reciente y en el feed de actividad de publicaciones del usuario con un código como el siguiente:
Web
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
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); }
En este ejemplo, se usa push()
para crear una entrada en el nodo que contiene las entradas de
todos los usuarios en /posts/$postid
y recuperar la clave de manera simultánea. Luego, se puede usar la clave para crear una segunda entrada en las publicaciones del usuario en /user-posts/$userid/$postid
.
Con estas rutas de acceso, puedes ejecutar actualizaciones simultáneas en varias ubicaciones del árbol JSON con una única llamada a update()
, de manera similar a este ejemplo en el que se crea la publicación nueva en ambas ubicaciones. Las actualizaciones simultáneas que se hacen de esta forma son atómicas: todas se ejecutan correctamente o todas fallan.
Agrega una devolución de llamada de finalización
Si quieres saber en qué momento se confirma la escritura de los datos, puedes agregar una devolución de llamada de finalización. Tanto set()
como update()
toman una devolución de llamada opcional de finalización que se llama cuando la escritura se confirma en la base de datos. Si
la llamada no funciona correctamente, la devolución de llamada recibirá un
objeto de error que indicará el motivo.
Web
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
firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }, (error) => { if (error) { // The write failed... } else { // Data saved successfully! } });
Borra datos
La forma más sencilla de borrar datos es llamar a remove()
en una referencia a la ubicación de los datos.
También puedes borrar datos si especificas null
como el valor de otra operación de escritura, como set()
o update()
. Puedes usar esta técnica con update()
para borrar varios datos secundarios con una sola llamada a la API.
Recibe una Promise
Para saber cuándo se confirman tus datos en el servidor de Firebase Realtime Database,
puedes usar Promise
.
Tanto set()
como update()
pueden mostrar una Promise
que puedes usar para saber cuándo
se confirma la escritura en la base de datos.
Desvincula objetos de escucha
Para quitar las devoluciones de llamada, llama al método off()
en tu
referencia de la base de datos de Firebase.
Para quitar un solo objeto de escucha, puedes pasarlo como parámetro a off()
.
Si llamas a off()
en la ubicación que no tiene argumentos, se quitarán todos los objetos de escucha de esa ubicación.
Si llamas a off()
en un objeto de escucha primario, no se quitan automáticamente los objetos de escucha registrados en sus nodos secundarios. Deberás llamar a off()
también en todos los objetos de escucha secundarios para quitar la devolución de llamada.
Guarda datos como transacciones
Cuando trabajas con datos que se podrían dañar si se hacen cambios simultáneos (por ejemplo, contadores incrementales) puedes usar una operación de transacción. Esta operación acepta una función de actualización y una devolución de llamada opcional de finalización. La función de actualización toma el estado actual de los datos como argumento y genera el nuevo estado que deseas escribir. Si otro cliente escribe en la ubicación antes de que se escriba de manera correcta el valor nuevo, se vuelve a llamar a la función de actualización con el nuevo valor actual y se intenta nuevamente la operación de escritura.
Por ejemplo, en la app social de blogs que mencionamos antes, podrías permitir que los usuarios agreguen o quiten estrellas en las entradas y llevar un seguimiento de cuántas estrellas recibió una entrada de la siguiente forma:
Web
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
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; }); }
Si usas una transacción, evitas que el recuento de estrellas sea incorrecto en caso de que varios usuarios agreguen estrellas a la entrada al mismo tiempo o el cliente tenga datos inactivos. Si se rechaza la transacción, el servidor le muestra el valor actual al cliente, que vuelve a ejecutar la transacción con el valor actualizado. Esto se repite hasta que se acepte la transacción, o hasta que decidas anularla.
Incrementos atómicos del servidor
En el caso de uso anterior, se escriben dos valores en la base de datos: el ID del usuario que destacó o dejó de destacar la entrada, y el recuento de estrellas general. Si ya sabemos que el usuario destacará la entrada, podemos usar una operación de incremento atómico en vez de una transacción.
Web
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
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 usa una operación de transacción, por lo que no se vuelve a ejecutar automáticamente si hay una actualización conflictiva. Sin embargo, como la operación de incremento ocurre directamente en el servidor de la base de datos, no hay posibilidades de que ocurran conflictos.
Si quieres detectar y rechazar conflictos específicos de la aplicación (por ejemplo, si un usuario destaca una entrada que ya destacó antes), debes escribir reglas de seguridad personalizadas para ese caso de uso.
Trabaja con datos sin conexión
Si un cliente pierde la conexión de red, la app continúa funcionando de manera correcta.
Todos los clientes conectados a una base de datos de Firebase mantienen su propia versión interna de los datos activos. Cuando se escriben datos, se hace primero en esta versión local. Después, el cliente de Firebase sincroniza esos datos con los servidores de bases de datos remotas y con otros clientes según el “mejor esfuerzo”.
Como resultado, todas las operaciones de escritura en la base de datos activan eventos locales al instante, antes de que se escriban datos en el servidor. Esto significa que la app conserva la capacidad de respuesta, sin importar la latencia o el estado de conexión de la red.
Cuando se restablece la conectividad, la app recibe el conjunto de eventos adecuado, de manera que el cliente se sincroniza con el estado actual del servidor sin tener que escribir código personalizado.
Encontrarás más detalles sobre el comportamiento sin conexión en Más información sobre las capacidades en línea y sin conexión.
Próximos pasos
- Trabaja con listas de datos
- Aprende a estructurar datos
- Más información sobre las capacidades en línea y sin conexión