Offline mode 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 mode in mobile app
Complex
~3-5 business days
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 Mode in Mobile Application

Offline mode is not just "show cache when there's no network." It's an architectural decision affecting all app layers: how to store data, show actuality, what user can do without internet, queue actions and sync after reconnection.

Mobile internet drops in metro, elevator, poor coverage. App that just spins and waits loses users.

Architectural Foundation: Local-First

Principle is simple: local database is UI source of truth. Network is synchronization, not requirement for displaying data.

UI → ViewModel → Repository
                    ├── LocalDataSource (Room/SQLite)  ← UI reads from here
                    └── RemoteDataSource (API)         ← background sync

UI never makes direct network requests. Everything through Repository, which first returns local data, then updates in background.

class ArticleRepository(
    private val localDao: ArticleDao,
    private val api: ArticleApi,
    private val syncManager: SyncManager
) {
    // UI subscribed to this Flow — gets data immediately from DB
    fun observeArticles(categoryId: String): Flow<List<Article>> =
        localDao.observeByCategory(categoryId)
            .map { entities -> entities.map { it.toDomain() } }

    // Called on startup, pull-to-refresh, network recovery
    suspend fun refresh(categoryId: String) {
        try {
            val remote = api.getArticles(categoryId)
            localDao.upsertAll(remote.map { it.toEntity() })
        } catch (e: NetworkException) {
            // Don't propagate — UI just sees old data
            syncManager.scheduleSyncWhenOnline(SyncTask.RefreshArticles(categoryId))
        }
    }
}

Detecting Network State

On Android — ConnectivityManager with 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 doesn't mean real internet — captive portal (hotel WiFi without auth) passes this check. For reliability add NET_CAPABILITY_VALIDATED.

On iOS — NWPathMonitor from 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))

Offline Actions: Operation Queue

User taps "Send" without internet. Can't just show error. Right way — queue the action:

@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
)

On network recovery — WorkManager processes the queue:

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) // show error to user
                }
            }
        }
        return Result.success()
    }
}

// WorkManager registration with network condition
val syncRequest = OneTimeWorkRequestBuilder<OfflineSyncWorker>()
    .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.SECONDS)
    .build()

WorkManager on Android is the right tool for deferred operations. Survives app and device restart. Don't use coroutines directly for this — they live only while process is alive.

On iOS — Background Tasks framework (BGTaskScheduler):

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

UX: What to Show User

Bland "No internet" toast is bad. User needs to understand:

  • Data is fresh or stale (and how much)
  • What they can do offline
  • What queues and executes later

Show last sync timestamp in screen header. Send button offline changes text to "Send when connected" and changes style. Pending operations show in UI as "waiting for sync" until server confirms.

Common Problems

Optimistic update without rollback. Updated UI immediately (optimistically), operation in queue — user sees change. Server returned error — need rollback mechanism. Without it, UI shows non-existent state.

Concurrent writes. User made changes offline, same data changed on another device in parallel. Need conflict resolution strategy — separate task.

Large data volumes. Don't cache everything. Cache what user likely opens: current screen, data from last N days, favorites.

Offline mode implementation with operation queue, WorkManager and UX for both platforms: 3–5 weeks depending on domain logic complexity. Cost calculated individually.