Build MVVM Architecture with Kotlin Android Step by Step
Step-by-step guide to implementing MVVM architecture in a Kotlin Android app with ViewModel, StateFlow, Repository pattern, and Hilt dependency injection.
Introduction Architecture matters more than almost anything else in Android development. MVVM (Model-View-ViewModel) is Google's officially recommended architecture pattern for Android apps, and implementing it correctly with Kotlin transforms your codebase into something maintainable, testable, and built to last. This step-by-step guide walks you through building a complete MVVM app from scratch. What is MVVM Architecture in Kotlin/Android? MVVM (Model-View-ViewModel) is a software design pattern that separates an application into three distinct layers. The Model represents data and business logic — it includes your repositories, data sources (APIs, databases), and domain entities. The View is the UI layer — Activities, Fragments, and Composables that observe and display state. The ViewModel is the mediator — it holds and manages UI-related data, survives configuration changes (like screen rotations), and exposes observable state to the View. In modern Android development with Kotlin, MVVM is typically combined with Kotlin Coroutines for async operations, StateFlow or LiveData for observable state, and the Repository pattern for abstracting data sources. This combination creates a clean, unidirectional data flow that makes your app easier to test and maintain as it grows. Key Features / Why It Matters Implementing MVVM properly in your Android app delivers substantial engineering benefits: Separation of Concerns: Each layer has a clear, single responsibility — UI code doesn't mix with business logic or data fetching code. Configuration Change Survival: ViewModels survive Activity/Fragment recreation, preventing unnecessary re-fetching of data on screen rotation. Testability: Business logic in the ViewModel can be unit tested without needing an Android emulator or device. Lifecycle Awareness: Using StateFlow with lifecycle-aware collectors ensures UI updates only happen when the View is in the foreground, preventing memory leaks. Scalability: The Repository pattern makes it trivial to swap data sources (e.g., switching from REST API to GraphQL) without touching the ViewModel or UI. Team Collaboration: Clear layer separation means UI developers and backend developers can work independently with minimal merge conflicts. Step-by-Step: Building a Complete MVVM App We'll build a simple News Feed app that fetches articles from a REST API and displays them in a list. This will cover all core MVVM layers. Step 1: Define the Data Model // Model Layer — Data Entitydata class Article( val id: Int, val title: String, val summary: String, val imageUrl: String, val publishedAt: String) Step 2: Create the API Service (Retrofit) interface NewsApiService { @GET("articles") suspend fun getArticles( @Query("page") page: Int = 1, @Query("limit") limit: Int = 20 ): Response<List<Article>>} Step 3: Build the Repository interface NewsRepository { suspend fun getArticles(page: Int): Result<List<Article>>}class NewsRepositoryImpl( private val apiService: NewsApiService, private val articleDao: ArticleDao // Room for caching) : NewsRepository { override suspend fun getArticles(page: Int): Result<List<Article>> { return try { val response = apiService.getArticles(page = page) if (response.isSuccessful) { val articles = response.body() ?: emptyList() // Cache to Room DB for offline support articleDao.insertAll(articles.map { it.toEntity() }) Result.success(articles) } else { // Fallback to local cache val cached = articleDao.getAll().map { it.toDomain() } if (cached.isNotEmpty()) Result.success(cached) else Result.failure(Exception("Server error: ${response.code()}")) } } catch (e: Exception) { // Network error — load from cache val cached = articleDao.getAll().map { it.toDomain() } if (cached.isNotEmpty()) Result.success(cached) else Result.failure(e) } }} Step 4: Create the ViewModel sealed class NewsUiState { object Loading : NewsUiState() data class Success(val articles: List<Article>) : NewsUiState() data cl