Kotlin ViewModel Basics

A ViewModel stores and manages UI-related data in a lifecycle-aware way. When a user rotates their phone, Android destroys and recreates the Activity — but the ViewModel survives. This prevents losing data during screen rotations and separates business logic from UI code.

The Problem Without ViewModel


Without ViewModel:
  User fills a form → phone rotates → Activity recreated
  → All data gone! Form is blank again.

With ViewModel:
  User fills a form → phone rotates → Activity recreated
  → ViewModel still has the data → form stays filled.

ViewModel lifecycle:
  Activity created  → ViewModel created
  Activity rotated  → Activity destroyed and recreated
                    → ViewModel SURVIVES (same instance)
  User presses Back → Activity finished
                    → ViewModel destroyed

Adding ViewModel Dependency

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

Creating a ViewModel

import androidx.lifecycle.ViewModel

class CounterViewModel : ViewModel() {

    var count: Int = 0
        private set

    fun increment() {
        count++
    }

    fun decrement() {
        if (count > 0) count--
    }

    fun reset() {
        count = 0
    }
}

Using the ViewModel in an Activity

import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView

class CounterActivity : AppCompatActivity() {

    private val viewModel: CounterViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_counter)

        val countText: TextView = findViewById(R.id.countText)
        val plusBtn: Button     = findViewById(R.id.plusButton)
        val minusBtn: Button    = findViewById(R.id.minusButton)
        val resetBtn: Button    = findViewById(R.id.resetButton)

        fun updateUI() {
            countText.text = "Count: ${viewModel.count}"
        }

        plusBtn.setOnClickListener  { viewModel.increment(); updateUI() }
        minusBtn.setOnClickListener { viewModel.decrement(); updateUI() }
        resetBtn.setOnClickListener { viewModel.reset(); updateUI() }

        updateUI()   // show initial value
    }
}

ViewModel with LiveData

LiveData is an observable data holder. When the data changes, the UI updates automatically — no manual updateUI() calls needed:

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

class ScoreViewModel : ViewModel() {

    private val _score = MutableLiveData(0)
    val score: LiveData<Int> = _score   // read-only view for the Activity

    fun addPoints(points: Int) {
        _score.value = (_score.value ?: 0) + points
    }

    fun reset() {
        _score.value = 0
    }
}
// In Activity:
class ScoreActivity : AppCompatActivity() {
    private val viewModel: ScoreViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_score)

        val scoreText: TextView = findViewById(R.id.scoreText)

        // Observer updates the UI whenever score changes
        viewModel.score.observe(this) { newScore ->
            scoreText.text = "Score: $newScore"
        }

        findViewById<Button>(R.id.addTenBtn).setOnClickListener {
            viewModel.addPoints(10)
        }

        findViewById<Button>(R.id.resetBtn).setOnClickListener {
            viewModel.reset()
        }
    }
}

ViewModel Architecture Diagram


         UI Layer (Activity / Fragment)
         ┌─────────────────────────────┐
         │  Observes LiveData          │
         │  Calls ViewModel functions  │
         └──────────┬──────────────────┘
                    │ communicates via
         ┌──────────▼──────────────────┐
         │       ViewModel             │
         │  Holds state (LiveData)     │
         │  Contains business logic    │
         │  Survives rotation          │
         └──────────┬──────────────────┘
                    │ reads/writes
         ┌──────────▼──────────────────┐
         │  Data Sources               │
         │  (Repository, DB, API)      │
         └─────────────────────────────┘

Practical Example: Shopping List ViewModel

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

class ShoppingListViewModel : ViewModel() {
    private val _items = MutableLiveData<List<String>>(emptyList())
    val items: LiveData<List<String>> = _items

    fun addItem(name: String) {
        val current = _items.value ?: emptyList()
        if (name.isNotBlank() && name !in current) {
            _items.value = current + name
        }
    }

    fun removeItem(name: String) {
        _items.value = _items.value?.filter { it != name }
    }

    val itemCount: Int get() = _items.value?.size ?: 0
}

// In Activity, observe items:
// viewModel.items.observe(this) { list ->
//     adapter.submitList(list)
//     countText.text = "${list.size} items"
// }

The ViewModel keeps the shopping list alive across rotations and back presses, while the Activity only handles what the user sees — a clean separation that makes your Android app easier to maintain and test.

Leave a Comment

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