0
0
Android Kotlinmobile~10 mins

Repository pattern in depth in Android Kotlin

Choose your learning style9 modes available
Introduction

The repository pattern helps organize data access in your app. It acts like a middleman between your app and data sources, making your code cleaner and easier to manage.

When your app needs to get data from multiple places like a database and a web service.
When you want to keep your UI code simple and separate from data handling.
When you want to easily change where your data comes from without changing the whole app.
When you want to test your app by replacing real data sources with fake ones.
When you want to cache data locally to improve app speed and offline use.
Syntax
Android Kotlin
interface UserRepository {
    suspend fun getUser(userId: String): User
    suspend fun saveUser(user: User)
}

class UserRepositoryImpl(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) : UserRepository {
    override suspend fun getUser(userId: String): User {
        val localUser = localDataSource.getUser(userId)
        return localUser ?: remoteDataSource.getUser(userId).also {
            localDataSource.saveUser(it)
        }
    }

    override suspend fun saveUser(user: User) {
        localDataSource.saveUser(user)
        remoteDataSource.saveUser(user)
    }
}

The UserRepository interface defines what data actions are possible.

The UserRepositoryImpl class handles where data comes from and where it is saved.

Examples
A simple repository interface for products.
Android Kotlin
interface ProductRepository {
    suspend fun getProduct(productId: String): Product
}
Implementation tries local data first, then remote if needed.
Android Kotlin
class ProductRepositoryImpl(
    private val localDataSource: ProductLocalDataSource,
    private val remoteDataSource: ProductRemoteDataSource
) : ProductRepository {
    override suspend fun getProduct(productId: String): Product {
        return localDataSource.getProduct(productId) ?: remoteDataSource.getProduct(productId)
    }
}
A fake repository for testing without real data sources.
Android Kotlin
class FakeUserRepository : UserRepository {
    private val fakeData = mutableMapOf<String, User>()
    override suspend fun getUser(userId: String): User = fakeData[userId] ?: User(userId, "Fake User")
    override suspend fun saveUser(user: User) { fakeData[user.id] = user }
}
Sample App

This example shows a repository that first checks local storage for a user. If not found, it fetches from remote and saves locally. It also saves users to both places.

Android Kotlin
data class User(val id: String, val name: String)

interface UserLocalDataSource {
    suspend fun getUser(userId: String): User?
    suspend fun saveUser(user: User)
}

interface UserRemoteDataSource {
    suspend fun getUser(userId: String): User
    suspend fun saveUser(user: User)
}

interface UserRepository {
    suspend fun getUser(userId: String): User
    suspend fun saveUser(user: User)
}

class UserRepositoryImpl(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) : UserRepository {
    override suspend fun getUser(userId: String): User {
        val localUser = localDataSource.getUser(userId)
        return localUser ?: remoteDataSource.getUser(userId).also {
            localDataSource.saveUser(it)
        }
    }

    override suspend fun saveUser(user: User) {
        localDataSource.saveUser(user)
        remoteDataSource.saveUser(user)
    }
}

// Fake implementations for demo
class FakeLocalDataSource : UserLocalDataSource {
    private val storage = mutableMapOf<String, User>()
    override suspend fun getUser(userId: String): User? = storage[userId]
    override suspend fun saveUser(user: User) { storage[user.id] = user }
}

class FakeRemoteDataSource : UserRemoteDataSource {
    private val storage = mutableMapOf<String, User>("1" to User("1", "Alice"))
    override suspend fun getUser(userId: String): User = storage[userId] ?: User(userId, "Unknown")
    override suspend fun saveUser(user: User) { storage[user.id] = user }
}

suspend fun main() {
    val local = FakeLocalDataSource()
    val remote = FakeRemoteDataSource()
    val repo = UserRepositoryImpl(local, remote)

    val user = repo.getUser("1")
    println("User name: ${user.name}")

    repo.saveUser(User("2", "Bob"))
    val user2 = repo.getUser("2")
    println("User2 name: ${user2.name}")
}
OutputSuccess
Important Notes

Repository pattern helps keep your app's data logic clean and testable.

Use interfaces so you can swap real and fake data sources easily.

Remember to handle errors and loading states when fetching data.

Summary

The repository pattern separates data access from UI code.

It can combine multiple data sources like local and remote.

It makes testing easier by allowing fake data sources.