Skip to main content
Android Development with Kotlin
CHAPTER 22 Beginner

Clean Architecture and Repositories

Updated: May 16, 2026
30 min read

# CHAPTER 22

Clean Architecture and Repositories

1. Introduction

We have reached a critical architectural inflection point. In our current application, the ViewModel is responsible for observing LiveData, launching Coroutines, executing Retrofit API calls, and interacting with the Room Database. This violates the Single Responsibility Principle. If the ViewModel decides *where* the data comes from (Network vs. Database), it becomes massively bloated and impossible to test. In professional Android development, we introduce an intermediary layer: the Repository. In this chapter, we will master Clean Architecture and the Repository Pattern. We will abstract data operations, enforce a Single Source of Truth, and orchestrate seamless synchronization between the remote cloud and local storage.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Understand the principles of the Repository Pattern in MVVM.
  • Decouple data fetching logic from the UI Controller and ViewModel.
  • Implement a Single Source of Truth caching strategy.
  • Construct a Repository class that abstracts Room DAOs and Retrofit APIs.
  • Pass Repository dependencies cleanly into the ViewModel.

3. The Problem: Bloated ViewModels

Imagine an app that shows a list of news articles.
  • The user opens the app.
  • The ViewModel checks if the internet is connected.
  • If yes, the ViewModel calls Retrofit to download articles.
  • The ViewModel then saves the articles into Room.
  • If no, the ViewModel queries Room to get cached articles.
This is chaotic. The ViewModel's *only* job should be formatting data for the Activity. It shouldn't care *how* the data was retrieved.

4. The Solution: The Repository

The Repository is a plain Kotlin class. It sits between the ViewModel and your Data Sources (Retrofit & Room). The ViewModel asks the Repository: "Get me the articles." The ViewModel does not know, nor does it care, if the Repository gets them from the internet or the database. The Repository acts as the ultimate traffic controller.

5. Single Source of Truth Strategy

The industry standard caching strategy works exactly like this:
  1. 1. The ViewModel observes the Room Database via the Repository. (The database is the "Single Source of Truth").
  1. 2. Simultaneously, the Repository asks Retrofit for fresh data from the server.
  1. 3. Retrofit downloads the fresh data.
  1. 4. The Repository saves the fresh data directly into the Room Database.
  1. 5. Because Room is being observed (returning LiveData), the database updates instantly, which automatically pushes the fresh data up to the ViewModel and finally to the screen.

6. Building the Repository

Let's construct the Repository. We pass our UserDao (Room) and our ApiService (Retrofit) into its constructor.
kotlin
1234567891011121314151617181920212223242526272829303132
package com.example.repositorydemo

import androidx.lifecycle.LiveData

// The Repository abstracts the data sources!
class UserRepository(private val userDao: UserDao, private val apiService: ApiService) {

    // 1. The ViewModel observes THIS. It always comes straight from the local DB!
    val allUsers: LiveData<List<UserEntity>> = userDao.getAllUsers()

    // 2. The ViewModel calls this to trigger a network refresh
    suspend fun refreshUsersFromNetwork() {
        try {
            // Fetch fresh data from the internet
            val networkUsers = apiService.getUsers()
            
            // Convert network models to database entities (Mapping)
            val userEntities = networkUsers.map { user ->
                UserEntity(id = user.id, name = user.name, email = user.email)
            }
            
            // Delete old data and insert new data into the DB!
            // The LiveData will automatically notify the UI that the DB changed!
            userDao.deleteAllUsers()
            userDao.insertAll(userEntities)
            
        } catch (e: Exception) {
            // Handle no internet connection (the UI will just keep showing the old DB data!)
            println("Network error, relying on cache.")
        }
    }
}

7. Refactoring the ViewModel

Now look at how incredibly clean our ViewModel becomes! It has zero Retrofit code and zero Room connection code. It just delegates to the Repository.
kotlin
1234567891011121314151617181920212223242526272829303132
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class UserViewModel(application: Application) : AndroidViewModel(application) {

    // The ViewModel only needs to talk to the Repository!
    private val repository: UserRepository
    val users: LiveData<List<UserEntity>>

    init {
        // Initialize DAO and Network
        val dao = UserDatabase.getDatabase(application).userDao()
        val api = RetrofitClient.apiService
        
        // Pass them to the Repository
        repository = UserRepository(dao, api)
        
        // Connect the LiveData
        users = repository.allUsers
    }

    // Triggered by a "Pull to Refresh" or Activity launch
    fun refreshData() {
        viewModelScope.launch(Dispatchers.IO) {
            repository.refreshUsersFromNetwork()
        }
    }
}

