Step 7: LiveData and ViewModel in Android – Smarter Data Handling for Modern Apps

Managing UI Data with LiveData and ViewModel

Welcome back, Android explorers! You’ve navigated through layouts, Intents, Fragments, and even conquered the mighty RecyclerView. Now, let’s tackle something that makes your apps not just functional but robust and lifecycle-smart — say hello to LiveData and ViewModel!

Android ViewModels

This is Step 7 in your journey to mastering Android development with Kotlin. In this post, we’ll look at how LiveData and ViewModel help you handle UI-related data in a clean, efficient, and lifecycle-aware way. Trust me, your future self will thank you.

Why LiveData and ViewModel?

Imagine this: your app rotates, the Activity is recreated, and boom - all your user data vanishes. Not fun, right? That's where LiveData and ViewModel step in.
  • ViewModel: Stores and manages UI-related data in a lifecycle-conscious way. It survives configuration changes like screen rotations.
  • LiveData: A data holder that is lifecycle-aware, meaning it updates only when the UI is active and prevents memory leaks.
Together, they help keep your UI updated and your logic clean, like peanut butter and jelly.

ViewModel Scope and Benefits 

A ViewModel is scoped to a LifecycleOwner like an Activity or a Fragment. It lives as long as the scope is alive. Here's why this is so powerful:
  • Survives Configuration Changes: When a device is rotated, the Activity is destroyed and recreated, but the ViewModel persists.
  • Separation of Concerns: Keeps UI logic out of Activity/Fragment.
  • Testability: ViewModel can be tested independently of the UI. 
  • Decoupling: Enables communication with other architecture components like the Repository and UseCases.
Use ViewModelProvider or by viewModels() Kotlin property delegate to scope your ViewModel to an Activity or Fragment.

Understanding ViewModel Lifecycle



android-viewmodel-lifecycle


The ViewModel is created when the associated Activity or Fragment is first created and is retained as long as the scope exists. It is destroyed only when the associated LifecycleOwner is permanently gone. 
  • If your Activity is destroyed due to configuration changes (like screen rotation), the same ViewModel instance is provided to the new Activity instance. 
  • The ViewModel is cleared when the Activity is finished or the Fragment is detached permanently. 
 This makes ViewModel a perfect choice for holding UI state and logic that shouldn't be lost.

What is LiveData?

LiveData is an observable data holder that is lifecycle-aware. This means: 
  • It only updates observers that are in an active lifecycle state (like STARTED or RESUMED).
  • Prevents memory leaks by automatically removing inactive observers.
  • Perfect for communicating between the ViewModel and UI components. 
There are two types:
  • MutableLiveData: You can change the value. 
  • LiveData: You can observe but not modify the value (read-only).

Step-by-Step Implementation

Let’s build a simple counter app demonstrating LiveData and ViewModel in action.

Step 1: Add Dependencies (if needed)

Make sure you're using the latest AndroidX lifecycle libraries in your build.gradle:

dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
}

Step 2: Create the ViewModel

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

class CounterViewModel : ViewModel() {
    private val _count = MutableLiveData(0)
    val count: LiveData<Int> get() = _count

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

Step 3: Create the UI (activity_main.xml)

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp">

    <TextView
        android:id="@+id/countTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24sp" />

    <Button
        android:id="@+id/incrementButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Increment" />
</LinearLayout>

Step 4: Connect ViewModel to UI (MainActivity.kt)

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

class MainActivity : AppCompatActivity() {
    private val viewModel: CounterViewModel by viewModels()

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

        val countTextView: TextView = findViewById(R.id.countTextView)
        val incrementButton: Button = findViewById(R.id.incrementButton)

        // Observe LiveData
        viewModel.count.observe(this, Observer { count ->
            countTextView.text = "Count: $count"
        })

        incrementButton.setOnClickListener {
            viewModel.increment()
        }
    }
}

Now your UI reacts automatically to changes in the ViewModel, even across configuration changes like screen rotation.

Real-World Use Cases with ViewModel and LiveData

  • Form validation: Observe LiveData for input fields and update the UI.
  • Network response handling: Fetch data using a Repository and update LiveData. The UI observes the changes.
  • Navigation events: Use LiveData as a single-time event trigger to navigate without leaks.
  • Loading states: Display loaders by observing a LiveData for loading status.

Best Practices for Implementing ViewModels

  • Scope ViewModels to Screen-Level State: Use ViewModels for screen-level state management, not for reusable UI components (e.g., chip groups, forms). Reusing the same ViewModel instance across different UI components can cause issues unless you use explicit view model keys.
  • Keep ViewModels UI-Agnostic: Avoid tying ViewModels to specific UI implementation details. Use generic method names and UI state fields to ensure compatibility across different device types (mobile, tablet, Chromebook, etc.).
  • //incorrect usage:
    class UserProfileViewModel : ViewModel() {
        val userName = MutableLiveData<String>()
        val userBio = MutableLiveData<String>()
    }
    
    //correct usage:
    class UserProfileViewModel : ViewModel() {
        val name = MutableLiveData<String>()
        val bio = MutableLiveData<String>()
    }
  • Avoid Holding References to Lifecycle-Related APIs: To prevent memory leaks, ViewModels should not hold references to Context or Resources, as they may outlive the ViewModelStoreOwner.
  • //incorrect usage:
    class UserProfileViewModel(context: Context) : ViewModel() {
        val userPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
    }
    
    //correct usage:
    class UserProfileViewModel(private val userRepository: UserRepository) : ViewModel() {
        val user = userRepository.getUserProfile()
    }
  • Limit ViewModel Scope: Don't pass ViewModels to other classes or UI components. Keep them close to the Activity, Fragment, or screen-level composable function to avoid exposing unnecessary data and logic to lower-level components.
  • //incorrect usage:
    class MainActivity : AppCompatActivity() {
        private lateinit var sharedViewModel: UserProfileViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            
            // using applicationContext could expose data and logic that aren't necessary at a lower level.
            
            sharedViewModel = ViewModelProvider(applicationContext).get(UserProfileViewModel::class.java)
        }
    }
    
    //correct usage:
    class MainActivity : AppCompatActivity() {
        private lateinit var userProfileViewModel: UserProfileViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            
            // ViewModel is scoped to the Activity lifecycle, ensuring it's only used within this component and not exposed to other parts of the app.
            
            userProfileViewModel = ViewModelProvider(this).get(UserProfileViewModel::class.java)
        }
    }

Summary

  • ViewModel survives configuration changes and keeps UI logic clean.
  • LiveData is lifecycle-aware and prevents memory leaks.
  • You can observe state changes, update the UI reactively, and separate UI logic from business logic.
  • ViewModel is ideal for storing data that needs to survive Activity or Fragment recreation.

What’s Next?

In Step 8, we’ll introduce Room Database to persist your app’s data locally. Say goodbye to data loss when the app closes!

Stay Tuned! If you're enjoying the series, don’t forget to subscribe and follow for more fun, practical Android development tutorials. Got a question? Drop it in the comments!—I’m here to help!

Let’s get coding and bring your app ideas to life! 

Pragnesh Ghoda

A forward-thinking developer offering more than 8 years of experience building, integrating, and supporting android applications for mobile and tablet devices on the Android platform. Talks about #kotlin and #android

Post a Comment

Please let us know about any concerns or query.

Previous Post Next Post

Contact Form