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.
