Перейти к основному содержанию
Мобильные приложения3 мин чтения

Офлайн-режим в мобильном приложении: когда он нужен и сколько стоит

Офлайн-режим удваивает сложность работы с данными. Разбираем, каким приложениям он действительно нужен и как выбрать стратегию синхронизации.

Схема синхронизации данных между приложением и сервером

«Пусть работает и без интернета» звучит как небольшое дополнение к требованиям, а на деле меняет всю работу с данными. Приложение перестаёт быть тонким клиентом к серверу и становится второй копией базы, у которой своя история изменений. Отсюда конфликты, очереди операций и вопросы, на которые в онлайн-приложении отвечать не приходилось.

Это не значит, что офлайн не нужен. Значит, что решение стоит принимать осознанно, а не по инерции.

Четыре уровня офлайн-поддержки

Уровни офлайн-режима и их цена в разработке
УровеньЧто умеетНадбавка к смете
Понятная ошибкаПоказывает состояние сети и кнопку повторадо 5%
Кэш на чтениеОткрывает ранее загруженные экраны10–15%
Очередь операцийКопит действия и отправляет при сети25–40%
Полная репликацияРаботает автономно неограниченно долгоот 60%

Большинству приложений достаточно второго уровня, а разговор обычно начинается с четвёртого. Кэш на чтение закрывает типовой сценарий: человек зашёл в метро, приложение показало последние данные вместо пустого экрана. Очередь операций нужна там, где действие нельзя отложить: акт выполненных работ у монтажника, отметка о приёмке на складе, замер у инженера в подвале.

Очередь операций

Базовая идея: локально применяем изменение сразу, а в очередь кладём описание операции. Когда сеть появилась, отправляем операции по порядку. Важная деталь — операции должны быть идемпотентными: сервер обязан узнать повторную отправку и не создать вторую запись.

Операция с клиентским идентификатором для защиты от дублей
type PendingOp = {
  id: string            // uuid, генерируется на устройстве
  entity: "inspection" | "photo" | "comment"
  action: "create" | "update" | "delete"
  payload: unknown
  createdAt: number
  attempts: number
}

export async function flushQueue() {
  const ops = await db.pending.orderBy("createdAt").toArray()

  for (const op of ops) {
    try {
      // Сервер проверяет op.id и при повторе возвращает прежний результат
      await api.apply(op)
      await db.pending.delete(op.id)
    } catch (error) {
      if (isConflict(error)) {
        await markForReview(op)     // решает пользователь
        await db.pending.delete(op.id)
        continue
      }
      // Сетевые ошибки — оставляем в очереди с задержкой
      await db.pending.update(op.id, { attempts: op.attempts + 1 })
      break
    }
  }
}

Конфликты и кто их решает

Конфликт возникает, когда одну запись поменяли и на устройстве, и на сервере. Универсального правильного решения нет — есть выбор, который надо сделать явно и объяснить пользователю.

Стратегии разрешения конфликтов
СтратегияКогда подходитРиск
Последняя запись побеждаетЛичные заметки, черновикиТихая потеря чужих правок
Сервер главныйСправочники, цены, каталогиПотеря работы, сделанной офлайн
Устройство главноеЗамеры и акты с объектаЗатирание правок из офиса
Показать пользователюДокументы, где важна каждая правкаСложнее интерфейс, нужен экран сравнения
Слияние по полямФормы, где правки редко пересекаютсяСложная логика, трудно тестировать

На практике в одном приложении сочетаются несколько стратегий: справочники тянутся с сервера без вопросов, а данные, введённые в поле, считаются приоритетными и при расхождении выносятся на подтверждение.

Что усложняется вместе с офлайном

  • Миграции локальной базы: у пользователя может стоять версия полугодовой давности с непустой очередью
  • Тестирование: нужны сценарии с обрывом сети посередине операции и с расхождением часов на устройстве
  • Хранение файлов: фотографии с объекта занимают память и требуют отдельной очереди загрузки
  • Безопасность: локальная база с данными клиентов должна шифроваться, а при выходе — стираться
  • Поддержка: разбор жалобы «пропали данные» требует журнала операций с устройства

Последний пункт стоит заложить сразу: без журнала операций и возможности его выгрузить любая история о потерянных данных превращается в спор без доказательств.

Частые вопросы

Насколько офлайн-режим удорожает приложение
Кэш на чтение — плюс 10–15% к смете, очередь операций с разрешением конфликтов — 25–40%, полная автономная работа — от 60%. Дороже становится и поддержка: тестировать приходится больше состояний.
Можно ли добавить офлайн позже
Можно, но это почти всегда переработка слоя данных, а не добавление функции. Если автономная работа вероятна в перспективе года, дешевле сразу построить работу с данными через локальное хранилище, даже без очереди операций.
Какие библиотеки вы используете
Для React Native — WatermelonDB или SQLite с собственным слоем синхронизации, для простых случаев MMKV и кэш запросов TanStack Query. Выбор зависит от объёма данных и того, нужна ли реляционная модель на устройстве.
мобильные приложенияархитектурасинхронизацияофлайн

Похожая задача в вашем проекте?

Разберём вашу ситуацию и пришлём оценку по этапам — без обязательств и общих слов.

Обсудить задачу

Вопрос по статье
или по своему проекту

Отвечаем на заявки в течение одного рабочего дня. Если задача не наша — скажем прямо и подскажем, к кому обратиться.

Написать нам

Поля со звёздочкой обязательны для заполнения.