# How the Repository Pattern Coordinates Between Network and Database in Android

> Learn how the repository pattern in gsygithubappcompose coordinates network and database calls. Get cached data first, then update with fresh API results for a single source of truth.

- Repository: [Shuyu Guo/gsygithubappcompose](https://github.com/carguo/gsygithubappcompose)
- Tags: deep-dive
- Published: 2026-02-26

---

**The repository pattern in `gsygithubappcompose` coordinates network and database interactions by emitting cached Room data first via Kotlin Flow, then fetching fresh data from the Retrofit API, updating the local database, and emitting the network result to maintain a single source of truth.**

The open-source Android application `carguo/gsygithubappcompose` demonstrates a production-ready implementation of the repository pattern that seamlessly coordinates between remote REST APIs and local SQLite storage. By abstracting data access behind repository classes located in `data/src/main/java/com/shuyu/gsygithubappcompose/data/repository`, the app provides a unified interface that handles offline-first scenarios and reactive data updates automatically.

## Core Architecture of the Repository Pattern

Each repository in the codebase acts as a mediator between the UI layer and multiple data sources. The architecture relies on **dependency injection** to provide both network and database access objects.

### Data Source Abstraction

Repositories inject a `GitHubApiService` for network operations and corresponding Room DAOs for local persistence. In [`UserRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/UserRepository.kt), the constructor receives both the API service and multiple DAOs:

```kotlin
class UserRepository @Inject constructor(
    private val apiService: GitHubApiService,
    private val userDao: UserDao,
    private val eventDao: EventDao,
    private val repoDao: RepoDao
) {
    // Repository implementation
}

```

This design allows the repository to query local storage immediately while simultaneously initiating network requests, ensuring the UI never waits for remote data when cached content is available.

### The RepositoryResult Wrapper

All repository methods return `Flow<RepositoryResult<T>>`, a standardized envelope defined in [`RepositoryResult.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/RepositoryResult.kt) that carries three critical pieces of information:

- **`Result<T>`**: Contains either the successful data or the failure exception
- **`DataSource`**: An enum distinguishing between `DataSource.CACHE` and `DataSource.NETWORK`
- **`isDbEmpty`**: A boolean flag indicating whether the database was empty before the request initiated

This wrapper enables ViewModels to distinguish between stale cached data and fresh network updates while handling loading states consistently across the application.

## Implementing Cache-First Data Flow

The repository pattern implements an **offline-first** strategy where data always flows from local storage first, then network. Two generic helper functions centralize this logic to eliminate code duplication.

### Generic Helper Functions

Located at the bottom of [`UserRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/UserRepository.kt), the `getFromCacheAndNetwork` helper orchestrates single-object fetching:

```kotlin
private fun <T, R> getFromCacheAndNetwork(
    cacheFlow: Flow<T?>,
    networkCall: suspend () -> R,
    cacheUpdate: suspend (R) -> Unit,
    toDomain: (T) -> R
): Flow<RepositoryResult<R>> = flow {
    val cached = cacheFlow.first()
    val empty = cached == null
    
    if (cached != null) {
        emit(RepositoryResult(Result.success(toDomain(cached)), DataSource.CACHE, empty))
    }
    
    try {
        val fresh = networkCall()
        cacheUpdate(fresh)
        emit(RepositoryResult(Result.success(fresh), DataSource.NETWORK, empty))
    } catch (e: Exception) {
        emit(RepositoryResult(Result.failure(e), DataSource.NETWORK, empty))
    }
}

```

This function emits cached data immediately (if present), then executes the network call, updates the database via `cacheUpdate`, and finally emits the fresh network result.

### Handling Paginated Lists

For paginated data such as user events or repository issues, the `getPaginatedFromCacheAndNetwork` helper extends this pattern to handle lists. It checks the database for the first page only, emits cached items, fetches from the network, replaces the database content on page one, and emits the network result.

## Real-World Implementation Examples

The repository pattern manifests differently across various features, adapting to specific caching requirements while maintaining consistent core mechanics.

### User Profile Loading

In [`UserRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/UserRepository.kt), the `getUser(username)` method demonstrates cache-then-network coordination for user profiles:

1. Queries `userDao.getUserByLogin` and emits cached user data immediately
2. Calls `apiService.getUser` to fetch enriched profile data including starred repository counts
3. Inserts or replaces the user record in `UserDao` using `cacheUpdate`
4. Emits the network result with `DataSource.NETWORK` origin

This ensures the UI displays profile information instantly while background updates refresh the data.

### Trending Repositories

[`TrendingRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/TrendingRepository.kt) handles simple list caching for trending repositories:

- Reads all rows from `TrendingDao` and emits cache via `Flow`
- Calls `apiService.getTrendingRepos` with language and time period filters
- Executes `trendingDao.clearTrendingRepos()` followed by `insertAll` to atomically replace stored data
- Emits fresh network results to complete the flow

### Issue Lists with Pagination

[`IssueRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/IssueRepository.kt) implements sophisticated coordination for repository issues, handling both search queries and standard pagination:

- For page one without search filters, queries `issueDao.getIssues` and emits cached issues
- Calls either `apiService.searchIssues` or `apiService.getRepositoryIssues` based on query parameters
- On successful first-page responses, clears existing issues and inserts new rows via `IssueDao`
- Emits both cached (when available) and network results through the standard `RepositoryResult` wrapper

## Benefits of This Coordination Strategy

The repository pattern implementation in `gsygithubappcompose` delivers several architectural advantages:

- **Single Source of Truth**: Room database updates occur immediately after successful network calls, ensuring subsequent data collections start from fresh cache without redundant API requests
- **Offline-First Behavior**: When devices lack connectivity, flows still emit cached data while network failures wrap errors in `RepositoryResult` without crashing the UI
- **Consistent UI Contracts**: Standardized return types allow ViewModels to handle all repositories uniformly, processing `Result`, `DataSource`, and `isDbEmpty` flags through shared logic
- **Testability**: Injected dependencies enable unit testing with fake API services and in-memory databases, isolating repository logic from external infrastructure

## Summary

- The repository pattern in `carguo/gsygithubappcompose` abstracts `GitHubApiService` and Room DAOs behind unified repository classes in `data/src/main/java/com/shuyu/gsygithubappcompose/data/repository`
- All data flows emit as `Flow<RepositoryResult<T>>`, first from cache (`DataSource.CACHE`) then network (`DataSource.NETWORK`)
- Generic helpers `getFromCacheAndNetwork` and `getPaginatedFromCacheAndNetwork` eliminate duplication while enforcing consistent cache-then-network semantics
- Real implementations in [`UserRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/UserRepository.kt), [`TrendingRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/TrendingRepository.kt), and [`IssueRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/IssueRepository.kt) demonstrate single-object, list replacement, and paginated synchronization strategies
- The architecture provides offline-first capabilities, single source of truth guarantees, and testable data layers through dependency injection

## Frequently Asked Questions

### What is the single source of truth in this repository pattern?

The local Room database serves as the single source of truth. After every successful network call, repositories immediately update the corresponding DAOs (such as `UserDao` or `TrendingDao`), ensuring that all subsequent data requests—whether from network responses or future UI sessions—reference the same persisted state.

### How does the repository pattern handle offline scenarios?

When devices lack connectivity, the repository still emits cached data from Room via the initial `cacheFlow.first()` call. The network attempt fails and emits a `RepositoryResult` containing the exception as a `Result.failure`, but the UI continues displaying cached content. This offline-first approach ensures users always see data when available locally.

### What is the purpose of the RepositoryResult wrapper?

`RepositoryResult` standardizes communication between repositories and ViewModels by packaging three elements: the actual data or error in a `Result<T>`, the origin of the data (`DataSource.CACHE` or `DataSource.NETWORK`), and a flag indicating whether the database was empty before the request. This allows UI layers to distinguish between initial loads, background updates, and error states consistently across all features.

### How does pagination work with the cache-first strategy?

For paginated lists like user events in [`UserRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/UserRepository.kt), the repository only queries the database for page one, emitting cached items immediately. It then fetches the requested page from the network, but only updates the database (clearing and inserting) when receiving page one results. Subsequent pages bypass the cache query and emit directly from network, preventing stale data from mixing with fresh paginated results.