Popular Searches
Popular Course Categories
Popular Courses

Understanding the Model-View-ViewModel Architecture

Understanding the Model-View-ViewModel Architecture

Android Architecture and State Management

Understanding the Model-View-ViewModel (MVVM) Architecture in Android

Course: Android App Development

Topic: Understanding the Model-View-ViewModel (MVVM) Architecture

Course Link: Android App Development Course

Demo Link: Register for a Course Demo

Website: JustAcademy


1. Introduction to MVVM Architecture

Model-View-ViewModel (MVVM) is a software architectural pattern used to organize Android applications into separate components responsible for data management, user interface presentation, and UI-related business logic. It helps developers build applications that are easier to maintain, test, debug, and extend.

In Android development, MVVM is commonly implemented using Kotlin, Android Jetpack components, ViewModel, LiveData or StateFlow, Repository classes, Room Database, and Retrofit for network operations.

The primary goal of MVVM is to separate the user interface from application logic and data sources. This separation reduces dependencies between components and makes large applications easier to manage.

Why Is MVVM Important?

  • Separates UI code from business logic and data management.
  • Improves code readability and maintainability.
  • Supports unit testing of application logic.
  • Helps preserve UI-related state during configuration changes.
  • Works effectively with Kotlin Coroutines and Flow.
  • Encourages reusable and modular application components.
  • Makes applications easier to scale as new features are introduced.

2. What Is Software Architecture?

Software architecture defines the overall structure of an application, including its components, responsibilities, communication mechanisms, and dependencies. A well-designed architecture helps developers organize code and control how different parts of an application interact.

Without a clear architecture, Android applications may place network requests, database operations, validation rules, and UI updates inside a single Activity or Fragment. This creates tightly coupled code that becomes difficult to test and maintain.

MVVM addresses this problem by dividing responsibilities among the Model, View, and ViewModel layers.

3. Understanding the Three Components of MVVM

3.1 Model

The Model represents the application's data and data-related operations. It manages how information is obtained, stored, processed, and provided to the rest of the application.

In Android applications, the Model layer may contain data classes, repository implementations, API service interfaces, database entities, and data-access objects (DAOs).

Responsibilities of the Model:

  • Represent application data using Kotlin data classes.
  • Retrieve information from remote APIs.
  • Read and write local database records.
  • Coordinate data sources through repositories.
  • Handle data mapping and data-related processing.
  • Provide data to the ViewModel through defined interfaces.

Example of a Model data class:

data class User(
    val id: Int,
    val name: String,
    val email: String
)

This Kotlin data class represents a user with an ID, name, and email address. It can be used to represent information received from an API or retrieved from a database.

3.2 View

The View is the user interface through which users interact with the application. In Android, it may be implemented using an Activity, Fragment, XML layout, or Jetpack Compose screen.

The View displays information, collects user input, and forwards user actions to the ViewModel. It observes UI state and updates the interface when that state changes.

Responsibilities of the View:

  • Display data to the user.
  • Collect user input and interaction events.
  • Observe UI state exposed by the ViewModel.
  • Display loading indicators and error messages.
  • Forward user actions to the ViewModel.
  • Avoid directly containing extensive business logic or database operations.

For example, a login screen displays email and password fields. When the user taps the Login button, the View forwards the entered information to the ViewModel instead of directly communicating with the database.

3.3 ViewModel

The ViewModel acts as a bridge between the View and the data layer. It stores and prepares UI-related state, handles user actions, coordinates application logic, and obtains data through repositories or other appropriate data sources.

Android's Jetpack ViewModel class is designed to retain UI-related state across configuration changes, such as screen rotation, as long as the same ViewModel scope remains alive.

Responsibilities of the ViewModel:

  • Manage UI-related data and state.
  • Respond to user actions received from the View.
  • Coordinate repository operations.
  • Launch asynchronous tasks using Kotlin Coroutines when appropriate.
  • Expose observable state through StateFlow, SharedFlow, or LiveData.
  • Represent loading, success, and error conditions.
  • Keep the View independent of data-source implementation details.

Example: In a product listing application, the ViewModel requests product information from a repository, exposes the resulting UI state, and allows the View to display products or an error message.

4. MVVM Architecture Diagram

+------------------------------+
|             VIEW             |
| Activity / Fragment / Compose|
| Displays UI and accepts input|
+---------------+--------------+
                |
                | User actions
                v
+------------------------------+
|           VIEWMODEL          |
| UI state and presentation logic|
| Coordinates data operations  |
+---------------+--------------+
                |
                | Requests data
                v
