Android Kotlin: ViewModel LiveData example

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:

gradle

def lifecycle_version = "2.0.0" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"

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.


MainActivity.kt

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)
        }
    }
}
RandomNumberViewModel.kt

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>()
    }
}
activity_main.xml

<?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>
gradle dependencies

def lifecycle_version = "2.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"