Интеграция Firebase Firestore в мобильное приложение

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Интеграция Firebase Firestore в мобильное приложение
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Интеграция Firebase Firestore в мобильное приложение

Firestore — документно-ориентированная БД с real-time слушателями, офлайн-кэшем и гибкими запросами. В отличие от RTDB, поддерживает составные индексы, where() по нескольким полям, orderBy() с пагинацией. Но у неё свои подводные камни: onSnapshot без limit() на растущей коллекции постепенно увеличивает объём данных на каждый тик, пока не замедлит рендеринг.

Структура данных: коллекции и подколлекции

Базовая структура для социального приложения:

users/{userId}
  ├── displayName: string
  ├── photoURL: string
  └── posts/{postId}     ← подколлекция
      ├── text: string
      ├── createdAt: Timestamp
      └── likes: number

Подколлекции — правильно для данных, количество которых не ограничено. Вложенные массивы в документ — неправильно для коллекций > 50 элементов: Firestore ограничивает размер документа 1 МБ, и весь документ читается даже если нужно одно поле.

Подписки и пагинация

import firestore from '@react-native-firebase/firestore';

// Реальное время + пагинация
const [posts, setPosts] = useState<Post[]>([]);
const [lastDoc, setLastDoc] = useState<FirebaseFirestoreTypes.DocumentSnapshot | null>(null);
const [loading, setLoading] = useState(false);

const fetchPage = useCallback(async () => {
  if (loading) return;
  setLoading(true);

  let query = firestore()
    .collection(`users/${userId}/posts`)
    .orderBy('createdAt', 'desc')
    .limit(20);

  if (lastDoc) query = query.startAfter(lastDoc);

  const snapshot = await query.get();
  const newPosts = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as Post));

  setPosts(prev => [...prev, ...newPosts]);
  setLastDoc(snapshot.docs[snapshot.docs.length - 1] ?? null);
  setLoading(false);
}, [lastDoc, loading, userId]);

// Real-time: только для верхней части ленты (без пагинации)
useEffect(() => {
  const unsubscribe = firestore()
    .collection(`users/${userId}/posts`)
    .orderBy('createdAt', 'desc')
    .limit(10)
    .onSnapshot(snapshot => {
      const fresh = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as Post));
      setPosts(prev => {
        // Объединяем новые real-time данные с пагинированными
        const ids = new Set(fresh.map(p => p.id));
        return [...fresh, ...prev.filter(p => !ids.has(p.id))];
      });
    });

  return () => unsubscribe();
}, [userId]);

Разделяем real-time (первая страница) и load more (пагинация через get()). onSnapshot на весь список с пагинацией — антипаттерн: каждое изменение в коллекции вернёт полный новый снапшот первых N документов.

Офлайн-кэш

// Включить до любого обращения к Firestore
await firestore().settings({
  cacheSizeBytes: firestore.CACHE_SIZE_UNLIMITED, // или конкретный размер в байтах
  persistence: true, // включено по умолчанию на мобильных
});

При офлайне onSnapshot продолжает работать — возвращает данные из кэша с snapshot.metadata.fromCache === true. Записи буферизуются и отправляются при восстановлении сети.

Для явного чтения из кэша без сетевого запроса:

const snapshot = await firestore()
  .collection('posts')
  .doc(postId)
  .get({ source: 'cache' }); // 'cache' | 'server' | 'default'

Составные индексы

Запрос с where + orderBy по разным полям требует составного индекса. Firestore автоматически предлагает его создать при первой ошибке в development (линк в консоль). В продакшне — настраивайте индексы в firestore.indexes.json заранее:

{
  "indexes": [
    {
      "collectionGroup": "posts",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "userId", "order": "ASCENDING" },
        { "fieldPath": "createdAt", "order": "DESCENDING" }
      ]
    }
  ]
}

Без индекса Firestore вернёт FAILED_PRECONDITION ошибку. Время создания индекса — от нескольких минут до часов на больших коллекциях.

Правила безопасности Firestore

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId}/posts/{postId} {
      allow read: if request.auth != null;
      allow write: if request.auth.uid == userId
        && request.resource.data.keys().hasAll(['text', 'createdAt'])
        && request.resource.data.text is string
        && request.resource.data.text.size() <= 2000;
    }
  }
}

Валидируйте типы и размеры в правилах — не только в клиентском коде.

Транзакции и batch writes

// Транзакция: атомарный перевод лайка
await firestore().runTransaction(async transaction => {
  const postRef = firestore().doc(`posts/${postId}`);
  const userLikeRef = firestore().doc(`userLikes/${userId}_${postId}`);

  const [postSnap, likeSnap] = await Promise.all([
    transaction.get(postRef),
    transaction.get(userLikeRef),
  ]);

  if (likeSnap.exists()) {
    transaction.delete(userLikeRef);
    transaction.update(postRef, { likes: firestore.FieldValue.increment(-1) });
  } else {
    transaction.set(userLikeRef, { userId, postId, createdAt: firestore.FieldValue.serverTimestamp() });
    transaction.update(postRef, { likes: firestore.FieldValue.increment(1) });
  }
});

FieldValue.increment() — атомарный инкремент без read-modify-write гонки. Без транзакции при одновременных лайках счётчик будет некорректным.

Оценка

Firestore с офлайн-персистентностью, real-time подписками, пагинацией и правилами безопасности: 2–4 недели в зависимости от сложности структуры данных.