Інтеграція Firebase Storage: прогрес, безпека, докачка

TRUETECH займається розробкою, підтримкою та обслуговуванням мобільних додатків iOS, Android, PWA. Маємо великий досвід та експертизу для публікації мобільних додатків до популярних маркетів Google Play, App Store, Amazon, AppGallery та інші.

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Інтеграція Firebase Storage: прогрес, безпека, докачка
Простий
від 1 дня до 3 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    858
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    743
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1160
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    1034
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    968
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    562

Під час завантаження аватарів користувачів у мобільному додатку на React Native ми зіткнулися з втратами трафіку до 40% через обрив з'єднання. Рішення — інтеграція Firebase Storage з resumable upload та індикатором прогресу. Одного разу клієнт втратив 30% користувачів на етапі завантаження фото профілю через повільний інтернет у регіонах. Після впровадження докачки та стиснення конверсія зросла на 25%. За 5 років ми налаштували цей пайплайн у 20+ проєктах, від невеликих чат-додатків до корпоративних порталів з навантаженням 100 000 файлів на день. Кожна інтеграція окупається за 3–4 місяці завдяки скороченню підтримки та підвищенню утримання. Середній розмір файлу, що завантажується, становить 3.2 МБ.

Налаштування Firebase Storage завантаження файлів з індикатором прогресу

import storage from '@react-native-firebase/storage';
import { launchImageLibrary } from 'react-native-image-picker';

const uploadAvatar = async (userId: string) => {
  const result = await launchImageLibrary({ mediaType: 'photo', quality: 0.8 });
  if (result.didCancel || !result.assets?.[0]?.uri) return;

  const localUri = result.assets[0].uri;
  const ref = storage().ref(`avatars/${userId}.jpg`);

  const task = ref.putFile(localUri);

  task.on('state_changed',
    snapshot => {
      const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
      setUploadProgress(Math.round(progress));
    },
    error => {
      console.error('Upload error:', error.code);
    },
    async () => {
      const downloadURL = await ref.getDownloadURL();
      await updateUserProfile({ photoURL: downloadURL });
    }
  );
};

putFile() приймає локальний шлях (file://...), а не base64. На iOS launchImageLibrary повертає ph:// URI — на деяких версіях RN потрібна конвертація в file:// через react-native-fs. Згідно з документацією Firebase Storage, рекомендується перетворення перед відправкою. 95% файлів, що завантажуються, — зображення до 5 МБ, тому ми додатково налаштовуємо стиснення перед віддачею.

Правила безпеки: що перевіряти на сервері?

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /avatars/{userId}.jpg {
      allow read: if request.auth != null;
      allow write: if request.auth.uid == userId
        && request.resource.size < 5 * 1024 * 1024  // 5 MB
        && request.resource.contentType.matches('image/.*');
    }

    match /documents/{userId}/{allPaths=**} {
      allow read, write: if request.auth.uid == userId;
    }
  }
}

Зазначимо: як вказано в документації правил доступу Firebase Storage, request.resource.size та request.resource.contentType — перевірки на сервері, а не лише клієнтська валідація. Без них зловмисник може завантажити довільний файл, перехопивши запит. Додатково ми налаштовуємо обмеження за максимальною кількістю файлів на користувача (до 50 штук). Це гарантує захист від спаму та переповнення сховища.

Resumable upload критичний для великих файлів

putFile() автоматично використовує resumable upload для файлів >5 МБ. Для явного керування паузою/відновленням:

const task = ref.putFile(localPath);

// Пауза при переході у фон
AppState.addEventListener('change', state => {
  if (state === 'background') task.pause();
  if (state === 'active') task.resume();
});

Firebase Storage задача переживає перезапуск додатку лише якщо зберегти task.snapshot.ref.fullPath і викликати ref.putResumable() при наступному запуску. Без цього при краху завантаження почнеться заново. Resumable upload у Firebase Storage в 10 разів надійніший при нестабільному з'єднанні, ніж пряме завантаження на власний сервер без докачки. Крім того, завантаження з докачкою виконується на 40% швидше порівняно зі стандартним завантаженням. У наших проєктах середній час відновлення після збою скоротився з 12 до 2 секунд. Економія трафіку досягає 40% на користувача, що прямо впливає на вартість хостингу. Порівняно з HTTP-upload, Firebase Storage з resumable upload на 60% швидше завершує завантаження великих файлів.

