Repository Pattern: What It Is, the Data Abstraction Pattern in iOS and Android

Author: IT Sectr Published: 2026-02-17 Reading time: 7 min

Repository Pattern — a pattern that adds an abstraction layer between business logic and data sources. Instead of directly calling the API, database, or cache, the Repository provides a unified interface for obtaining and storing data. This simplifies testing and switching between sources. Read more in the Android Data Layer documentation.

Key Takeaways

  • Repository Pattern — a layer between business logic and data sources (API, DB, cache)
  • DataSource — separate classes for each source: RemoteDataSource, LocalDataSource
  • Single source of truth — the Repository becomes the single data source for the UI layer
  • Testing — Repository can be easily replaced with a mock object via DI for unit tests
  • Compatibility — works with MVVM, Clean Architecture, and other architectural patterns

What Is the Repository Pattern in Mobile Development?

Repository Pattern is a structural pattern that isolates business logic from direct access to data sources. Instead of having an Activity, UIViewController, or ViewModel directly call Retrofit, URLSession, Room, or CoreData, they communicate with the Repository. The Repository decides where to get the data — from the network, database, or cache — and returns the result in a unified format. This implements the single responsibility principle — the UI does not know how or from where the data was obtained.

Repository components include an interface (protocol), an implementation, and one or more DataSources. A DataSource is a class that works with a single source: RemoteDataSource calls the API via an HTTP client, LocalDataSource reads and writes to the database. The Repository receives DataSources through the constructor (Dependency Injection) and decides which source to use. For example, when requesting a list of users, the Repository first checks the cache, then the database, then the network.

Benefits of the Repository Pattern: isolation of data source changes (API changes, database migrations) does not affect the UI layer; unit testing by mocking Repository or DataSource; caching is transparent to the UI; switching between online and offline modes without changing screen logic. The Android community recommends Repository as a mandatory layer in Clean Architecture.

Repository Pattern in iOS with Swift: Implementation and Example

iOS implementation of Repository is built on Swift protocols. The Repository protocol declares methods for obtaining and storing data. The actual implementation is injected through the initializer — this allows replacing the implementation in tests and SwiftUI previews. DataSources are also declared as protocols: Protocol RemoteDataSource, Protocol LocalDataSource. The ViewModel or Interactor does not know about a specific implementation — only about the Repository protocol.

swift
protocol UserRepository {
    func getUsers() async throws -> [User]
}

protocol UserRemoteDataSource {
    func fetchUsers() async throws -> [User]
}

protocol UserLocalDataSource {
    func getCachedUsers() throws -> [User]
    func saveUsers(_: [User]) throws
}

final class UserRepositoryImpl: UserRepository {
    private let remote: UserRemoteDataSource
    private let local: UserLocalDataSource

    init(remote: UserRemoteDataSource, local: UserLocalDataSource) {
        self.remote = remote
        self.local = local
    }

    func getUsers() async throws -> [User] {
        if let cached = try? local.getCachedUsers() {
            return cached
        }
        let users = try await remote.fetchUsers()
        try local.saveUsers(users)
        return users
    }
}

Dependency Injection in iOS for Repository is typically configured through a factory or DI container (Swinject, Factory). In tests, the UserRepository protocol is replaced with a mock implementation that returns predefined data. Async-await makes the code synchronous and readable without closures and delegates. For Combine reactivity, Repository methods return AnyPublisher instead of async throws.

Repository Pattern in Android with Kotlin: Example with Flow

Android implementation of Repository extensively uses Kotlin Coroutines and Flow for asynchronous operations. Google recommends Repository in the official Android architecture guide (Android Architecture Components). The Repository accepts RemoteDataSource (Retrofit) and LocalDataSource (Room) through the constructor, and the ViewModel subscribes to a Flow from the Repository. The Repository manages the data strategy: cache-first, network-first, or always network with cache write.

kotlin
interface UserRepository {
    fun getUsers(): Flow<Result<List<User>>>
}

interface UserRemoteDataSource {
    suspend fun fetchUsers(): List<User>
}

interface UserLocalDataSource {
    fun getCachedUsers(): Flow<List<User>>
    suspend fun saveUsers(users: List<User>)
}

class UserRepositoryImpl(
    private val remote: UserRemoteDataSource,
    private val local: UserLocalDataSource
) : UserRepository {

    override fun getUsers(): Flow<Result<List<User>>> = flow {
        emit(Result.Loading)
        local.getCachedUsers().collect { cached ->
            if (cached.isNotEmpty()) {
                emit(Result.Success(cached))
            }
        }
        try {
            val users = remote.fetchUsers()
            local.saveUsers(users)
            emit(Result.Success(users))
        } catch (e: Exception) {
            emit(Result.Error(e))
        }
    }
}

