ViewModel and LiveData in Android Kotlin: A Practical Example
In this article, we will explore a simple yet practical example of using ViewModel
and LiveData
in an Android Kotlin project. These two components, part of the Android Architecture Components, help developers manage UI-related data in a lifecycle-conscious way. The example consists of a MainActivity
, a RandomNumberViewModel
, and a simple XML layout with a button and a TextView
.
The goal of the example is to demonstrate how LiveData
and ViewModel
can be used together to generate and observe random numbers. Every time a user clicks the button, a new random number is generated and displayed in the TextView
. The use of ViewModel
ensures that the random number persists across configuration changes, such as device rotation, while LiveData
makes sure the UI updates automatically whenever the data changes.
MainActivity: Setting up ViewModel and Observing LiveData
In the MainActivity
, the onCreate
function is responsible for setting up the ViewModel
and linking it to the UI components. First, we use the ViewModelProviders.of(this).get()
method to instantiate the RandomNumberViewModel
. This ViewModel instance is tied to the lifecycle of the activity, meaning it survives configuration changes like screen rotations.
Next, an Observer
is created for the LiveData
object. The Observer
listens for changes in the currentRandomNumber
property of the ViewModel
. When a new number is generated, this observer updates the TextView
with the latest value. The observe()
method is used to bind the LiveData
object to the activity's lifecycle, ensuring the UI only updates when the activity is in an appropriate state (e.g., started or resumed).
The Button
in the UI is wired to generate a new random number every time it is clicked. This is achieved by updating the currentRandomNumber
property of the ViewModel
, which triggers the Observer
to update the displayed number.
RandomNumberViewModel: Managing Data
The RandomNumberViewModel
class extends ViewModel
, providing a place to store and manage the random number. The currentRandomNumber
property is a MutableLiveData
object, which can hold integer values and notify observers when the data changes.
The by lazy
keyword is used to initialize the currentRandomNumber
only when it is accessed for the first time. This ensures that the LiveData
object is not created unnecessarily, improving performance. The ViewModel
ensures that the random number persists even when the MainActivity
is destroyed and recreated during a configuration change.
Layout Design: Button and TextView
The layout file activity_main.xml
defines a simple UI with a Button
and a TextView
. The button, labeled "Generate New Random Number", triggers the generation of a new random number when clicked. The TextView
displays the current number generated by the ViewModel
.
The layout uses a ConstraintLayout
to manage the positioning of the Button
and TextView
. The button is placed at the top, and the TextView
is positioned directly below it, centered horizontally. This minimal UI design is perfect for demonstrating the interaction between ViewModel
, LiveData
, and the activity lifecycle.
Dependencies
To use ViewModel
and LiveData
, the project needs the appropriate dependencies. In this example, the following dependency is added to the build.gradle
file:
This ensures that the ViewModel
and LiveData
classes are available for use in the project.
Conclusion
This example demonstrates the power of ViewModel
and LiveData
in building responsive and lifecycle-aware Android applications. By separating UI data management from the activity lifecycle, these components allow for more maintainable and robust code. The use of LiveData
ensures that the UI is always in sync with the data, while ViewModel
provides a way to store data that survives configuration changes.
This architecture is ideal for scenarios where UI data needs to be persisted across different states of an activity. By adopting these components in your applications, you can greatly improve performance, simplify your code, and provide a more seamless user experience.
package com.cfsuman.jetpack
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import java.util.*
class MainActivity : AppCompatActivity() {
private lateinit var mModel: RandomNumberViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get the view model
mModel = ViewModelProviders.of(this).get(RandomNumberViewModel::class.java)
// Create the observer which updates the ui
val randomNumberObserver = Observer<Int>{newNumber->
// Update the ui with current data
textView.text = "Current Number : $newNumber"
}
// Observe the live data, passing in this activity as the life cycle owner and the observer
mModel.currentRandomNumber.observe(this,randomNumberObserver)
// Button click listener
button.setOnClickListener{
// Change the data
mModel.currentRandomNumber.value = Random().nextInt(50)
}
}
}
package com.cfsuman.jetpack
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class RandomNumberViewModel:ViewModel(){
// Create a LiveData with a random number
val currentRandomNumber:MutableLiveData<Int> by lazy {
MutableLiveData<Int>()
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/rootLayout"
tools:context=".MainActivity"
android:background="#fdfdfc">
<Button
android:text="Generate New Random Number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.454"
android:layout_marginTop="32dp"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:text="Live Data"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@+id/button"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
def lifecycle_version = "2.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"