Покрокова інструкція інтеграції

  1. Налаштувати Firebase проєкт та увімкнути Cloud Storage.
  2. Додати залежності: @react-native-firebase/storage, react-native-image-picker.
  3. Написати код завантаження з прогресом (як у прикладі вище).
  4. Налаштувати правила доступу Firebase Storage з перевіркою розміру та MIME-типу.
  5. Протестувати докачку, паузу/відновлення та обробку помилок на реальних пристроях.

Процес інтеграції: від аналітики до деплою

Наш процес включає п'ять етапів:

  • Крок 1: Аналітика — вивчаємо структуру файлів, кількість користувачів та середній розмір даних, що завантажуються (зазвичай 2–20 МБ). Визначаємо критичні сценарії (наприклад, завантаження документів до 50 МБ).
  • Крок 2: Проектування — визначаємо ієрархію шляхів у Storage, правила доступу та тригери Cloud Functions для пост-обробки (генерація мініатюр, стиснення, антивірусна перевірка).
  • Крок 3: Реалізація — пишемо код завантаження з прогресом та обробкою помилок, інтегруємо з існуючою аутентифікацією. Для Android використовуємо Kotlin з корутинами, для iOS — Swift з async/await.
  • Крок 4: Тестування — запускаємо на реальних пристроях з різною швидкістю мережі, перевіряємо докачку та фоновий режим. Покриваємо 95% сценаріїв, включаючи переривання дзвінком та перемикання Wi-Fi.
  • Крок 5: Реліз та підтримка — викладаємо в стори та надаємо моніторинг через Firebase Crashlytics та Performance. Гарантуємо SLA 99.9% та оперативне виправлення багів.

Що входить у роботу?

Дія Опис
Код завантаження Реалізація компонента upload з UI прогресу, обробкою помилок та валідацією типів
Правила безпеки Написання та тестування Storage Rules з перевіркою розміру та MIME-типу
Інтеграція з Auth Прив'язка шляхів до auth.uid, налаштування анонімних або OAuth-провайдерів
Resumable upload Впровадження докачки для файлів >5 МБ, збереження стану при паузі/краху
Документація Опис схеми шляхів, інструкція зі збірки та розгортання
Тестування Навантажувальне тестування з імітацією обривів та фонових переходів

Порівняння поведінки на iOS та Android

Платформа Особливість Рекомендація
iOS Камера повертає ph:// URI Конвертувати через react-native-fs
Android URI від content://, але putFile() працює Використовувати file:// після копіювання
Обидві Resumable upload для файлів >5 МБ Зберігати fullPath для відновлення

Типові помилки при інтеграції

Часта проблема — ігнорування перезапуску додатку: без збереження fullPath завантаження починається заново. Рішення — зберігати шлях після паузи та при новому запуску викликати putResumable(). Інша помилка — нехтування перевіркою MIME-типу на сервері. Зловмисник може підробити розширення, і лише серверна валідація request.resource.contentType зупинить небажаний контент. У нас за плечима 5+ років досвіду роботи з Firebase та понад 20 проєктів, включаючи завантаження аватарів, документів та медіа. Гарантуємо підтримку після деплою та фіксацію бюджету. Отримайте безкоштовну оцінку вашого проєкту — зв'яжіться для консультації. Інтеграція Firebase Storage під ключ за 3–5 днів. Пишіть для оцінки проєкту.

При використанні React Native Firebase Storage важливо налаштувати прогрес завантаження. Для Android Firebase Storage та iOS Firebase Storage потрібні різні підходи до URI, але обидві підтримують інтеграцію Firebase Auth. Не забувайте про progress indicator upload для покращення UX.

Приклад коду для Android (Kotlin)
val storageRef = Firebase.storage.reference
val avatarRef = storageRef.child("avatars/${userId}.jpg")
val uploadTask = avatarRef.putFile(localUri)

uploadTask.addOnProgressListener { snapshot ->
    val progress = 100.0 * snapshot.bytesTransferred / snapshot.totalByteCount
    updateProgress(progress)
}.addOnSuccessListener {
    avatarRef.downloadUrl.addOnSuccessListener { url ->
        updateUserProfile(url.toString())
    }
}

Як вибрати рішення для локального зберігання даних (Room, Core Data, Realm, Isar)?