+------------------------------+
|            MODEL             |
| Repository / Data Sources    |
| API / Room Database          |
+---------------+--------------+
                |
                | Results
                v
+------------------------------+
|         VIEWMODEL            |
| Updates observable UI state  |
+---------------+--------------+
                |
                | Observed state
                v
+------------------------------+
|             VIEW             |
| Renders updated interface    |
+------------------------------+

This diagram represents the main interaction pattern. In a well-structured application, the View sends actions to the ViewModel, while the ViewModel requests data from the Model layer and exposes state for the View to observe. The exact implementation may vary depending on the UI framework and architecture.

5. How MVVM Works in an Android Application

Step 1: The User Interacts with the View

The user performs an action, such as tapping a button, submitting a login form, refreshing a list, or searching for a product.

Step 2: The View Sends an Action to the ViewModel

The Activity, Fragment, or Compose screen calls an appropriate ViewModel function. The View does not need to know whether the requested data comes from a local database or a remote server.

Step 3: The ViewModel Coordinates the Operation

The ViewModel validates UI input when appropriate, updates loading state, and starts the required operation. For asynchronous work, it can use viewModelScope and Kotlin Coroutines.

Step 4: The Repository Retrieves Data

The repository coordinates data sources, such as a Retrofit API service and a Room database. It may return cached data, retrieve fresh information, or combine data sources according to application requirements.

Step 5: The ViewModel Updates UI State

When the operation finishes, the ViewModel publishes the new state, such as a list of users, a successful login result, or an error message.

Step 6: The View Observes and Renders the State

The View observes the exposed state and updates its interface. With StateFlow and Jetpack Compose, for example, the screen can recompose when the state changes.

6. MVVM vs. Traditional Android Architecture

Feature Traditional Approach MVVM Approach
UI logic Often mixed with Activity or Fragment code Separated into ViewModel and View responsibilities
Data access May be called directly from UI classes Typically coordinated through repositories
Testing May require complex UI setup ViewModel logic can often be tested independently
Configuration changes State may need to be manually restored ViewModel can retain UI state across configuration changes
Maintainability Can become difficult as the app grows Clearer separation of responsibilities
Reusability Logic may be tied to a specific UI class Shared logic can be reused through suitable components
Complexity May seem simpler for very small screens Requires thoughtful separation and state management

7. MVVM and Android Jetpack Components

Android Jetpack provides libraries that help implement MVVM and related architecture practices. These components serve different purposes and can be combined according to the needs of an application.

Component Purpose Example Usage
ViewModel Stores UI-related state and coordinates UI operations Managing a product screen's state
LiveData Lifecycle-aware observable data holder Observing state in a Fragment
StateFlow Observable state holder from Kotlin Coroutines Exposing current screen state
Room Provides an abstraction over SQLite Storing user profiles locally
Retrofit Helps define and execute HTTP API requests Fetching products from a server
Coroutines Supports asynchronous and suspending operations Performing network or database work
Navigation Supports navigation between app destinations Moving from login to dashboard
Hilt Provides dependency injection Supplying a repository to a ViewModel

8. Implementing MVVM in Kotlin

Consider a simple Android application that displays a user's profile. The following example demonstrates the relationship between a Model, Repository, ViewModel, and View. It uses an in-memory repository to keep the example focused on architecture; a production application could replace it with API or database access.

8.1 Create the Model

data class User(
    val id: Int,
    val name: String,
    val email: String
)

The User class defines the structure of the information displayed on the screen.

8.2 Create the Repository

class UserRepository {
    suspend fun getUser(): User {
        return User(
            id = 1,
            name = "Rahul Sharma",
            email = "[email protected]"
        )
    }
}

The repository provides the data to the ViewModel. In this demonstration, the function returns a sample user. In a real application, it could retrieve data from a network service or database.

8.3 Create the ViewModel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

data class UserUiState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null
)

class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {

    private val _uiState =
        MutableStateFlow(UserUiState())

    val uiState: StateFlow<UserUiState> =
        _uiState.asStateFlow()

    fun loadUser() {
        viewModelScope.launch {
            _uiState.value = UserUiState(
                isLoading = true
            )

            try {
                val user = repository.getUser()

                _uiState.value = UserUiState(
                    isLoading = false,
                    user = user
                )
            } catch (exception: Exception) {
                _uiState.value = UserUiState(
                    isLoading = false,
                    error = exception.message
                        ?: "Unable to load user"
                )
            }
        }
    }
}

