MVVM, LiveData & ViewModel

Build maintainable Android apps with separation of concerns and lifecycle-safe UI updates

MVVM LiveData ViewModel Repository

Table of Contents

Architecture Overview

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)
LayerResponsibilityShould NOT Do
ViewDisplay UI, handle user inputNetwork calls, database access
ViewModelExpose UI state, handle eventsHold Activity/Fragment references
RepositoryCoordinate data from all sourcesKnow about Views or UI widgets
ModelData classes, Room entities, API modelsContain 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 rotationViewModel survives configuration changes
Network code in ActivityRepository handles data fetching
Manual UI updatesLiveData pushes updates to observers
Hard to unit testViewModel 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

Gradle dependencies

Kotlin — app/build.gradle.ktsdependencies { implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.activity:activity-ktx:1.8.2") implementation("androidx.fragment:fragment-ktx:1.6.2") }

2LiveData

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 } }

Observe in Activity

Kotlin — MainActivity.ktclass MainActivity : AppCompatActivity() { private val viewModel: TaskViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) viewModel.tasks.observe(this) { taskList -> adapter.submitList(taskList) } viewModel.loading.observe(this) { isLoading -> binding.progressBar.isVisible = isLoading } binding.btnRefresh.setOnClickListener { viewModel.loadTasks() } } }

Transformations and MediatorLiveData

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() } }

LiveData with coroutines

Kotlin — liveData builderval searchResults: LiveData<List<Product>> = liveData { emit(emptyList()) // initial value val query = searchQuery.value ?: return@liveData emit(repository.searchProducts(query)) }

3ViewModel

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

BenefitDescription
Single source of truthUI always reads from one place
Offline supportReturn cached data when network fails
TestabilityMock Repository in ViewModel tests
Clean separationViewModel 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

ComponentLifecycle Role
LiveDataAuto-stops updates when observer inactive
ViewModelSurvives rotation; cleared when Activity finishes
viewLifecycleOwnerFragment view-scoped observation
repeatOnLifecycleSafe Flow collection, cancels when STOPPED
DefaultLifecycleObserverCustom 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 MCQs 10 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
BApplication context only
CService
DManifest
Explanation: Observation respects STARTED+ lifecycle state.
5

Repository pattern?

AUI layout pattern
BSingle source of truth for data access
CGradle plugin
DNotification channel
Explanation: Abstracts network, database, cache from ViewModel.
6

ViewModel should not hold?

ARepository reference
BUser data state
CActivity/Fragment/View references
DCoroutine scope
Explanation: Holding View causes memory leaks on rotation.
7

MutableLiveData exposed as?

ASharedPreferences
BPublic mutable always
CStatic field
DLiveData via getter (encapsulation)
Explanation: Private _data MutableLiveData; public val data: LiveData = _data.
8

viewModelScope?

ACoroutineScope cancelled when ViewModel cleared
BActivity lifecycleScope same
CGlobal scope
DService scope
Explanation: Launch coroutines in ViewModel that auto-cancel on destroy.
9

Model in MVVM?

AOnly XML layout
BData layer — API, database, domain objects
COnly Activity
DManifest
Explanation: Model represents business data and rules.
10

Data binding + ViewModel?

ARequires Java only
BImpossible
CXML can bind to ViewModel LiveData fields
DBanned
Explanation: layout variable viewModel with observable fields.

10 Advanced MVVM, LiveData & ViewModel MCQs

11

SavedStateHandle in ViewModel?

AReplaces Room
BRestores state after process death
CGPS only
DLayout inflation
Explanation: Persists small state via Bundle across process kill.
12

SingleLiveEvent pattern?

AHTTP cache
BDatabase table
COne-time events (navigation, toast) without re-fire on rotate
DSensor fusion
Explanation: Wrapper ensuring event consumed once per emission.
13

StateFlow vs LiveData?

AStateFlow Android-only legacy
BIdentical
CLiveData only in Compose
DStateFlow Kotlin coroutines; LiveData lifecycle-aware Java-friendly
Explanation: Modern Kotlin apps often use StateFlow in ViewModel.
14

Hilt ViewModel injection?

A@HiltViewModel + @Inject constructor
BManual new always
CManifest meta-data
DRoom DAO
Explanation: by viewModels() with Hilt provides dependencies.
15

Transformations.map on LiveData?

ASQL query
BDerives new LiveData from source
CNetwork call sync
DDeletes source
Explanation: map/switchMap for reactive transformations.
16

UI state sealed class?

ASQL only
BDeprecated
CLoading/Success/Error unified state model
DManifest merge
Explanation: Single uiState LiveData/StateFlow drives UI consistently.
17

ViewModelProvider.Factory?

AParses JSON only
BInflates layout
CStarts Service
DCreates ViewModel with constructor params
Explanation: Needed when ViewModel has non-default constructor args.
18

MediatorsLiveData?

ACombines multiple LiveData sources
BBluetooth
CCamera
DDex
Explanation: addSource merges streams into one observable.
19

Process death ViewModel?

ASurvives always
BViewModel destroyed; use SavedStateHandle or restore from repository
CSame as rotation
DAutomatic Room sync
Explanation: Rotation survives; process death does not unless state saved.
20

Testing ViewModel.

ACannot unit test
BEspresso only
CInstantTaskExecutorRule + fake repository
DADB required
Explanation: JUnit test ViewModel logic without Android framework.
Click an option to select, then check answers.

MVVM, LiveData & ViewModel Interview Q&A

15 topic-focused questions for interviews and revision.

1Explain MVVM layers.easy
Answer: View: Activity/Fragment UI. ViewModel: UI logic and state. Model/Repository: data sources.
2Why ViewModel on rotation?easy
Answer: Activity recreated but ViewModel retained — avoids reloading data and losing form state.
3LiveData observe example.easy
Answer: viewModel.users.observe(viewLifecycleOwner) { list -> adapter.submitList(list) }
4Repository pattern benefits.medium
Answer: Single API for data, swap sources, testable, cache logic centralized.
5viewModelScope usage.medium
Answer: viewModelScope.launch { repository.fetchData(); _uiState.value = Success(data) }
6Expose LiveData safely.easy
Answer: Private MutableLiveData, public LiveData read-only to prevent external mutation.
7One-time event navigation.hard
Answer: Use SharedFlow, Channel, or SingleLiveEvent to avoid re-navigation on rotate.
8ViewModel vs Activity storing data.medium
Answer: ViewModel survives config change and separates concerns; Activity handles UI lifecycle only.
9SavedStateHandle example.medium
Answer: SavedStateHandle.get("query") ?: ""; set on search submit.
10StateFlow in Compose.medium
Answer: Collect with collectAsStateWithLifecycle(); drives recomposition from ViewModel.
11Hilt ViewModel setup.medium
Answer: @HiltViewModel class MyVm @Inject constructor(repo: Repo) : ViewModel()
12UI state pattern Loading/Success/Error.medium
Answer: Sealed class UiState; ViewModel updates; UI when(success) show data when(error) show message.
13Test ViewModel with coroutines.hard
Answer: runTest, StandardTestDispatcher, fake repository returning test data.
14Anti-pattern: API call in Activity.easy
Answer: Move to ViewModel + repository; Activity only observes state.
15LiveData vs observing Flow.medium
Answer: LiveData lifecycle-aware out of box; Flow needs repeatOnLifecycle or collectAsStateWithLifecycle.