Interview QuestionPractical QuestionFollow-up Questions

Offline First Architecture

skydovesJaewoong Eum (skydoves)||7 min read

Offline First Architecture

Offline first design ensures that an application functions without an active network connection by relying on locally stored data and synchronizing with a remote server when connectivity is restored. This approach improves user experience in environments with unreliable or intermittent internet access, which is common on mobile devices. By the end of this lesson, you will be able to:

  • Describe the core principles of offline first architecture on Android.
  • Explain how Room and WorkManager work together to support local persistence and background synchronization.
  • Define read through and write through caching strategies.
  • Identify conflict resolution approaches for syncing local and remote data.

Local Data Persistence with Room

A reliable offline first strategy starts with local storage as the single source of truth. The Room database is the recommended solution for structured data on Android. It generates SQLite queries at compile time, integrates with Kotlin Flow for reactive UI updates, and supports coroutines for asynchronous operations.

@Entity
data class Article(
    @PrimaryKey val id: Int,
    val title: String,
    val content: String,
    val isSynced: Boolean = false
)

@Dao
interface ArticleDao {
    @Query("SELECT * FROM Article")
    fun getAllArticles(): Flow<List<Article>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertArticle(article: Article)
}

The isSynced flag tracks whether each record has been pushed to the server. The UI observes getAllArticles() as a Flow, so it always reflects the latest local state regardless of network availability.

Background Synchronization with WorkManager

WorkManager handles deferred tasks that must execute reliably, even if the app is killed or the device restarts. It is the right tool for syncing local changes to a remote server because it supports constraints like network availability and automatic retry on failure.

class SyncWorker(
    appContext: Context,
    params: WorkerParameters
) : CoroutineWorker(appContext, params) {

    override suspend fun doWork(): Result {
        val dao = AppDatabase.getInstance(applicationContext)
            .articleDao()
        val unsynced = dao.getUnsyncedArticles()
        return if (syncToServer(unsynced)) {
            unsynced.forEach { dao.markSynced(it.id) }
            Result.success()
        } else {
            Result.retry()
        }
    }
}

This interview continues for subscribers

Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.

Become a Sponsor