100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Kotlin

Repository Pattern in Android

The repository pattern centralizes data access behind a single abstraction, hiding whether data comes from Room, Retrofit, or DataStore from ViewModels and the UI layer.

Data & NetworkingIntermediate9 min readJul 8, 2026
Analogies

Repository Pattern in Android

A repository is a single class (or interface plus implementation) that ViewModels talk to for a given domain concept — tasks, users, weather — without knowing or caring whether the data ultimately comes from a local Room database, a network call through Retrofit, an in-memory cache, or some combination of all three. This matters because a ViewModel's job is to hold and expose UI state, not to decide caching policy or reconcile conflicting data sources; pushing those decisions into a repository keeps the ViewModel simple and makes the data-fetching strategy independently testable and swappable. In a typical layered Android app, the repository sits between the data layer (Room DAOs, Retrofit API interfaces, DataStore) and the domain/presentation layer (ViewModels), and it's the natural place to implement patterns like offline-first caching, where the repository serves cached Room data instantly while a background refresh from the network updates that same cache.

🏏

Cricket analogy: A repository hiding whether data comes from Room, Retrofit, or cache is like a team's analyst desk feeding the captain a single stats sheet, without the captain needing to know whether numbers came from today's live feed or last season's archive.

Interfaces and testability

Defining a repository as an interface with a concrete implementation is a small amount of extra ceremony that pays off directly in testing: a ViewModel unit test can inject a fake implementation of the repository interface that returns canned data instantly, with no real database or network involved, making the test fast and deterministic. This also decouples the ViewModel from a specific data-layer technology — if the app later swaps Retrofit for a different networking library, only the repository implementation changes, and every ViewModel that depends on the repository interface is unaffected.

🏏

Cricket analogy: Injecting a fake repository implementation in a ViewModel test is like a coach running fielding drills with a practice ball machine instead of waiting for a live bowler, getting fast, predictable reps.

Single source of truth and offline-first

A well-designed repository typically establishes one source of truth for the UI to observe — usually the local Room database — and treats the network as a way to refresh that source of truth rather than a second place the UI reads from directly. Concretely, the repository exposes a Flow backed by a Room query, and a separate suspend function triggers a network fetch that, on success, writes the results into Room. Because the UI is only ever observing the Room-backed Flow, it automatically reflects both the immediately available cached data and the freshly fetched data once the write completes, without the ViewModel needing to merge two different data streams itself.

🏏

Cricket analogy: A repository exposing a Room-backed Flow as the single source of truth is like fans only ever watching the stadium's official scoreboard, which updates itself the instant the scorer's booth confirms a run, rather than checking multiple radio commentaries.

kotlin
interface TaskRepository {
    fun observeTasks(): Flow<List<Task>>
    suspend fun refreshTasks(): Result<Unit>
    suspend fun addTask(title: String)
}

class DefaultTaskRepository(
    private val taskDao: TaskDao,
    private val api: TaskApi
) : TaskRepository {

    override fun observeTasks(): Flow<List<Task>> =
        taskDao.observeAllTasks().map { entities -> entities.map { it.toDomain() } }

    override suspend fun refreshTasks(): Result<Unit> = try {
        val remoteTasks = api.getTasks()
        taskDao.insertAll(remoteTasks.map { it.toEntity() })
        Result.success(Unit)
    } catch (e: IOException) {
        Result.failure(e)
    } catch (e: HttpException) {
        Result.failure(e)
    }

    override suspend fun addTask(title: String) {
        val id = taskDao.insert(TaskEntity(title = title))
        runCatching { api.createTask(TaskRequest(title)) }
    }
}

The repository pattern is essentially the Facade pattern applied to data access: it presents one simple interface (observeTasks, refreshTasks) while coordinating multiple, more complex subsystems (Room, Retrofit) behind the scenes.

A repository that exposes raw Retrofit response types or Room entities directly to the ViewModel has only partially achieved the pattern's goal. Map data-layer models to domain models inside the repository so a change to the network DTO or the Room entity schema doesn't ripple up into the UI layer.

  • A repository centralizes data access for a domain concept, hiding Room/Retrofit/DataStore details from ViewModels.
  • Defining repositories as interfaces enables fast, deterministic ViewModel unit tests using fake implementations.
  • A single source of truth — typically Room — lets the UI observe one Flow that reflects both cached and freshly fetched data.
  • Network calls in an offline-first repository write into the local database rather than being read by the UI directly.
  • Repositories should map data-layer models (entities, DTOs) into domain models before exposing them upward.
  • The pattern is effectively a facade over multiple, more complex data subsystems.

Practice what you learned

Was this page helpful?

Topics covered

#Kotlin#AndroidWithJetpackComposeStudyNotes#MobileDevelopment#RepositoryPatternInAndroid#Repository#Pattern#Android#Interfaces#StudyNotes#SkillVeris