Інтеграція Firebase Realtime Database у мобільний застосунок
Firebase Realtime Database (RTDB) — JSON-дерево з WebSocket-синхронізацією. Не реляційна БД, не документна БД у класичному сенсі. Ключова фіча — офлайн-персистентність та real-time синхронізація з коробки. Але неправильна структура даних у RTDB перетворює ці переваги на проблему: підписка на вузол з глибоким вложенням затягне весь поддиатри в пам'ять пристрою.
Структура даних: денормалізація обов'язкова
У 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: слухач живе вічно, перерендеривает компонент, який уже видалений. У production це крэш с 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 — або всім читати/писати, або нікому. Обов'язково налаштовуємо перед production:
{
"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 тижні.







