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:
- Single source of truth — Decide which data source to use (usually local DB)
- Data synchronization — Keep local and remote data in sync
- Caching — Cache network responses in local storage
- Error handling — Translate network/database errors to domain errors
- 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
1// Complete Repository implementation — offline-first23// 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 = false11)1213// 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: String21)2223// 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)3435// Mappers — keep at data layer boundaries36fun ArticleDto.toEntity() = ArticleEntity(37 id = id, title = title, content = content,38 author = author, publishedAt = Instant.parse(publishedAt).toEpochMilli()39)4041fun ArticleEntity.toDomain() = Article(42 id = id, title = title, content = content,43 author = author, publishedAt = Instant.ofEpochMilli(publishedAt),44 isBookmarked = isBookmarked45)4647// Repository — single source of truth pattern48class ArticleRepositoryImpl @Inject constructor(49 private val api: ArticleApi,50 private val dao: ArticleDao,51 @IoDispatcher private val ioDispatcher: CoroutineDispatcher52) : ArticleRepository {5354 // Expose Flow from Room — UI always gets latest data55 override fun getArticles(): Flow<List<Article>> {56 return dao.getAllArticles().map { entities ->57 entities.map { it.toDomain() }58 }.onStart {59 // Trigger network refresh in background60 refreshArticles()61 }.flowOn(ioDispatcher)62 }6364 // Network refresh — save to DB, Room Flow auto-updates UI65 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 Flow70 } catch (e: Exception) {71 // Don't throw — stale local data is better than no data72 Timber.e(e, "Failed to refresh articles")73 }74 }7576 override suspend fun toggleBookmark(articleId: String) {77 dao.toggleBookmark(articleId)78 // Optionally sync to server79 }80}
🏋️ Practice Exercise
Practice:
- Implement a complete Repository with offline-first pattern and sync
- Add cache expiration — refresh from network if data is older than 15 minutes
- Implement error handling that distinguishes network errors from server errors
- Create mappers for a model with 3 layers (DTO → Entity → Domain)
- 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.