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.
Practice
Repository pattern in Android Kotlin development?Solution
Step 1: Understand the role of Repository
The Repository pattern is designed to manage data operations and provide a clean API to the rest of the app.Step 2: Identify separation of concerns
It separates data handling from UI and business logic, making the app easier to maintain and test.Final Answer:
To separate data access logic from UI and business logic -> Option AQuick Check:
Repository pattern = separation of data logic [OK]
- Confusing Repository with UI components
- Thinking Repository manages navigation
- Assuming Repository stores layout files
Solution
Step 1: Identify interface syntax in Kotlin
Interfaces in Kotlin are declared with theinterfacekeyword followed by function signatures without bodies.Step 2: Match the correct declaration
interface UserRepository { fun getUser(id: Int): User } correctly declares an interface with a function signature. class UserRepository { fun getUser(id: Int): User } is a class, not an interface. Options A, B, and C have incorrect syntax.Final Answer:
interface UserRepository { fun getUser(id: Int): User } -> Option AQuick Check:
Interface declaration usesinterfacekeyword [OK]
- Using class instead of interface for Repository
- Incorrect function syntax inside interface
- Trying to instantiate interface directly
class UserRepository {
fun getUserName(id: Int): String = if (id == 1) "Alice" else "Unknown"
}
fun main() {
val repo = UserRepository()
println(repo.getUserName(1))
println(repo.getUserName(2))
}Solution
Step 1: Analyze getUserName function logic
The function returns "Alice" if id is 1, otherwise "Unknown".Step 2: Trace the calls in main()
Calling getUserName(1) returns "Alice". Calling getUserName(2) returns "Unknown".Final Answer:
Alice\nUnknown -> Option BQuick Check:
id==1 returns Alice, else Unknown [OK]
- Mixing up return values for different ids
- Assuming both calls return 'Alice'
- Confusing id with returned string
class UserRepository {
fun getUser(id: Int): User {
return User(id, name)
}
}
Assuming name is not defined anywhere.Solution
Step 1: Check variables used in return statement
The function returnsUser(id, name), butnameis not declared or passed.Step 2: Identify compilation error
Using an undefined variable causes a compilation error in Kotlin.Final Answer:
Variable 'name' is undefined causing a compilation error -> Option CQuick Check:
Undefined variables cause compile errors [OK]
- Ignoring undefined variables
- Assuming missing imports cause error here
- Thinking function must be abstract
Solution
Step 1: Understand Repository responsibility
The Repository should act as a single source of truth and hide data sources from the UI.Step 2: Choose approach that centralizes data logic
Create a Repository class that decides whether to fetch from local or remote and exposes a single method to get user data centralizes data fetching logic inside the Repository, deciding between local and remote sources.Step 3: Avoid direct data calls in UI
Options A and C expose data sources to UI, breaking separation of concerns. Store all user data in the UI components for quick access mixes data with UI, which is bad practice.Final Answer:
Create a Repository class that decides whether to fetch from local or remote and exposes a single method to get user data -> Option DQuick Check:
Repository centralizes data access [OK]
- Calling data sources directly from UI
- Splitting data logic across UI and multiple classes
- Storing data inside UI components
Ready to prove it?
Pass the test and this pattern moves unknown
