Реалізація офлайн-режиму роботи мобільного додатку

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

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

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

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1735 послуг
Реалізація офлайн-режиму роботи мобільного додатку
Складний
~3-5 днів
Часті запитання

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

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

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

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    495

Реалізація офлайн-режиму роботи мобільного додатка

Офлайн-режим — не просто «показувати кеш коли немає сіті». Це архітектурне рішення, яке торкається всіх шарів додатка: як зберігати дані, показувати актуальність, що дозволяти робити користувачу без інтернету, ставити дії в очередь та як синхронізувати після відновлення зв'язку.

Мобільний інтернет рвється в метро, в ліфті, при поганому покритті. Додаток, який просто висить спіннер та чекає — втрачає користувачів.

Архітектурний фундамент: local-first

Принцип простий: локальна база — джерело правди для UI. Мережа — це синхронізація, а не обов'язкова умова для відображення даних.

UI → ViewModel → Repository
                    ├── LocalDataSource (Room/SQLite)  ← UI читає звідси
                    └── RemoteDataSource (API)         ← фонова синхронізація

UI ніколи не робить прямих сітьових запитів. Все через Repository, який спочатку видає локальні дані, а у фоні оновлює їх з сервера.

class ArticleRepository(
    private val localDao: ArticleDao,
    private val api: ArticleApi,
    private val syncManager: SyncManager
) {
    // UI підписаний на цей Flow — отримує дані сразу з БД
    fun observeArticles(categoryId: String): Flow<List<Article>> =
        localDao.observeByCategory(categoryId)
            .map { entities -> entities.map { it.toDomain() } }

    // Викликається при старті, pull-to-refresh, відновленні мережі
    suspend fun refresh(categoryId: String) {
        try {
            val remote = api.getArticles(categoryId)
            localDao.upsertAll(remote.map { it.toEntity() })
        } catch (e: NetworkException) {
            // Не пробрасуємо — UI просто видить старі дані
            syncManager.scheduleSyncWhenOnline(SyncTask.RefreshArticles(categoryId))
        }
    }
}

Визначення стану мережі

На Android — ConnectivityManager з NetworkCallback:

class NetworkMonitor(context: Context) {
    private val connectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    val isOnline: StateFlow<Boolean> = callbackFlow {
        val callback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) { trySend(true) }
            override fun onLost(network: Network) { trySend(false) }
        }
        val request = NetworkRequest.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .build()
        connectivityManager.registerNetworkCallback(request, callback)
        awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
    }.stateIn(
        scope = CoroutineScope(Dispatchers.IO),
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = connectivityManager.isCurrentlyConnected()
    )
}

NET_CAPABILITY_INTERNET не означає реального інтернету — captive portal (WiFi в готелі без авторизації) проходить цю перевірку. Для надійності додайте NET_CAPABILITY_VALIDATED.

На iOS — NWPathMonitor з Network framework:

let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
    let isConnected = path.status == .satisfied
    DispatchQueue.main.async {
        self.networkState = isConnected ? .online : .offline
    }
}
monitor.start(queue: DispatchQueue.global(qos: .background))

Офлайн-дії: очередь операцій

Користувач натиснув «Відправити» без інтернету. Не можна просто видати помилку. Правильно — поставити дію в очередь:

@Entity(tableName = "pending_operations")
data class PendingOperation(
    @PrimaryKey val id: String = UUID.randomUUID().toString(),
    val type: String,           // "CREATE_ORDER", "UPDATE_PROFILE", "DELETE_ITEM"
    val payload: String,        // JSON
    val createdAt: Long = System.currentTimeMillis(),
    val retryCount: Int = 0,
    val status: String = "PENDING"  // PENDING, PROCESSING, FAILED
)

При відновленні мережі — WorkManager обробляє очередь:

class OfflineSyncWorker(
    context: Context,
    params: WorkerParameters,
    private val operationDao: PendingOperationDao,
    private val api: AppApi
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val pending = operationDao.getPendingOperations()

        for (operation in pending) {
            try {
                operationDao.markProcessing(operation.id)
                when (operation.type) {
                    "CREATE_ORDER" -> {
                        val order = Json.decodeFromString<CreateOrderRequest>(operation.payload)
                        api.createOrder(order)
                    }
                    "UPDATE_PROFILE" -> {
                        val update = Json.decodeFromString<UpdateProfileRequest>(operation.payload)
                        api.updateProfile(update)
                    }
                }
                operationDao.delete(operation.id)
            } catch (e: Exception) {
                operationDao.incrementRetry(operation.id)
                if (operation.retryCount >= 3) {
                    operationDao.markFailed(operation.id)
                    notifyUser(operation) // показати помилку користувачу
                }
            }
        }
        return Result.success()
    }
}

// Реєстрація WorkManager з умовою наявності мережі
val syncRequest = OneTimeWorkRequestBuilder<OfflineSyncWorker>()
    .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.SECONDS)
    .build()

WorkManager на Android — правильний інструмент для відкладених операцій. Переживає перезапуск додатка та пристрою. Не використовуйте корутини напрямі для цього — вони живуть тільки поки живий процес.

На iOS — Background Tasks framework (BGTaskScheduler):

BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.app.offline-sync",
    using: nil) { task in
    self.handleOfflineSync(task: task as! BGProcessingTask)
}

UX: що показувати користувачу

Звичайний toast «Немає інтернету» — погано. Користувачу важливо розуміти:

  • Дані актуальні чи застаріли (та на скільки)
  • Що він може робити офлайн
  • Що встане в очередь та виконається пізніше

Показуємо timestamp останньої синхронізації в шапці екрана. Кнопка «Відправити» офлайн — змінює текст на «Відправити при підключенні» та змінює стиль. Pending-операції відображаються в UI як «очікує синхронізації» до підтвердження з сервера.

Типові проблеми

Optimistic update без rollback. Обновили UI сразу (оптимістично), операція в очереди — користувач видит зміну. Сервер вернув помилку — потрібен механізм rollback. Без нього UI показує неіснуючий стан.

Конкурентні записи. Користувач зробив зміни офлайн, паралельно ті ж дані змінилися на іншому пристрої. Потрібна стратегія конфліkt-резолюції — це окрема задача.

Великі обсяги даних. Не потрібно кешувати все. Потрібно кешувати те, що користувач з високою ймовірністю відкриє: поточний екран, дані за останні N днів, вибране.

Реалізація офлайн-режиму з очередею операцій, WorkManager та UX для двох платформ: 3–5 тижнів залежно від складності доменної логіки. Вартість розраховується індивідуально.