Интеграция Firebase Realtime Database в мобильное приложение
Firebase Realtime Database (RTDB) — JSON-дерево с WebSocket-синхронизацией. Не реляционная БД, не документная БД в классическом смысле. Ключевая фича — офлайн-персистентность и real-time синхронизация из коробки. Но неправильная структура данных в RTDB превращает эти преимущества в проблему: подписка на узел enablePersistence при глубоком вложении затянет весь поддиатри в память устройства.
Структура данных: денормализация обязательна
В RTDB нет JOIN. Если хотите получить посты пользователя — не вкладывайте посты внутрь пользователя. Денормализуйте:
{
"users": {
"uid123": { "name": "Иван", "email": "ivan@..." }
},
"posts": {
"postId1": { "userId": "uid123", "text": "...", "createdAt": 1700000000 }
},
"userPosts": {
"uid123": { "postId1": true, "postId2": true }
}
}
userPosts — обратный индекс для получения постов конкретного пользователя без сканирования всего узла posts. Это стандартный паттерн для RTDB.
Подписки в React Native
import database from '@react-native-firebase/database';
useEffect(() => {
const ref = database().ref(`/userPosts/${userId}`);
// value: полный снапшот при каждом изменении
const onValue = ref.on('value', snapshot => {
const postIds = Object.keys(snapshot.val() ?? {});
setPostIds(postIds);
});
// child_added: только новые элементы
const onChildAdded = ref.on('child_added', snapshot => {
setPostIds(prev => [...prev, snapshot.key!]);
});
return () => {
ref.off('value', onValue);
ref.off('child_added', onChildAdded);
};
}, [userId]);
Критично: всегда вызывайте ref.off() при размонтировании. on() без off() — memory leak: слушатель живёт вечно, перерендеривает компонент который уже удалён. В продакшне это крэш с Can't perform a React state update on an unmounted component.
Офлайн-персистентность
// index.js, до любых обращений к database()
import database from '@react-native-firebase/database';
database().setPersistenceEnabled(true);
database().setPersistenceCacheSizeBytes(10 * 1024 * 1024); // 10 MB
setPersistenceEnabled(true) включает SQLite-кэш на устройстве. При офлайне приложение читает из кэша. При восстановлении сети — синхронизирует изменения. Вызывать только один раз при инициализации, до первого подключения к БД.
keepSynced(true) на конкретном узле — предзагружает данные и держит в кэше, даже если нет активных слушателей:
database().ref(`/userPosts/${userId}`).keepSynced(true);
Осторожно: не применяйте keepSynced к большим узлам — RTDB скачает всё дерево.
Правила безопасности
Дефолтные правила RTDB — либо всем читать/писать, либо никому. Обязательно настраиваем перед продакшном:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
"posts": {
"$postId": {
".read": "auth != null",
".write": "auth != null && newData.child('userId').val() === auth.uid",
".validate": "newData.hasChildren(['userId', 'text', 'createdAt'])"
}
}
}
}
.validate — проверяет структуру данных перед записью. Без валидации клиент может записать произвольный JSON.
Транзакции для конкурентного обновления
Лайки, счётчики, баланс — любой конкурентный инкремент:
const likeRef = database().ref(`/posts/${postId}/likes`);
await likeRef.transaction(currentLikes => (currentLikes ?? 0) + 1);
transaction() атомарно читает и записывает. Если между read и write другой клиент изменил значение — транзакция повторяется автоматически (до 25 раз). Для лайков это единственно правильный подход — set(currentLikes + 1) даст race condition при одновременных нажатиях.
RTDB vs Firestore: когда что выбирать
RTDB лучше для: real-time чатов, presence/статусы онлайн, игровые leaderboard, потоки событий. Firestore лучше для: сложные запросы, коллекции документов, масштабирование > 1M пользователей.
Стоимость RTDB: $5/ГБ хранилища + $1/ГБ трафика. При высокой частоте обновлений (чат, real-time) RTDB дешевле Firestore за счёт отсутствия поперечного billing по операциям read/write.
Оценка
RTDB с офлайн-персистентностью, правилами безопасности и real-time подписками: 2–3 недели.







