Repository pattern.

Where does data come from? Network? Database? Cache? The UI should not care. A Repository abstracts data sources. Today we understand the Repository pattern and how it fits in Android architecture.

A Repository provides data to the application.

It hides where data comes from. Network, database, in-memory cache. The caller just asks for data.

The ViewModel calls repository dot getUsers. It gets users. It does not know if they came from network or cache.

This abstraction is powerful. Change the data source, the caller does not change. Test with fake data, the caller does not change.

Repository is the boundary between data and domain.

Separation of concerns.

ViewModel manages UI state. Repository manages data. Each has one job.

Testability.

Replace the repository with a fake in tests. No network needed. No database needed. Fast, reliable tests.

Flexibility.

Start with network only. Add caching later. The ViewModel does not change.

Single responsibility.

Data fetching logic in one place. Not scattered across ViewModels.

A Repository can combine multiple data sources.

Network for fresh data. Database for persistence. Cache for speed.

But there is one source of truth. Usually the database.

Fetch from network. Store in database. UI reads from database. The database is the source of truth.

Why? Network is unreliable. Cache can be stale. Database persists. It is the reliable source.

UI observes the database. Network updates the database. UI sees updates.

Define a repository as an interface.

interface UserRepository brace suspend fun getUsers colon List of User suspend fun getUser id Int colon User fun observeUsers colon Flow of List of User close brace.

The interface declares what data is available. Not how it is fetched.

Implementations can vary. NetworkUserRepository. CachingUserRepository. FakeUserRepository for tests.

Depend on the interface, not the implementation. This enables flexibility and testing.

Repository functions are often suspend functions.

suspend fun getUsers colon List of User. Fetching data takes time. Suspension makes it non-blocking.

For one-time requests, use suspend functions. Fetch user by ID. Create a new item. Delete.

For observable data, return Flow. fun observeUsers colon Flow of List of User. UI collects, receives updates.

The repository handles the async nature of data fetching. Callers use suspend or collect.

A simple repository implementation.

class UserRepositoryImpl constructor api UserApi, dao UserDao colon UserRepository.

getUsers: try to fetch from network, store in database, return from database.

Fallback: if network fails, return cached data from database.

The implementation decides the strategy. The interface stays simple.

More complex strategies are possible. Check cache age. Fetch in background. Sync periodically.

Cache-first: check cache, return if valid. Otherwise fetch.

Network-first: always fetch, update cache.

Stale-while-revalidate: return cache immediately, fetch in background, update when ready.

Offline-first: always return cache. Sync when possible.

Choose based on your needs. Freshness requirements. Offline support. Performance.

The repository encapsulates the strategy. Callers are unaware.

Repository sits between ViewModel and data sources.

UI talks to ViewModel. ViewModel talks to Repository. Repository talks to API and Database.

Dependencies point inward. Repository does not know about ViewModel. API does not know about Repository.

This layering makes each component testable and replaceable.

Common pattern: Compose to ViewModel to Repository to data sources.

Let us summarize.

Repository abstracts data sources from the rest of the app.

It provides a clean API. Callers do not know where data comes from.

Single source of truth keeps data consistent.

Use suspend functions for one-time operations. Use Flow for observable data.

Repositories enable testing, flexibility, and separation of concerns.

Define interface, implement with real sources, test with fakes.