(Необязательно) Создание прототипов и тестирование с помощью Firebase Local Emulator Suite
Прежде чем говорить о том, как ваше приложение считывает и записывает данные в Realtime Database, давайте познакомимся с набором инструментов, которые можно использовать для создания прототипов и тестирования функций Realtime Database: Firebase Local Emulator Suite. Если вы пробуете разные модели данных, оптимизируете правила безопасности или ищете наиболее экономичный способ взаимодействия с серверной частью, возможность работать локально без развертывания действующих сервисов может быть отличной идеей.
Эмулятор Realtime Database входит в состав Local Emulator Suite. Он позволяет приложению взаимодействовать с эмулируемыми данными и конфигурацией базы данных, а также с эмулируемыми ресурсами проекта (функциями, другими базами данных и правилами безопасности).
Чтобы начать работу с эмулятором Realtime Database, выполните следующие действия:
- Добавьте строку кода в тестовую конфигурацию приложения, чтобы подключиться к эмулятору.
- Выполните команду
firebase emulators:startв корневом каталоге локального проекта. - Вызовы из кода прототипа приложения с помощью SDK платформы Realtime Database или REST API Realtime Database.
Доступно подробное руководство по работе с Realtime Database и Cloud Functions. Также рекомендуем ознакомиться с вводной статьей о Local Emulator Suite.
Получение ссылки на базу данных
Чтобы читать данные из базы данных или записывать их в нее, вам нужен экземпляр firebase.database.Reference:
Web
import { getDatabase } from "firebase/database"; const database = getDatabase();
Web
var database = firebase.database();
Запись данных
В этом документе рассматриваются основы извлечения данных, а также способы упорядочивания и фильтрации. Данные Firebase.
Данные Firebase извлекаются путем подключения асинхронного прослушивателя к firebase.database.Reference. Прослушиватель срабатывает один раз для начального состояния данных и затем каждый раз, когда данные меняются.
Основные операции записи
Для базовых операций записи можно использовать set(), чтобы сохранить данные в указанной ссылке, заменив все существующие данные по этому пути. Например, приложение для ведения блогов может добавить пользователя с помощью set() следующим образом:
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 }); }
При использовании set() данные в указанном месте, включая дочерние узлы, перезаписываются.
Чтение данных
Как отслеживать события ценности
Чтобы прочитать данные по пути и отслеживать изменения, используйте onValue() для наблюдения за событиями. Это событие можно использовать для чтения статических снимков содержимого по заданному пути, существовавших на момент события. Этот метод запускается один раз при подключении прослушивателя и затем каждый раз, когда данные, включая дочерние, изменяются. Обратному вызову события передается снимок, содержащий все данные в этом местоположении, включая дочерние данные. Если данных нет, то при вызове exists() снимок вернет false, а при вызове val() – null.
В следующем примере показано, как приложение для социальных блогов извлекает из базы данных количество звезд, присвоенных публикации:
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); });
Прослушиватель получает snapshot, содержащий данные в указанном месте базы данных на момент события. Вы можете получить данные в snapshot с помощью метода val().
Прочитать данные один раз
Однократное чтение данных с помощью get()
SDK предназначен для управления взаимодействием с серверами баз данных независимо от того, находится ли ваше приложение в сети или офлайн.
Как правило, для чтения данных и получения уведомлений об их обновлении из серверной части следует использовать описанные выше методы событий значений. Методы прослушивания снижают использование и расходы, а также оптимизированы для обеспечения наилучшего взаимодействия с пользователями при подключении к сети и в автономном режиме.
Если данные нужны вам только один раз, вы можете использовать get(), чтобы получить снимок данных из базы данных. Если по какой-либо причине get() не может вернуть значение сервера, клиент проверит кеш локального хранилища и вернет ошибку, если значение по-прежнему не найдено.
Необоснованное использование get() может привести к увеличению трафика и снижению производительности. Чтобы избежать этого, используйте прослушиватель в реальном времени, как показано выше.
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); });
Как считать данные один раз с помощью наблюдателя
В некоторых случаях может потребоваться немедленно вернуть значение из локального кеша, не проверяя наличие обновленного значения на сервере. В таких случаях вы можете использовать once(), чтобы сразу получить данные из локального кеша диска.
Это полезно для данных, которые нужно загрузить только один раз и которые не будут часто меняться или требовать активного прослушивания. Например, приложение для ведения блогов из предыдущих примеров использует этот метод для загрузки профиля пользователя, когда он начинает создавать новую запись:
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'; // ... });
Обновление или удаление данных
Обновление определенных полей
Чтобы одновременно записать данные в определенные дочерние узлы, не перезаписывая другие дочерние узлы, используйте метод update().
При вызове update() вы можете обновить значения дочерних элементов более низкого уровня, указав путь к ключу. Если данные хранятся в нескольких местах для лучшего масштабирования, вы можете обновить все экземпляры этих данных с помощью разветвления данных.
Например, приложение для ведения блогов может создать запись и одновременно обновить ее в фиде недавних действий и фиде действий пользователя, опубликовавшего запись, используя следующий код:
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); }
В этом примере используется push() для создания записи в узле, содержащем записи для всех пользователей в /posts/$postid, и одновременного получения ключа. Затем этот ключ можно использовать для создания второй записи в сообщениях пользователя по адресу /user-posts/$userid/$postid.
Используя эти пути, вы можете одновременно обновлять несколько местоположений в дереве JSON с помощью одного вызова update(), как в этом примере, где создается новая запись в обоих местоположениях. Одновременные обновления, выполненные таким образом, являются атомарными: либо все обновления выполняются успешно, либо все обновления завершаются неудачно.
Добавьте обратный вызов по завершении
Если вы хотите знать, когда данные будут зафиксированы, добавьте обратный вызов. И set(), и update() принимают необязательный обратный вызов завершения, который вызывается, когда запись была зафиксирована в базе данных. Если вызов не удался, обратному вызову передается объект ошибки, указывающий на причину сбоя.
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! } });
Удаление данных
Самый простой способ удалить данные – вызвать remove() для ссылки на местоположение этих данных.
Вы также можете удалить данные, указав null в качестве значения для другой операции записи, например set() или update(). Этот метод можно использовать с update(), чтобы удалить несколько дочерних элементов за один вызов API.
Получите Promise
Чтобы узнать, когда ваши данные будут переданы на сервер Firebase Realtime Database, используйте Promise.
И set(), и update() могут возвращать Promise, который можно использовать, чтобы узнать, когда запись будет зафиксирована в базе данных.
Отключить прослушиватели
Чтобы удалить обратные вызовы, вызовите метод off() для ссылки на базу данных Firebase.
Чтобы удалить один прослушиватель, передайте его в качестве параметра в off().
Вызов off() для местоположения без аргументов удаляет всех слушателей в этом местоположении.
Вызов off() для родительского прослушивателя не приводит к автоматическому удалению прослушивателей, зарегистрированных для дочерних узлов. Чтобы удалить обратный вызов, необходимо также вызвать off() для всех дочерних прослушивателей.
Сохранение данных в виде транзакций
При работе с данными, которые могут быть повреждены одновременными изменениями, например с инкрементными счетчиками, можно использовать транзакцию. Вы можете назначить этой операции функцию обновления и необязательный обратный вызов по завершении. Функция обновления принимает текущее состояние данных в качестве аргумента и возвращает новое желаемое состояние, которое вы хотите записать. Если другой клиент выполнит запись в то же место до того, как будет успешно записано ваше новое значение, функция обновления будет вызвана снова с новым текущим значением, и запись будет повторена.
Например, в приложении для ведения блогов вы можете разрешить пользователям ставить и убирать отметки "Нравится" и отслеживать, сколько отметок получил пост, следующим образом:
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; }); }
Использование транзакции предотвращает неправильный подсчет звезд, если несколько пользователей одновременно помечают один и тот же пост или у клиента были устаревшие данные. Если транзакция отклонена, сервер возвращает клиенту текущее значение, и клиент снова выполняет транзакцию с обновленным значением. Это повторяется до тех пор, пока транзакция не будет принята или вы не прервете ее.
Атомарные приращения на стороне сервера
В приведенном выше примере мы записываем в базу данных два значения: идентификатор пользователя, который ставит или убирает звездочку, и увеличенное количество звездочек. Если мы уже знаем, что пользователь пометил запись звездочкой, мы можем использовать атомарную операцию инкремента вместо транзакции.
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); }
Этот код не использует операцию транзакции, поэтому он не перезапускается автоматически в случае конфликтующего обновления. Однако, поскольку операция инкремента происходит непосредственно на сервере базы данных, вероятность конфликта отсутствует.
Если вы хотите обнаруживать и отклонять конфликты, связанные с приложением, например, когда пользователь помечает звездочкой публикацию, которую он уже пометил ранее, вам следует написать собственные правила безопасности для этого случая использования.
Работа с данными в офлайн-режиме
Если клиент потеряет сетевое подключение, ваше приложение продолжит работать корректно.
Каждый клиент, подключенный к базе данных Firebase, поддерживает собственную внутреннюю версию всех активных данных. При записи данных они сначала записываются в эту локальную версию. Затем клиент Firebase синхронизирует эти данные с удаленными серверами баз данных и другими клиентами по мере возможности.
В результате все записи в базу данных немедленно вызывают локальные события, прежде чем какие-либо данные будут записаны на сервер. Это означает, что ваше приложение остается отзывчивым независимо от задержки сети или подключения.
После восстановления подключения приложение получает соответствующий набор событий, чтобы клиент синхронизировался с текущим состоянием сервера без необходимости писать какой-либо пользовательский код.
Подробнее о поведении в офлайн-режиме рассказывается в разделе Подробнее о возможностях онлайн и офлайн.