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.
Understanding 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
orRESUMED
). - 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>() }
//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() }
//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
orFragment
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!
- Step 1: Master the Basics of Kotlin Programming for Android Development
- Step 2: Setting Up Your Development Environment
- Step 3: Understanding Android Basics — Activities, Layouts, and Views
- Step 4: Working with Intents in Android
- Step 5: Fragments — Dynamic and Modular UI Design
- Step 6: RecyclerView and Adapters — Displaying Lists of Data
- Step 7: ViewModel and LiveData — Building Reactive Apps
- Next -> Step 8: Room Database — Local Data Storage
- Step 9: Networking with Retrofit — Fetching and Sending Data
- Step 10: Material Design — Enhancing User Experience
- Step 11: Firebase Integration — Real-time Data and Authentication
- Step 12: Testing and Debugging — Ensuring App Stability
- Step 13: Publishing Your App — Taking It Live.
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!