Repository Pattern & Data Layer

📖 Concept

The Repository pattern is the cornerstone of Android's data layer. It provides a clean API for data access, abstracting the data sources (network, database, cache) from the rest of the app.

Responsibilities:

  1. Single source of truth — Decide which data source to use (usually local DB)
  2. Data synchronization — Keep local and remote data in sync
  3. Caching — Cache network responses in local storage
  4. Error handling — Translate network/database errors to domain errors
  5. Data mapping — Convert API DTOs and DB entities to domain models

The offline-first pattern:

UI reads from: Room DB (always available, always fast)
Network call → save to Room → Room emits update → UI refreshes

This ensures the app works offline and provides instant data display.

Key design decisions:

  • Expose Flow, not suspend functions — Flows allow real-time updates when DB changes
  • Map at boundaries — DTOs → Entity → Domain Model. Don't leak API shapes to the UI.
  • Error handling — Use Result/Either types, not exceptions, for expected failures

💻 Code Example

codeTap to expand ⛶
1// Complete Repository implementation — offline-first
2
3// Domain model (clean, no annotations)
4data class Article(
5 val id: String,
6 val title: String,
7 val content: String,
8 val author: String,
9 val publishedAt: Instant,
10 val isBookmarked: Boolean = false
11)
12
13// Network DTO (Moshi/Gson annotations)
14@JsonClass(generateAdapter = true)
15data class ArticleDto(
16 @Json(name = "id") val id: String,
17 @Json(name = "title") val title: String,
18 @Json(name = "body") val content: String, // Different field name!
19 @Json(name = "author_name") val author: String,
20 @Json(name = "published_at") val publishedAt: String
21)
22
23// Room Entity (database annotations)
24@Entity(tableName = "articles")
25data class ArticleEntity(
26 @PrimaryKey val id: String,
27 val title: String,
28 val content: String,
29 val author: String,
30 val publishedAt: Long,
31 val isBookmarked: Boolean = false,
32 val lastFetchedAt: Long = System.currentTimeMillis()
33)
34
35// Mappers — keep at data layer boundaries
36fun ArticleDto.toEntity() = ArticleEntity(
37 id = id, title = title, content = content,
38 author = author, publishedAt = Instant.parse(publishedAt).toEpochMilli()
39)
40
41fun ArticleEntity.toDomain() = Article(
42 id = id, title = title, content = content,
43 author = author, publishedAt = Instant.ofEpochMilli(publishedAt),
44 isBookmarked = isBookmarked
45)
46
47// Repository — single source of truth pattern
48class ArticleRepositoryImpl @Inject constructor(
49 private val api: ArticleApi,
50 private val dao: ArticleDao,
51 @IoDispatcher private val ioDispatcher: CoroutineDispatcher
52) : ArticleRepository {
53
54 // Expose Flow from Room — UI always gets latest data
55 override fun getArticles(): Flow<List<Article>> {
56 return dao.getAllArticles().map { entities ->
57 entities.map { it.toDomain() }
58 }.onStart {
59 // Trigger network refresh in background
60 refreshArticles()
61 }.flowOn(ioDispatcher)
62 }
63
64 // Network refresh — save to DB, Room Flow auto-updates UI
65 private suspend fun refreshArticles() {
66 try {
67 val response = api.getArticles()
68 val entities = response.map { it.toEntity() }
69 dao.upsertAll(entities) // Room emits new data via Flow
70 } catch (e: Exception) {
71 // Don't throw — stale local data is better than no data
72 Timber.e(e, "Failed to refresh articles")
73 }
74 }
75
76 override suspend fun toggleBookmark(articleId: String) {
77 dao.toggleBookmark(articleId)
78 // Optionally sync to server
79 }
80}

🏋️ Practice Exercise

Practice:

  1. Implement a complete Repository with offline-first pattern and sync
  2. Add cache expiration — refresh from network if data is older than 15 minutes
  3. Implement error handling that distinguishes network errors from server errors
  4. Create mappers for a model with 3 layers (DTO → Entity → Domain)
  5. Write unit tests for the Repository using fake data sources

⚠️ Common Mistakes

  • Exposing API DTOs directly to the UI layer — any API change breaks the UI

  • Throwing exceptions from the Repository — use Result/sealed classes for expected failures

  • Not using Flow for data that can change — suspend functions miss real-time updates

  • Putting caching logic in the ViewModel — caching is a data layer concern

  • Making the Repository a god class — split into smaller repos per data domain

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Repository Pattern & Data Layer. Login to unlock this feature.