Result wrapper in the example above is standard for Android: a sealed class Result informs the ViewModel about the loading state (Loading, Success, Error). The ViewModel subscribes via collect and updates StateFlow or LiveData. Repository with Flow automatically notifies the UI of database changes — this is a key difference from one-shot requests where the UI does not know about changes without manual refresh.

DataSource: Remote, Local, and Data Caching

DataSource — classes responsible for working with a specific data source. RemoteDataSource uses an HTTP client (URLSession, Retrofit, Ktor) to fetch data from the API. LocalDataSource works with local storage (CoreData, Realm, Room, UserDefaults, DataStore). Each DataSource has a narrow responsibility: RemoteDataSource only knows about the API request format, LocalDataSource — about the database schema. The Repository combines them, implementing a caching strategy.

DataSourceiOS PlatformAndroid PlatformSource
RemoteURLSession + CodableRetrofit + Moshi/GsonREST / GraphQL API
Local (DB)CoreData, SwiftDataRoom, SQLDelightSQLite on device
Local (cache)NSCache, UserDefaultsDataStore, EncryptedSPIn-memory / disk
PreferenceUserDefaults, KeychainSharedPreferences, EncryptedSPSettings, tokens

Caching strategies in Repository: Cache-First (cache first, then background fetch), Network-Only (network only, for payment screens), Network-First-With-Cache-Backup (network first, fallback to cache on error). The choice of strategy depends on the scenario: a country list can be cached for a long time, currency rates — for 15 minutes, wallet balance — network only. The Repository implements the strategy and changes it without modifying the ViewModel or UI.

Repository Pattern vs Service Layer: Differences and How to Choose

Repository and Service are different patterns with overlapping functions. Repository is responsible for data access and caching, returning data models. Service (or Interactor, Use Case) contains business logic: validation, data transformation, orchestrating multiple Repository calls. Service can combine UserRepository, OrderRepository, and NotificationRepository to process an order. Repository does not contain business logic — only CRUD and caching.

When to choose Repository — data navigation with multiple sources (API + DB + cache), offline-first architecture, the need for caching and transparent source switching. Repository is mandatory in Clean Architecture and is recommended by Google for Android applications. In VIPER architecture on iOS, the Repository role is performed by the Interactor layer, interacting with Manager or Service for data access.

When Service is enough — simple applications with a single data source, read-only screens without writing, projects without offline mode. In such cases, DataSource is used directly by the ViewModel or Presenter, and Repository becomes an unnecessary layer. However, adding Repository early does not require significant effort and simplifies adding caching and tests in the future.

Frequently Asked Questions

How is Repository different from DataSource?

DataSource is a class that works with a single source (API, DB, cache). Repository is a class that manages multiple DataSources and provides a unified interface. The Repository decides which DataSource to use and coordinates caching. DataSource does not know about other sources; Repository does not know the implementation details of each source.

Is Repository necessary in iOS with SwiftUI?

Yes, Repository is useful in SwiftUI for separating data from View. The ViewModel subscribes to a Publisher from the Repository, and the Repository manages caching and synchronization. In simple applications, you can use URLSession directly in the ViewModel, but for testability and scalability, Repository is preferred. Apple does not enforce the pattern, but it is compatible with SwiftData and Network.framework.

How to test Repository with multiple DataSources?

DataSources are replaced with mock objects through Dependency Injection. The test creates a mock RemoteDataSource (returns predefined JSON) and a mock LocalDataSource (verifies data is saved). The Repository is tested in isolation: caching strategy, error handling, and correct call order are verified. For integration tests, TestDispatcher (Kotlin) or MainActor.run (Swift) is used.

Can Repository be used without an interface (protocol)?

It is possible but not recommended. Without a protocol, it is impossible to replace the implementation in tests and previews. In Kotlin, the Repository interface allows replacing the implementation via DI (Dagger, Hilt, Koin). In Swift, the Repository protocol is mandatory for testing async-await and Combine code. The exception is simple projects with a single data source where Repository has no caching logic.

What is offline-first in the context of Repository?

Offline-first is a strategy where the application works without the internet using local data. Repository plays a key role: it first returns data from the local DataSource and then synchronizes with the server in the background. The user sees data instantly, and Repository updates it after loading from the network. Room with Flow provides reactive UI updates when data changes in the local database.

Summary

  • Repository Pattern — an abstraction layer between UI and data sources
  • DataSource — separate classes for API, DB, and cache
  • Protocols — required for testing and replacing implementations
  • Caching strategies — Cache-First, Network-Only, Network-First-With-Cache-Backup
  • iOS — async-await or Combine with protocols
  • Android — Kotlin Flow + Room + Retrofit, Google-recommended approach
  • Testing — mock DataSources via DI, verify caching strategies

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also