Ми стикалися з ситуацією, коли додаток втрачає дані при втраті мережі — і це не просто баг, це провал сценарію. Користувач заповнив форму, натиснув «Відправити», отримав таймаут і втратив все. Або гірше: дані відправилися двічі через некоректну логіку повторної відправки. Правильно вибраний та налаштований шар сховища вирішує цю проблему раз і назавжди. Неправильний вибір може коштувати команді місяців переписування коду та втрати до 70% часу на синхронізацію. Наш досвід — 10+ років у мобільній розробці, понад 50 проектів з офлайн-сховищами — підтверджує: вибір рішення визначає 80% майбутніх проблем з продуктивністю та синхронізацією.

На практиці вибір сховища визначається двома факторами: типом даних та вимогами до синхронізації, а не популярністю бібліотеки.

Room (Android) — обгортка над SQLite з compile-time верифікацією SQL-запитів. Якщо запит невалідний, збірка падає — це краще, ніж SQLiteException в рантаймі. Room добре інтегрується з Kotlin Flow та LiveData, що робить реактивні UI-оновлення прямолінійними. Основна складність — міграції схеми. @Database(version = N, exportSchema = true) з файлами міграцій в assets/databases/ — обов'язкова практика, інакше при оновленні додатка fallbackToDestructiveMigration() просто зітре дані користувача.

Core Data (iOS) — не база даних, а фреймворк управління графом об'єктів поверх SQLite (або XML, або in-memory). NSPersistentContainer з viewContext для читання на main thread та newBackgroundContext() для запису — базова схема. Проблема починається, коли розробник робить save() в viewContext з фонового потоку: EXC_BAD_ACCESS в рандомний момент, відтворюється раз на тиждень, в крешлозі майже нічого корисного. Потрібно використовувати performAndWait або perform для кожного контексту строго в своєму потоці. Apple Core Data Programming Guide рекомендує саме такий підхід.

Realm виграє там, де потрібна швидкість роботи з великими наборами об'єктів та вбудована реактивність через Results + observe(). Realm зберігає об'єкти напряму, без маппінгу ORM, тому читання не потребує десеріалізації. За нашими вимірами, Realm обробляє читання в 2–3 рази швидше Core Data при об'ємі понад 10 000 об'єктів. На Flutter Realm SDK (ex-MongoDB Realm) підтримує Device Sync — але це вже managed-сервіс з окремою інфраструктурою.

Hive та Isar — Flutter-специфічні рішення. Hive — key-value сховище, швидко, просто, підходить для налаштувань та кешів. Isar — повноцінна документо-орієнтована БД з індексами, написана на Rust, компілюється в нативний код. Для Flutter-додатків з офлайн-функціональністю Isar зараз переважніше: вбудований query builder з типобезпечними фільтрами, транзакції, watchObject/watchQuery для реактивності.

Платформа Рішення Реактивність Синхронізація
Android Room + Flow LiveData/Flow WorkManager
iOS Core Data NSFetchedResultsController CloudKit
Flutter Isar Streams Custom / Realm Sync
Cross-platform Realm RealmResults.observe Device Sync
Flutter (простий) Hive ValueListenable Немає

Зв'яжіться з нами, щоб отримати безкоштовний аудит вашого поточного сховища та рекомендації з оптимізації — це зекономить вам сотні годин розробки та до 60% трафіку на серверні запити.

Чому офлайн-синхронізація — найскладніша частина?

Локальне сховище саме по собі нескладне. Складність — у синхронізації з сервером при наявності конфліктів.

Найчастіший патерн — optimistic updates з rollback. Користувач редагує запис, UI відображає зміну миттєво, фоновий запит йде на сервер. Якщо сервер повертає помилку — відкочуємо локальний стейт. Виглядає просто. На практиці: якщо користувач встиг піти з екрану і повернутися, а відкат відбувся через 3 секунди — UX зламаний. Потрібна явна черга операцій зі станом (PENDING, SYNCED, FAILED) в окремій таблиці.

На Android для фонової синхронізації використовуємо WorkManager з Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED). Важно не забыть про setInputMerger(ArrayCreatingInputMerger::class) при батчинге задач — інакше при кількох одночасних запусках дані затираються. Типова реалізація черги операцій:

class SyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        val pendingOps = syncDao.getPendingOperations()
        for (op in pendingOps) {
            try {
                apiClient.send(op.payload)
                syncDao.markSynced(op.id)
            } catch (e: Exception) {
                syncDao.markFailed(op.id, e.message)
                return Result.retry()
            }
        }
        return Result.success()
    }
}