Explanation:

  • UserUiState represents the screen's loading, success, and error information.
  • MutableStateFlow allows the ViewModel to update the current UI state.
  • asStateFlow() exposes a read-only state stream to consumers.
  • viewModelScope.launch starts a coroutine tied to the ViewModel's lifecycle.
  • The repository supplies user data without requiring the ViewModel to know the details of its source.
  • The View can render the screen based on the current state instead of managing data retrieval itself.

Important: This simplified example catches exceptions to demonstrate error state. Production applications should use structured error handling, avoid treating cancellation as an ordinary failure, and map technical errors into useful user-facing messages.

8.4 Display the State in a Jetpack Compose View

import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue

@Composable
fun UserScreen(
    viewModel: UserViewModel
) {
    val state by viewModel.uiState.collectAsState()

    LaunchedEffect(viewModel) {
        viewModel.loadUser()
    }

    Column {
        when {
            state.isLoading -> {
                CircularProgressIndicator()
            }

            state.error != null -> {
                Text("Error: ${state.error}")
                Button(
                    onClick = { viewModel.loadUser() }
                ) {
                    Text("Retry")
                }
            }

            state.user != null -> {
                Text("Name: ${state.user.name}")
                Text("Email: ${state.user.email}")
            }

            else -> {
                Text("No user information available")
            }
        }
    }
}

This Compose screen observes the ViewModel's StateFlow and renders the current UI state. The loading indicator appears while data is being retrieved, user details appear on success, and a retry option appears when an error is reported.

For a complete application, create the ViewModel through an appropriate ViewModel provider or dependency-injection setup. The sample omits application-specific setup, imports, and dependency configuration.

9. Understanding UI State Management in MVVM

UI state describes what the screen should display at a particular moment. A screen that loads data from a server commonly needs to represent at least three conditions: loading, success, and error.

State Description UI Example
Loading A request is in progress Progress indicator
Success Data was retrieved successfully Display the user's profile
Error An operation failed Show an error message and retry option
Empty The request succeeded but returned no items Show an empty-state message

A single UI state object can group related values and help avoid inconsistent combinations of independent Boolean flags. For more complex applications, Kotlin sealed classes can represent mutually exclusive states.

Example Using a Sealed UI State

sealed interface ProductUiState {
    data object Loading : ProductUiState

    data class Success(
        val products: List<String>
    ) : ProductUiState

    data class Error(
        val message: String
    ) : ProductUiState
}

This approach explicitly represents the possible states of a product screen. It also makes exhaustive handling with Kotlin's when expression easier.

10. MVVM with LiveData and StateFlow

LiveData and StateFlow can both expose observable state, but they belong to different APIs and have different lifecycle behaviors.

Feature LiveData StateFlow
Library Android Lifecycle Kotlin Coroutines
Initial value Can be unset Requires an initial value
State access Uses a value property Uses a value property
Observation Lifecycle-aware observers Collected as a Flow
Compose integration Can be observed using suitable adapters Can be collected with lifecycle-aware Flow APIs
Best suited for Existing Android codebases using LiveData Coroutine-based state management

For new coroutine-oriented Android applications, StateFlow is a common choice. In Compose applications, prefer lifecycle-aware collection such as collectAsStateWithLifecycle() when available through the appropriate AndroidX lifecycle Compose dependency.

11. The Role of the Repository Pattern in MVVM

The repository pattern separates the ViewModel from the details of where data comes from. The repository can coordinate a remote API, local database, cache, or other data sources.

For example, a shopping application might retrieve product information from a Retrofit API and store selected product data in Room. The ViewModel requests products from the repository without directly managing HTTP calls or SQL queries.

Benefits of a Repository

  • Centralizes data-access logic.
  • Reduces dependencies between ViewModels and data sources.
  • Makes it easier to replace a data source.
  • Supports testing with fake repository implementations.
  • Allows caching and synchronization strategies to be implemented in one place.

A repository is not automatically required for every tiny application. Introduce it when it creates a useful separation of responsibilities, and avoid adding unnecessary abstraction layers.

12. Dependency Injection in MVVM

Dependency injection provides a class with the objects it needs instead of requiring it to construct every dependency internally. It helps make components easier to configure, replace, and test.

Android developers often use Hilt for dependency injection. A ViewModel can receive a repository through its constructor, while Hilt provides the repository and its dependencies.

@HiltViewModel
class ProfileViewModel @Inject constructor(
    private val repository: ProfileRepository
) : ViewModel() {
    // UI state and user actions
}

