The Repository pattern helps keep your app organized by separating data access from the rest of your code. It acts like a middleman between your app and data sources.
Repository pattern in Android Kotlin
interface UserRepository {
suspend fun getUser(userId: String): User
}
class UserRepositoryImpl(
private val remoteDataSource: RemoteDataSource,
private val localDataSource: LocalDataSource
) : UserRepository {
override suspend fun getUser(userId: String): User {
// Implementation to get user from local or remote
TODO("Not yet implemented")
}
}The repository is usually an interface with one or more implementations.
It hides where the data comes from, so the rest of the app just asks the repository.
interface ProductRepository {
suspend fun getProducts(): List<Product>
}class ProductRepositoryImpl( private val apiService: ApiService ) : ProductRepository { override suspend fun getProducts(): List<Product> { return apiService.fetchProducts() } }
class ProductRepositoryImpl( private val apiService: ApiService, private val localDb: ProductDao ) : ProductRepository { override suspend fun getProducts(): List<Product> { val cached = localDb.getAll() return if (cached.isNotEmpty()) cached else { val fresh = apiService.fetchProducts() localDb.insertAll(fresh) fresh } } }
This example shows a UserRepository that first tries to get a user from local cache. If not found, it fetches from remote and saves locally. The main function fetches the same user twice to show caching.
data class User(val id: String, val name: String) interface UserRepository { suspend fun getUser(userId: String): User } class RemoteDataSource { suspend fun fetchUser(userId: String): User { return User(userId, "Remote User") } } class LocalDataSource { private val cache = mutableMapOf<String, User>() suspend fun getUser(userId: String): User? { return cache[userId] } suspend fun saveUser(user: User) { cache[user.id] = user } } class UserRepositoryImpl( private val remoteDataSource: RemoteDataSource, private val localDataSource: LocalDataSource ) : UserRepository { override suspend fun getUser(userId: String): User { val localUser = localDataSource.getUser(userId) if (localUser != null) { return localUser } val remoteUser = remoteDataSource.fetchUser(userId) localDataSource.saveUser(remoteUser) return remoteUser } } suspend fun main() { val remote = RemoteDataSource() val local = LocalDataSource() val repo = UserRepositoryImpl(remote, local) val user1 = repo.getUser("123") println("First fetch: ${user1.name}") val user2 = repo.getUser("123") println("Second fetch: ${user2.name}") }
Use suspend functions for repository methods when working with coroutines.
Repository pattern helps you swap data sources easily without changing UI code.
Keep repository methods simple and focused on data retrieval or saving.
The Repository pattern separates data logic from UI and business logic.
It acts as a single source of truth for data in your app.
It makes your app easier to maintain, test, and extend.