Kotlin ViewModel and LiveData

ViewModel and LiveData are Android Architecture Components that separate your app's logic from its UI. ViewModel holds and manages UI-related data. LiveData wraps that data and automatically notifies the UI when it changes. Together, they prevent common bugs caused by screen rotations and lifecycle mismatches.

The Problem They Solve

Without ViewModel:
User types a search query → rotates screen
→ Activity recreated from scratch
→ Search result LOST

With ViewModel:
User types a search query → rotates screen
→ Activity recreated
→ ViewModel survives rotation
→ Search result STILL THERE

Diagram — ViewModel Survives Rotation

Screen Portrait              Rotation              Screen Landscape
──────────────               ───────               ──────────────────
Activity (created)     ──┐                   ┌──  Activity (recreated)
  ↕ (observes)           │                   │      ↕ (observes)
LiveData                 │                   │    LiveData
  ↕                      │                   │      ↕
ViewModel ───────────────┴───── SURVIVES ────┴──  ViewModel (same!)
  data = [search results]                          data = [still here]

Adding the Dependencies

// build.gradle.kts (app level)
dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
    implementation("androidx.activity:activity-ktx:1.8.2")
}

Creating a ViewModel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class CounterViewModel : ViewModel() {
    // MutableLiveData — can change value internally
    private val _count = MutableLiveData(0)

    // Expose as LiveData — read-only from outside
    val count: LiveData = _count

    fun increment() {
        _count.value = (_count.value ?: 0) + 1
    }

    fun decrement() {
        _count.value = (_count.value ?: 0) - 1
    }

    fun reset() {
        _count.value = 0
    }
}

MutableLiveData is private because only the ViewModel should change the data. The Activity only observes through the public LiveData.

Observing LiveData in an Activity

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    // Get the ViewModel — survives rotation
    private val viewModel: CounterViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Observe: runs whenever count changes
        viewModel.count.observe(this) { count ->
            binding.tvCount.text = count.toString()
        }

        binding.btnPlus.setOnClickListener  { viewModel.increment() }
        binding.btnMinus.setOnClickListener { viewModel.decrement() }
        binding.btnReset.setOnClickListener { viewModel.reset() }
    }
}

How It Works — Step by Step

Step 1: User taps "+" button
Step 2: Activity calls viewModel.increment()
Step 3: ViewModel updates _count.value = old + 1
Step 4: LiveData detects the change
Step 5: observe { } block runs automatically
Step 6: tvCount.text updates on screen

Activity never holds the count value itself.
ViewModel owns the data. Activity displays it.

ViewModel with a Repository (Real-World Pattern)

In real apps, the ViewModel does not fetch data directly. It asks a Repository, which decides whether to get data from the network or local database.

// Repository — single source of truth
class UserRepository {
    suspend fun getUser(id: Int): User {
        // In a real app: call an API here
        return User(id, "Priya Sharma", "priya@estudy247.com")
    }
}

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

// ViewModel uses the repository
class UserViewModel(private val repo: UserRepository) : ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> = _user

    fun loadUser(id: Int) {
        viewModelScope.launch {               // coroutine tied to ViewModel
            val result = repo.getUser(id)
            _user.postValue(result)           // update from background thread
        }
    }
}

Diagram — MVVM Architecture

┌───────────────────────────────────────────────────┐
│                     Activity / Fragment           │
│  (UI — displays data, sends user events to VM)    │
└──────────────────────┬────────────────────────────┘
                       │ observes LiveData
                       ▼
┌───────────────────────────────────────────────────┐
│                     ViewModel                     │
│  (holds UI state, calls repository, no View refs) │
└──────────────────────┬────────────────────────────┘
                       │ requests data
                       ▼
┌───────────────────────────────────────────────────┐
│                    Repository                     │
│  (decides: network or database?)                  │
└────────┬──────────────────────┬───────────────────┘
         │                      │
         ▼                      ▼
   Remote API              Local Database
   (Retrofit)              (Room)

LiveData vs StateFlow

Feature             | LiveData             | StateFlow
--------------------|----------------------|-------------------------
Lifecycle-aware     | Yes                  | Manual (with repeatOnLifecycle)
Null value          | Allowed              | Not allowed (needs initial)
Default value       | None required        | Required in constructor
Used with           | observe()            | collect() in coroutine
Modern preference   | Older projects       | New projects (recommended)

Leave a Comment

Your email address will not be published. Required fields are marked *