This snippet illustrates constructor injection. A complete implementation also requires the appropriate Hilt setup, imports, repository binding or provider, and application configuration.

13. MVVM with Retrofit and Room

Many Android applications combine Retrofit and Room to support remote and local data access. MVVM helps organize the interaction between these technologies and the UI.

  • Retrofit: Defines and executes HTTP API requests.
  • Room: Stores structured data locally using SQLite.
  • Repository: Coordinates remote and local data operations.
  • ViewModel: Exposes screen state and responds to UI actions.
  • View: Displays the data and collects user interaction.

In an offline-first design, the repository may expose a Flow from Room so the UI can observe local data while network synchronization updates the database. This can make the interface more responsive and reduce dependence on continuous connectivity.

14. Advantages of MVVM Architecture

14.1 Separation of Concerns

Each component has a clearer responsibility. The View focuses on presentation, the ViewModel manages UI-related state, and the data layer handles data operations.

14.2 Improved Testability

ViewModel logic can often be tested without launching a full Android screen. Repositories can be replaced with fake implementations to simulate success, empty results, and failures.

14.3 Easier Maintenance

When a UI layout changes, data-access code does not necessarily need to change. Similarly, changing an API implementation can often be isolated to the data layer.

14.4 Better State Handling

Observable state mechanisms help keep the UI synchronized with application data and make loading and error conditions explicit.

14.5 Support for Team Development

Developers can work on UI screens, ViewModels, and data repositories with fewer overlapping responsibilities when component interfaces are clearly defined.

15. Limitations and Challenges of MVVM

  • Additional classes: Small applications may require more files than a simple single-screen implementation.
  • State complexity: Poorly designed UI state can lead to confusing updates and difficult debugging.
  • Learning curve: Developers must understand observable state, lifecycle behavior, and coroutine cancellation.
  • Unnecessary abstraction: Creating a repository, use case, and several interfaces for every simple operation can add complexity without meaningful benefits.
  • Lifecycle mistakes: Incorrect collection or scope usage can cause duplicate requests or unnecessary work.
  • Testing setup: Coroutine-based code may require test dispatchers and controlled scheduling to test reliably.

16. Best Practices for Implementing MVVM

  • Keep Activities, Fragments, and Compose screens focused on UI responsibilities.
  • Expose immutable UI state from the ViewModel instead of exposing mutable state directly.
  • Use Kotlin Coroutines for asynchronous operations and appropriate dispatchers for blocking work.
  • Use repositories when they provide a useful boundary around data access.
  • Represent loading, success, empty, and error conditions clearly.
  • Handle cancellation correctly and do not convert coroutine cancellation into an ordinary application error.
  • Avoid storing Activity, Fragment, View, or other short-lived UI references in a ViewModel.
  • Use SavedStateHandle when appropriate for small pieces of state that must survive process recreation.
  • Use dependency injection or other explicit construction strategies for manageable dependencies.
  • Write unit tests for state transitions and data-related behavior.
  • Keep sensitive information, such as passwords and authentication tokens, out of logs and unnecessary UI state.
  • Choose architecture components based on the real requirements of the application.

17. Common MVVM Mistakes and Troubleshooting

Problem Possible Cause Solution
UI does not update State is not being emitted or observed correctly Check StateFlow updates and state collection
Repeated API requests Loading is triggered repeatedly during UI recomposition or lifecycle events Review effect keys, initialization, and event handling
UI freezes Blocking work runs on the main thread Move blocking operations to an appropriate background dispatcher
ViewModel depends on an Activity UI references are passed into long-lived state logic Use lifecycle-independent dependencies and state-based communication
Data is lost after rotation State is stored only in the Activity or Fragment Move appropriate UI state into a ViewModel
Errors are not displayed Failures are swallowed or not represented in UI state Model error state and render it in the View
Tests are unreliable Uncontrolled dispatchers or real dependencies are used Inject dependencies and use coroutine test utilities

18. Practical Project: User Profile Application

Build a simple Android application that retrieves and displays a user profile using MVVM architecture.

Project Requirements

  • Create a user data model containing ID, name, email, and profile image URL.
  • Create a repository to provide user information.
  • Implement a ViewModel that exposes loading, success, and error states.
  • Create a UI screen that displays profile information.
  • Show a loading indicator while data is retrieved.
  • Display an understandable error message when the request fails.
  • Add a retry action to reload the profile.
  • Optionally integrate Retrofit for remote data and Room for local caching.
  • Write unit tests for successful retrieval, empty results, and failure scenarios.

