Offline data sync with server in mobile app

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Offline data sync with server in mobile app
Complex
from 1 week to 3 months
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1050
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Implementing Offline Data Synchronization with Server

Synchronization is not just "load data on startup." It's a bidirectional process: client accumulates changes offline, server accumulates changes from other clients, on reconnection both must reach consistent state. Proper sync architecture is one of the most technically complex tasks in mobile development.

Delta Sync vs Full Sync

Most obvious implementation — on network recovery, request all data again. Works on small volumes. With 10,000 records downloading full list each time wastes traffic, time, and server load.

Delta sync — client sends lastSyncTimestamp, server returns only changed and deleted records since then.

data class SyncRequest(
    val lastSyncTimestamp: Long,
    val clientId: String
)

data class SyncResponse(
    val serverTimestamp: Long,       // response timestamp
    val updated: List<ProductDto>,   // changed or new
    val deletedIds: List<String>     // deleted on server
)

On client, save lastSuccessfulSyncTimestamp in MMKV or SharedPreferences. Next sync, use it as filter.

Important: time must be server time. If client uses its own time, clock skew causes misses. Server sends its timestamp in response — client saves exactly that.

SyncManager Architecture

class SyncManager(
    private val api: SyncApi,
    private val dao: ProductDao,
    private val pendingOpsDao: PendingOperationDao,
    private val prefs: SyncPreferences
) {
    suspend fun sync(): SyncResult {
        // 1. Upload accumulated offline operations
        val pending = pendingOpsDao.getAll()
        if (pending.isNotEmpty()) {
            try {
                val uploadResult = api.uploadOperations(pending.map { it.toRequest() })
                // Delete only successfully processed
                pendingOpsDao.deleteByIds(uploadResult.processedIds)
                // Failed ones stay in queue
            } catch (e: NetworkException) {
                return SyncResult.NetworkError
            }
        }

        // 2. Download server changes
        return try {
            val response = api.sync(
                SyncRequest(
                    lastSyncTimestamp = prefs.lastSyncTimestamp,
                    clientId = prefs.clientId
                )
            )

            dao.applyDelta(
                updated = response.updated.map { it.toEntity() },
                deletedIds = response.deletedIds
            )

            prefs.lastSyncTimestamp = response.serverTimestamp
            SyncResult.Success(
                updatedCount = response.updated.size,
                deletedCount = response.deletedIds.size
            )
        } catch (e: Exception) {
            SyncResult.Error(e)
        }
    }
}

applyDelta in transaction — atomic. Either apply all or nothing:

@Transaction
suspend fun applyDelta(updated: List<ProductEntity>, deletedIds: List<String>) {
    upsertAll(updated)
    softDeleteByIds(deletedIds, System.currentTimeMillis())
}

Soft delete mandatory: don't physically delete, set is_deleted = true flag and save timestamp. Otherwise, next delta sync we "forget" this deletion.

Sync Triggers

Launch sync in several scenarios:

class SyncScheduler(
    private val workManager: WorkManager,
    private val syncManager: SyncManager,
    private val networkMonitor: NetworkMonitor
) {
    init {
        // Periodic background sync
        val periodicSync = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
            .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
            .build()
        workManager.enqueueUniquePeriodicWork(
            "periodic-sync",
            ExistingPeriodicWorkPolicy.KEEP,
            periodicSync
        )
    }

    // On network recovery — immediate sync
    fun observeNetworkAndSync() {
        networkMonitor.isOnline
            .filter { it } // only offline→online transition
            .distinctUntilChanged()
            .onEach { triggerImmediateSync() }
            .launchIn(applicationScope)
    }

    // On app foreground
    fun onAppForeground() {
        val lastSync = prefs.lastSyncTimestamp
        val tooOld = System.currentTimeMillis() - lastSync > 5 * 60 * 1000L
        if (tooOld) triggerImmediateSync()
    }
}

iOS equivalent of WorkManager — BGAppRefreshTask and BGProcessingTask. Background tasks run at OS discretion and are time-limited (30 seconds for appRefresh, up to 3 minutes for processing).

Image and File Synchronization

Binary data separate from metadata. Sync file list (names, URLs, hashes), download files via separate requests with prioritization:

class MediaSyncManager {
    suspend fun syncMedia(mediaList: List<MediaMeta>) {
        val toDownload = mediaList.filter { meta ->
            !fileCache.exists(meta.localPath) ||
            fileCache.getHash(meta.localPath) != meta.serverHash
        }

        // Download in parallel, but limit concurrency
        toDownload.chunked(4).forEach { batch ->
            batch.map { meta ->
                async { downloadFile(meta) }
            }.awaitAll()
        }
    }
}

Chunks of 4 don't overload connection, on link loss we lose max 4 files from current batch.

Sync State in UI

User should see data freshness. Minimum: last sync timestamp. Better: status icon (synced / syncing / sync error) next to data that might be stale.

On sync error — don't block UI. Show warning, allow work with local data, offer retry.

Full bidirectional delta-sync implementation with operation queue and conflict handling: 4–8 weeks depending on data volume and entity types. Cost calculated individually.