8. The Complete MVVM Flow

You have now constructed a professional Android architecture:
  1. 1. Activity/Fragment: Captures user clicks and renders the UI. Observes the ViewModel.
  1. 2. ViewModel: Survives rotation. Holds LiveData. Delegates actions to the Repository.
  1. 3. Repository: The data traffic controller. Decides when to fetch from API vs. DB.
  1. 4. Room DAO (Local): The SQLite database cache (Single Source of Truth).
  1. 5. Retrofit (Remote): The internet API.

9. Common Mistakes

  • Bypassing the Repository: Often, developers will write a quick database query directly inside an Activity because it's "faster" than creating a function in the Repository and ViewModel. This instantly breaks the architecture. All data operations MUST strictly flow: Activity -> ViewModel -> Repository -> Data Source.
  • Memory Leaks via Context: Passing an Activity Context into a Repository. Repositories should ideally be context-agnostic. If Room requires a context, pass the ApplicationContext via the AndroidViewModel, never the Activity context.

10. Best Practices

  • Dependency Injection (Hilt/Dagger): In our example, the ViewModel manually created the Repository. In enterprise apps, the Repository is "Injected" into the ViewModel constructor automatically using a framework like Hilt. This makes mocking and Unit Testing significantly easier.
  • Data Mapping: An API might return a JSON object with 50 fields, but your Database only needs 5 fields. The Repository is the perfect place to execute Data Mapping, converting a complex NetworkUserDto into a lean LocalUserEntity before saving it to Room.

11. Exercises

  1. 1. Draw a flowchart on a piece of paper illustrating the MVVM Repository pattern. Label the arrows denoting where data flows, and where LiveData is observed.
  1. 2. In your own project, create a NoteRepository that takes a NoteDao. Move the insertNote logic out of the ViewModel and into the Repository.

12. Coding Challenges

Challenge: Implement a caching timeout mechanism in the Repository. Modify the Repository so that when refreshUsersFromNetwork() is called, it checks a local variable lastFetchTime. If the last fetch was less than 5 minutes ago, it simply returns and does *not* make a Retrofit call, saving user bandwidth!

13. MCQ Quiz with Answers

Question 1

In the context of Android Clean Architecture, what is the primary structural responsibility of the Repository class?

Question 2

When implementing the "Single Source of Truth" strategy utilizing Room and Retrofit, how does the UI receive newly downloaded network data?

14. Interview Questions

  • Q: Explain the Single Responsibility Principle as it relates to the decoupling of the ViewModel and the Repository. Why is it an anti-pattern for a ViewModel to instantiate a Retrofit client directly?
  • Q: Detail the "Single Source of Truth" caching paradigm. How does returning LiveData directly from a Room DAO facilitate seamless offline-first synchronization during a network fetch?
  • Q: Describe the concept of Data Mapping (DTOs to Entities). Why is it architecturally beneficial for the Repository to perform this mapping rather than the ViewModel or the UI controller?

15. FAQs

Q: Do I always need a Repository? What if my app just has a simple Room database and no internet features? A: For extremely simple, single-source apps, skipping the Repository is acceptable. However, as an app grows, you *will* eventually add networking, analytics, or SharedPreferences. Introducing a Repository from day one establishes a scalable, future-proof foundation with minimal initial overhead.

16. Summary

In Chapter 22, we elevated our application architecture to enterprise standards. We diagnosed the vulnerability of bloated ViewModels and implemented the Repository Pattern to enforce strict Separation of Concerns. We engineered a robust, Single Source of Truth data pipeline, where the Repository intelligently orchestrates background network fetches via Retrofit, synchronizes the payload into the local Room Database, and leverages reactive LiveData streams to automatically update the UI layer. Our application is now decoupled, testable, and highly scalable.

17. Next Chapter Recommendation

Our app architecture is complete, but it lacks user identity. We cannot build social networks, save user profiles to the cloud, or secure private data without Authentication. Proceed to Chapter 23: Firebase Authentication to integrate Google's powerful Backend-as-a-Service and build a robust login system.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·