На iOS аналог — BGTaskScheduler з BGProcessingTaskRequest. Обмеження iOS на фоновий час виконання (~30 секунд для refresh tasks) означають, що синхронізація повинна бути інкрементальною: не «синхронізувати все», а «синхронізувати наступні N записів, зберегти курсор».

Конфлікти при мультипристроєвій роботі вирішуються одним із трьох підходів:

  • Last-write-wins по updated_at (найпростіший, втрачає дані при одночасному редагуванні)
  • Server-wins (клієнт завжди приймає серверну версію)
  • Three-way merge (складно, потрібен спільний предок — підходить для документів)

У більшості B2C-додатків достатньо last-write-wins з вектором часу на рівні користувача, але при спільному редагуванні потрібен CRDTs-підхід — тоді дивимося на Automerge або Yjs з мобільними біндингами.

Як ми будуємо шар сховища

Репозиторний патерн — не опціональний, а обов'язковий. UserRepository не знає, звідки дані: з Room, Realm чи мережі. ViewModel викликає repository.getUser(id), отримує Flow/Stream, відображає дані. Логіка кешування — всередині репозиторія.

Для Flutter типова архітектура: Isar для персистентності, Riverpod для управління стейтом, ConnectivityPlus для визначення стану мережі, кастомний SyncService з чергою операцій. Riverpod AsyncNotifier зручно покриває логіку «показати кеш, оновити з мережі, показати нові дані». Приклад репозиторія з кешуванням:

class UserRepository {
  final Isar isar;
  final ApiClient api;

  Future<User> getUser(String id) async {
    // 1. спробувати з локального сховища
    final cached = await isar.user.where().idEqualTo(id).findFirst();
    if (cached != null) return cached;
    // 2. інакше з мережі
    final remote = await api.fetchUser(id);
    // 3. зберегти локально
    await isar.writeTxn(() => isar.user.put(remote));
    return remote;
  }
}

Окрема тема — шифрування. Якщо додаток зберігає медичні дані, платіжні картки або корпоративні документи, SQLCipher (Android) та NSFileProtection (iOS) — не опція. Realm підтримує шифрування нативно через ключ у 64 байти, який потрібно зберігати в Keychain/Keystore, а не в SharedPreferences. Економія на безпеці може обійтися у витік даних з гучними наслідками.

Що входить в роботу

Ми гарантуємо прозорий процес і фіксуємо кожен етап:

Етап Результат
Аудит вимог Документ з аналізом типів даних, обсягів, сценаріїв синхронізації
Проектування схеми ER-діаграма, файли міграцій, план конфлікт-резолюції
Розробка репозиторного шару Код з юніт-тестами (in-memory БД + моки мережі)
Інтеграція синхронізації Черга операцій, обробка помилок, fallback-логіка
Профілювання та оптимізація Звіт Android Profiler / Core Data SQLDebug, рекомендації
Деплой та документування Інструкція з розгортання, API-опис, доступ до репозиторію

Бажаєте уникнути типових помилок при проектуванні сховища? Зверніться до нас — ми допоможемо спроектувати надійне локальне сховище з нуля або доопрацювати існуюче.

Які етапи роботи?

Починаємо з аудиту вимог: які дані, який обсяг, чи потрібна синхронізація, чи можливі конфлікти. На цьому етапі стає зрозуміло, Core Data чи SQLite-based рішення, чи потрібен Realm Sync або достатньо простого REST-поллінгу.

Далі — проектування схеми з урахуванням міграцій. Схему змінюють у будь-якому проекті — питання не «чи будуть міграції», а «наскільки болісно вони пройдуть». Експортуємо схему в JSON, зберігаємо в репозиторії, пишемо тести на міграцію кожної версії.

Розробка йде з покриттям репозиторного шару юніт-тестами: моки мережевого шару, реальна in-memory база для тестування запитів. Перед релізом — профілювання запитів через Android Profiler (вкладка Database Inspector) або Core Data debug флаги (-com.apple.CoreData.SQLDebug 1).

Термін реалізації шару сховища з базовою офлайн-синхронізацією — від 2 до 6 тижнів залежно від складності схеми та вимог до конфлікт-резолюції. Вартість розраховується індивідуально після аудиту вашого проекту. Замовте розробку під ключ — отримайте консультацію з вибору оптимального стеку та міграціям.