MVVM (Model-View-ViewModel) is the recommended architecture for modern Android apps. Combined with Jetpack components — ViewModel, LiveData, and the Repository pattern — it keeps UI code thin, survives configuration changes, and makes apps easier to test.
View (Activity / Fragment / Compose)
↕ observes LiveData
ViewModel (UI logic, survives rotation)
↕ calls suspend functions
Repository (single source of truth)
↕
Data Sources (Room, Retrofit, SharedPreferences)
Layer
Responsibility
Should NOT Do
View
Display UI, handle user input
Network calls, database access
ViewModel
Expose UI state, handle events
Hold Activity/Fragment references
Repository
Coordinate data from all sources
Know about Views or UI widgets
Model
Data classes, Room entities, API models
Contain UI logic
1MVVM Architecture
MVVM separates the UI (View) from business logic (ViewModel) and data (Model). The View observes the ViewModel; the ViewModel never references the View directly — preventing memory leaks and simplifying testing.
MVVM vs traditional Activity-heavy code
Problem (Old Style)
MVVM Solution
Data lost on rotation
ViewModel survives configuration changes
Network code in Activity
Repository handles data fetching
Manual UI updates
LiveData pushes updates to observers
Hard to unit test
ViewModel tested without Android framework
Data flow in MVVM
User taps "Refresh"
→ View calls viewModel.loadTasks()
→ ViewModel launches coroutine
→ Repository fetches from API + caches in Room
→ ViewModel updates LiveData
→ View observes change → RecyclerView updates
LiveData is an observable data holder that is lifecycle-aware. It only notifies active observers (STARTED or RESUMED), preventing crashes and wasted updates when the Activity is in the background.
MutableLiveData vs LiveData
Kotlin — Expose read-only LiveDataclass TaskViewModel : ViewModel() {
// Private mutable — only ViewModel can write
private val _tasks = MutableLiveData<List<Task>>(emptyList())
// Public read-only — View can only observe
val tasks: LiveData<List<Task>> = _tasks
private val _loading = MutableLiveData(false)
val loading: LiveData<Boolean> = _loading
fun updateTasks(newTasks: List<Task>) {
_tasks.value = newTasks
}
}
Kotlin — map and switchMap// Transform LiveData value
val userName: LiveData<String> = Transformations.map(userLiveData) { user ->
user.name.uppercase()
}
// Switch to new LiveData based on selection
val tasksForUser: LiveData<List<Task>> = Transformations.switchMap(selectedUserId) { userId ->
repository.getTasksForUser(userId)
}
// Combine multiple sources
val uiState = MediatorLiveData<UiState>().apply {
addSource(tasks) { value = combineState() }
addSource(loading) { value = combineState() }
addSource(error) { value = combineState() }
}
A ViewModel stores and manages UI-related data. It survives configuration changes (like screen rotation) because it outlives the Activity/Fragment lifecycle. Use viewModelScope for coroutines that auto-cancel when the ViewModel is cleared.
Complete ViewModel example
Kotlin — TaskViewModel.ktclass TaskViewModel(
private val repository: TaskRepository = TaskRepository()
) : ViewModel() {
private val _tasks = MutableLiveData<List<Task>>()
val tasks: LiveData<List<Task>> = _tasks
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
private val _error = MutableLiveData<String?>()
val error: LiveData<String?> = _error
init {
loadTasks()
}
fun loadTasks() {
viewModelScope.launch {
_loading.value = true
_error.value = null
repository.getTasks()
.onSuccess { _tasks.value = it }
.onFailure { _error.value = it.message }
_loading.value = false
}
}
fun addTask(title: String) {
viewModelScope.launch {
repository.addTask(Task(title = title))
loadTasks()
}
}
fun deleteTask(taskId: Int) {
viewModelScope.launch {
repository.deleteTask(taskId)
loadTasks()
}
}
}
Obtain ViewModel in Fragment
Kotlin — TaskFragment.ktclass TaskFragment : Fragment() {
private val viewModel: TaskViewModel by viewModels()
private var _binding: FragmentTaskBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.tasks.observe(viewLifecycleOwner) { tasks ->
adapter.submitList(tasks)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
ViewModelFactory for dependencies
Kotlin — ViewModelFactory.ktclass TaskViewModelFactory(
private val repository: TaskRepository
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(TaskViewModel::class.java)) {
return TaskViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
// Usage:
val factory = TaskViewModelFactory(TaskRepository())
val viewModel: TaskViewModel by viewModels { factory }
UiState sealed class pattern
Kotlin — Single LiveData for UI statesealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
class TaskViewModel : ViewModel() {
private val _uiState = MutableLiveData<UiState<List<Task>>>(UiState.Loading)
val uiState: LiveData<UiState<List<Task>>> = _uiState
}
4Repository Pattern
The Repository is the single source of truth for app data. It abstracts whether data comes from a network API, local Room database, or cache — the ViewModel does not need to know.
Repository architecture
TaskViewModel
↓
TaskRepository (single source of truth)
├── TaskApiService (Retrofit — remote)
└── TaskDao (Room — local cache)
Complete Repository
Kotlin — TaskRepository.ktclass TaskRepository(
private val api: TaskApiService = RetrofitClient.api,
private val dao: TaskDao = AppDatabase.getInstance().taskDao()
) {
fun observeTasks(): Flow<List<Task>> = dao.observeAll()
suspend fun getTasks(): Result<List<Task>> {
return try {
val remoteTasks = api.getTasks()
dao.insertAll(remoteTasks)
Result.success(remoteTasks)
} catch (e: Exception) {
val cached = dao.getAll()
if (cached.isNotEmpty()) {
Result.success(cached)
} else {
Result.failure(e)
}
}
}
suspend fun addTask(task: Task): Result<Task> {
return try {
val created = api.createTask(task)
dao.insert(created)
Result.success(created)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun deleteTask(taskId: Int): Result<Unit> {
return try {
api.deleteTask(taskId)
dao.deleteById(taskId)
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
}
ViewModel using Repository with Flow
Kotlin — Reactive data from Roomclass TaskViewModel(
private val repository: TaskRepository = TaskRepository()
) : ViewModel() {
val tasks: LiveData<List<Task>> = repository.observeTasks()
.asLiveData()
fun refreshTasks() {
viewModelScope.launch {
repository.getTasks()
}
}
}
Benefits of Repository
Benefit
Description
Single source of truth
UI always reads from one place
Offline support
Return cached data when network fails
Testability
Mock Repository in ViewModel tests
Clean separation
ViewModel has no Retrofit or Room imports
5Lifecycle-aware Components
Lifecycle-aware components react to Activity and Fragment lifecycle events automatically. LiveData, ViewModel, and LifecycleObserver prevent memory leaks and avoid updating destroyed UI.
Activity lifecycle states
onCreate → onStart → onResume → [RUNNING]
↓
onPause → onStop → [STOPPED]
↓
onDestroy → [DESTROYED]
LiveData notifies observers only in STARTED or RESUMED state
Use viewLifecycleOwner in Fragments
Kotlin — Correct Fragment observation// WRONG — observes Activity lifecycle, may crash after onDestroyView
viewModel.tasks.observe(this) { ... }
// CORRECT — tied to Fragment view lifecycle
viewModel.tasks.observe(viewLifecycleOwner) { tasks ->
adapter.submitList(tasks)
}
LifecycleObserver for custom cleanup
Kotlin — LocationLifecycleObserver.ktclass LocationLifecycleObserver(
private val context: Context,
private val onLocationUpdate: (Location) -> Unit
) : DefaultLifecycleObserver {
private lateinit var fusedClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
override fun onStart(owner: LifecycleOwner) {
fusedClient = LocationServices.getFusedLocationProviderClient(context)
locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let(onLocationUpdate)
}
}
startLocationUpdates()
}
override fun onStop(owner: LifecycleOwner) {
fusedClient.removeLocationUpdates(locationCallback)
}
}
// Register in Activity:
lifecycle.addObserver(LocationLifecycleObserver(this) { location ->
viewModel.updateLocation(location)
})
repeatOnLifecycle for coroutines
Kotlin — Collect Flow safely in Fragmentoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showTasks(state.data)
is UiState.Error -> showError(state.message)
}
}
}
}
}
Lifecycle components summary
Component
Lifecycle Role
LiveData
Auto-stops updates when observer inactive
ViewModel
Survives rotation; cleared when Activity finishes
viewLifecycleOwner
Fragment view-scoped observation
repeatOnLifecycle
Safe Flow collection, cancels when STOPPED
DefaultLifecycleObserver
Custom start/stop logic tied to lifecycle
6Hands-On Exercises
1
Refactor an Activity-heavy app into MVVM. Move network and database code out of the Activity.
2
Expose task list, loading, and error states using LiveData. Observe them in the Activity.
3
Create a ViewModel with viewModelScope. Verify data survives screen rotation.
4
Implement a Repository that fetches from Retrofit and caches in Room. Return cached data on network failure.
5
In a Fragment, observe LiveData with viewLifecycleOwner instead of this.
6
Use repeatOnLifecycle to collect a Flow from the ViewModel and update UI with a sealed UiState.
7
Bonus: Write a unit test for your ViewModel using a fake Repository — no Android device required.
MVVM, LiveData & ViewModel MCQ Practice
10 Basic MCQs10 Advanced MCQs
10 Basic MVVM, LiveData & ViewModel MCQs
1
MVVM stands for?
AMobile Virtual View Module
BModel-View-ViewModel
CMain View Variable Method
DManifest ViewModel Version Map
Explanation: Separates UI (View) from logic (ViewModel) and data (Model).
2
ViewModel survives?
AApp uninstall
BProcess death always
CConfiguration changes like rotation
DGradle sync
Explanation: ViewModel scoped to Activity/Fragment retains data across rotation.
3
LiveData is?
ALayout XML
BSQL database
CHTTP client
DLifecycle-aware observable data holder
Explanation: Notifies active observers when data changes.
4
Observe LiveData with?
AviewLifecycleOwner in Fragment, owner in Activity