Android Viewmodel And Livedata Complete Guide
Understanding the Core Concepts of Android ViewModel and LiveData
Android ViewModel and LiveData: Detailed Explanation with Important Information
Lifecycle Awareness Both ViewModel and LiveData are integral components of the Jetpack Architecture Components framework. They are designed to be aware of the lifecycle of the UI (Activity or Fragment) they are attached to. This means that they handle configuration changes without losing the associated data and prevent memory leaks by automatically detaching themselves from the UI when it is destroyed.
ViewModel
Purpose A ViewModel acts as a central repository of UI-related data that is not dependent on the activity or fragment's lifecycle. This allows the UI to stay consistent while being destroyed and recreated during configuration changes, such as screen rotations or language settings switches.
Key Features
- Data Survives Configuration Changes: When an activity or fragment is destroyed and recreated due to configuration changes, the ViewModel survives. As a result, the data it holds, such as user inputs or network responses, remains intact.
- Handles UI-Related State: The ViewModel provides data to the UI components and manages their state in a lifecycle-conscious way.
- Avoids Memory Leaks: Because the ViewModel is not tied to the activity or fragment, it ensures that long-running operations do not cause memory leaks.
- Simplified Communication: It simplifies communication between different UI components (e.g., fragments within the same activity).
Example of Usage
public class MyViewModel extends ViewModel {
private MutableLiveData<String> currentName;
public MutableLiveData<String> getCurrentName() {
if (currentName == null) {
currentName = new MutableLiveData<String>();
}
return currentName;
}
public void setName(String name) {
currentName.postValue(name);
}
}
Retrieving ViewModel Instance
Use the ViewModelProvider
or ViewModelProvider.Factory
to get an instance of the ViewModel. This ensures that the ViewModel is retained across configuration changes.
MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
Benefits
- Consistency: Data consistency across config changes without needing to use
onSaveInstanceState()
method. - Efficiency: Reduces boilerplate code for retaining data and simplifies data management.
- Testing: Easier to test as UI-related business logic can be moved to the ViewModel.
LiveData
Purpose LiveData is an observable data holder class following the observer pattern. It is lifecycle-aware, meaning it respects the lifecycle of other app components such as activities, fragments, or services. This awareness ensures that LiveData only updates app component observers that are in an active lifecycle state.
Key Features
- Automatic Lifecycle Management: Only updates observers that are in an active lifecycle state (STARTED or RESUMED).
- Thread Safety: Ensures that the latest data is delivered to the observer regardless of the thread from which the value is set.
- Sticky Nature: Observers receive the most recent value even if they start observing after the event is posted.
- Data Binding: Seamlessly integrates with Data Binding and Android UI, which helps in reducing boilerplate code related to updating the UI.
Example of Usage
public class MyViewModel extends ViewModel {
private MutableLiveData<String> currentName = new MutableLiveData<>();
public LiveData<String> getName() {
return currentName;
}
public void setName(String name) {
currentName.setValue(name);
}
}
Updating LiveData
LiveData values can be updated using setValue()
or postValue()
. The primary difference is that setValue()
should be called from the main thread, whereas postValue()
can be used from background threads.
Benefits
- UI Consistency: Automatically updates the UI when the data changes but only when the UI is active.
- Threading Efficiency: Manages threading internally to deliver updates on the main thread.
- Memory Efficient: Only stores necessary copies of data and avoids unnecessary updates to inactive components.
Combining ViewModel and LiveData
When combined, ViewModel and LiveData offer a streamlined approach to handling UI-related data. The ViewModel stores the data, and the LiveData acts as an observer for this data, ensuring that the UI updates reactively based on data changes.
Architecture Overview
- UI Component: Activity or Fragment responsible for displaying data.
- ViewModel: Acts as a mediator between UI and data source. Handles data and provides it to the UI via LiveData.
- LiveData: Observes data changes and notifies UI components only when they are in an active lifecycle state.
- Repository/Data Source: Layer responsible for fetching data either from a local database or a remote server.
Lifecycle-Aware Observing The key advantage of LiveData over traditional observer patterns is its lifecycle-aware nature. By attaching observers only for the active lifecycle states (STARTED and RESUMED), LiveData ensures that app components receive updates only when they are visible or interactable, thus avoiding unnecessary processing during inactive states.
Example Flow
- Activity/Fragment: Subscribes to the LiveData object held in the ViewModel.
- ViewModel: Queries data from a Repository and posts it to the LiveData.
- Repository/Data Source: Fetches data from a database or network and returns it to the ViewModel.
Best Practices
- Encapsulation: Always encapsulate LiveData objects, providing getter methods instead of exposing them directly.
- Data Transformation: Use
Transformations
orMediatorLiveData
to transform or merge multiple LiveData sources. - Background Operations: Avoid complex data processing tasks within the ViewModel itself. Instead, delegate these tasks to repositories or workers.
Online Code run
Step-by-Step Guide: How to Implement Android ViewModel and LiveData
Step 1: Set Up Your Project
Create a New Project:
- Open Android Studio.
- Click on "Start a new Android Studio project".
- Choose "Empty Activity".
- Name your application (e.g.,
CounterApp
). - Choose a language (Kotlin for this example).
- Click "Finish" to create the project.
Add Required Dependencies:
- Open
build.gradle (Module: app)
and add the following dependencies for ViewModel and LiveData:dependencies { def lifecycle_version = "2.6.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" }
- Click "Sync Now" to sync your project with the Gradle files.
- Open
Step 2: Create a ViewModel
Create a New Kotlin Class:
- In the
app/src/main/java/com/example/counterapp
directory, create a new Kotlin class namedCounterViewModel
.
- In the
Implement the ViewModel:
- Inside
CounterViewModel.kt
, define a MutableLiveData to store the counter value and a function to increment the counter:package com.example.counterapp import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class CounterViewModel : ViewModel() { private val _counter = MutableLiveData<Int>().apply { value = 0 } val counter: LiveData<Int> get() = _counter fun incrementCounter() { _counter.value = (_counter.value ?: 0) + 1 } }
- Inside
Step 3: Set Up the Activity
Open
MainActivity.kt
:- Inside the
MainActivity.kt
file, we will set up the ViewModel and observe the LiveData.
- Inside the
Modify
MainActivity.kt
:- Here is the complete code for
MainActivity.kt
:package com.example.counterapp import android.os.Bundle import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import com.example.counterapp.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val viewModel: CounterViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // Observe the counter LiveData viewModel.counter.observe(this, Observer { newCount -> binding.counterText.text = newCount.toString() }) // Set up the button click listener binding.incrementButton.setOnClickListener { viewModel.incrementCounter() } } }
- Here is the complete code for
Step 4: Add Layout Resources
Open
res/layout/activity_main.xml
:- Modify the layout to include a
TextView
to display the counter and aButton
to increment it.
- Modify the layout to include a
Modify
activity_main.xml
:- Here is the complete code for
activity_main.xml
:<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="counterViewModel" type="com.example.counterapp.CounterViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/counterText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0" android:textSize="48sp" app:layout_constraintBottom_toTopOf="@+id/incrementButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/incrementButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Increment" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/counterText" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
- Here is the complete code for
Step 5: Bind ViewModel to Layout (Optional)
Modify
activity_main.xml
(Optional):- If you want to bind the ViewModel directly in the layout using Data Binding, you can do so. However, we have already handled the binding in the
MainActivity.kt
. Here is how you can do it:
- If you want to bind the ViewModel directly in the layout using Data Binding, you can do so. However, we have already handled the binding in the
Modify
activity_main.xml
for Data Binding:- Here is the updated
activity_main.xml
for Data Binding:<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="counterViewModel" type="com.example.counterapp.CounterViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/counterText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(counterViewModel.counter)}" android:textSize="48sp" app:layout_constraintBottom_toTopOf="@+id/incrementButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/incrementButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Increment" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/counterText" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
- Here is the updated
Modify
MainActivity.kt
for Data Binding (Optional):- Here is the updated
MainActivity.kt
for Data Binding:
- Here is the updated
Login to post a comment.