Suggested Project Structure

com.example.userprofile
|
+-- data
|   +-- model
|   |   +-- User.kt
|   +-- remote
|   |   +-- UserApiService.kt
|   +-- local
|   |   +-- UserDao.kt
|   +-- repository
|       +-- UserRepository.kt
|
+-- ui
|   +-- profile
|       +-- UserViewModel.kt
|       +-- UserUiState.kt
|       +-- UserScreen.kt
|
+-- di
    +-- AppModule.kt

This is an illustrative structure. A small application may not need every package. Organize files around the application's actual features and complexity.

19. Exercises for Practice

  1. Explain the responsibilities of Model, View, and ViewModel in your own words.
  2. Create a Kotlin data class for a product with an ID, name, price, and category.
  3. Implement a repository that returns a list of sample products.
  4. Create a ViewModel that exposes products using StateFlow.
  5. Build a Compose screen that displays the products.
  6. Add loading and error states to the screen.
  7. Modify the repository to simulate a network failure and implement retry behavior.
  8. Replace sample data with a Retrofit API call.
  9. Add Room caching and explain how the repository coordinates local and remote data.
  10. Write unit tests for the ViewModel using a fake repository.

20. Interview Questions and Answers

Q1. What is MVVM architecture?

Answer: MVVM is an architectural pattern that separates an application's UI into a View, UI-related logic and state into a ViewModel, and data operations into a Model or data layer.

Q2. What is the role of the ViewModel in Android?

Answer: The ViewModel manages UI-related state, responds to UI actions, coordinates operations, and exposes state for the View to observe. It can retain state across configuration changes while its scope remains alive.

Q3. What is the difference between Model and ViewModel?

Answer: The Model or data layer represents and manages application data and data sources. The ViewModel coordinates UI-related behavior and exposes the state needed by the screen.

Q4. Why should an Activity not contain all business logic?

Answer: Putting all logic in an Activity creates tight coupling, makes testing harder, and increases maintenance costs. Separating responsibilities makes the application easier to extend.

Q5. How does MVVM improve testability?

Answer: ViewModel behavior can be tested independently of the full UI, and repositories can be replaced with fake implementations to control test data and simulate failures.

Q6. What is the difference between LiveData and StateFlow?

Answer: LiveData is an Android lifecycle-aware observable data holder. StateFlow is a coroutine-based state holder that always has a current value and can be collected as a Flow.

Q7. What is the purpose of the Repository pattern?

Answer: It separates data-access details from the ViewModel and can coordinate sources such as a remote API, local database, and cache.

Q8. Does a ViewModel survive process death?

Answer: No. A ViewModel generally survives configuration changes while its scope remains alive, but it is not retained after process death. Use mechanisms such as SavedStateHandle or persistent storage for state that must be restored.

Q9. How are coroutines used in MVVM?

Answer: Coroutines support asynchronous operations such as fetching API data or accessing a repository. The ViewModel can launch work in viewModelScope, while blocking operations must use appropriate dispatchers.

Q10. Is MVVM suitable for every Android application?

Answer: MVVM is useful for many Android applications, especially those with asynchronous data and evolving UI requirements. Very small applications may need less structure, so architecture should match the project's complexity.

21. Quick Revision Checklist

  • Model: represents data and data-access operations.
  • View: displays information and forwards user actions.
  • ViewModel: coordinates UI behavior and exposes UI state.
  • Repository: abstracts and coordinates data sources.
  • StateFlow or LiveData: exposes observable state.
  • Coroutines: support asynchronous work.
  • Room: supports local persistence.
  • Retrofit: supports HTTP API integration.
  • Hilt: supports dependency injection.
  • Unit testing: verifies state transitions and business-related behavior.

22. Conclusion

Model-View-ViewModel is a widely used architectural pattern for building maintainable Android applications. By separating UI presentation, UI-related state, and data access, MVVM helps developers write code that is easier to understand, test, and extend.

To apply MVVM effectively, understand the responsibilities of each component, use observable UI state, manage asynchronous work carefully, and introduce repositories and dependency injection where they provide real value. Practicing with Kotlin, Android Jetpack, StateFlow, Coroutines, Retrofit, and Room will help you develop practical architecture skills for real-world Android projects.

23. Course Resources

Explore the Android App Development course to continue learning about Android architecture, Kotlin, application development, and modern Android tools.

Explore the course: Android App Development Training

Register for a demo: Book a Course Demo with JustAcademy

Visit the website: JustAcademy Official Website

Previous Next
whatsapp