Содержание

Офлайн-режим нужен приложениям, которыми пользуются там, где связь плохая или её нет: курьерам, выездным сотрудникам, складским работникам, путешественникам. Для большинства приложений достаточно кешировать данные для просмотра и аккуратно сообщать об отсутствии сети. Полноценный подход offline-first — когда пользователь создаёт и меняет данные без интернета, а приложение потом их синхронизирует — заметно сложнее и требует продуманной синхронизации и разрешения конфликтов.

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

Когда приложению нужен офлайн-режим?

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

  • выездные сотрудники — монтажники, инспекторы, торговые представители;
  • логистика — курьеры, водители, сборщики на складе с плохим покрытием;
  • медицина и полевые исследования — заполнение форм там, где нет связи;
  • путешествия — билеты, карты, бронирования;
  • контент — книги, курсы, подкасты для просмотра в дороге.

Про выбор между приложением и сайтом — статьи нужно ли мобильное приложение и PWA вместо мобильного приложения.

Какие бывают уровни поддержки офлайна?

Поддержка офлайна бывает разной глубины. Чем больше пользователь может сделать без сети, тем сложнее разработка и тестирование.

Уровень Что может пользователь без сети Сложность
Сообщение об ошибке Видит понятное сообщение и кнопку повтора Минимальная
Кеш для просмотра Смотрит ранее загруженные данные Низкая
Отложенные действия Создаёт записи, которые отправятся позже Средняя
Offline-first Полноценно работает, синхронизация в фоне Высокая

Начинать стоит с честного вопроса: какие сценарии пользователи действительно выполняют без сети, — и делать офлайн только для них.

Заснеженные горы и предупреждающий знак Фото: Unsplash

Как хранить данные на устройстве?

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

  1. Локальная база — SQLite или надстройки над ней, которые есть для нативных и кроссплатформенных приложений.
  2. Только нужное — на устройство загружаются данные пользователя и его задач, а не вся база компании.
  3. Метки времени и версии — у каждой записи есть версия или время изменения для синхронизации.
  4. Шифрование — локальная база шифруется, токены хранятся в защищённом хранилище системы.
  5. Очистка — при выходе из аккаунта данные удаляются.

Как синхронизировать изменения?

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

type PendingOperation = {
  id: string            // уникальный ключ — сервер не создаст дубль при повторе
  entity: 'delivery'
  entityId: string
  action: 'update_status'
  payload: { status: 'delivered'; signedAt: string; photoId?: string }
  createdAt: string
  attempts: number
}

Правила синхронизации:

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

Как разрешать конфликты?

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

  • последнее изменение побеждает — просто, подходит для некритичных полей;
  • слияние по полям — разные поля из разных источников объединяются;
  • приоритет источника — например, статус от диспетчера важнее статуса из приложения;
  • ручное разрешение — пользователь видит обе версии и выбирает;
  • только добавление — для журналов и комментариев конфликтов нет вовсе.

Как тестировать офлайн-режим?

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

  • эмуляция медленной и нестабильной сети;
  • обрыв связи во время отправки и загрузки файлов;
  • закрытие приложения с неотправленными изменениями;
  • одновременное изменение одной записи на двух устройствах;
  • долгая работа без сети и синхронизация большого объёма.

О тестировании мобильных и веб-продуктов — статьи ручное тестирование в небольшой команде и Flutter, React Native или натив.

Коротко

  • Офлайн-режим нужен, когда работа пользователя не должна зависеть от связи.
  • Уровни: сообщение об ошибке, кеш для просмотра, отложенные действия, offline-first.
  • Интерфейс читает данные из локальной базы, а сеть служит для синхронизации.
  • Изменения копятся в очереди операций с ключами идемпотентности.
  • Стратегию разрешения конфликтов выбирают для каждого типа данных заранее.

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

  • Можно ли сделать офлайн-режим в веб-приложении?

    Да, через Service Worker и хранилище браузера IndexedDB. Возможности скромнее, чем в нативном приложении, но для просмотра данных и заполнения форм без сети этого обычно хватает.

  • Насколько офлайн-режим увеличивает стоимость разработки?

    Кеш для просмотра добавляет немного. Полноценный offline-first с изменением данных, синхронизацией и конфликтами может увеличить объём работы над соответствующими функциями в полтора-два раза.

  • Как защитить данные, хранящиеся на устройстве?

    Хранить на устройстве только необходимое, шифровать локальную базу, хранить токены в защищённом хранилище системы и очищать данные при выходе из аккаунта.