The android View Model is a class that has right for managing and preparing the data for a Fragment or an Activity. It also responsible the communication of the Activity / Fragment with the others application (e.g. calling the business logic classes)
What is the use of View Model in Android?
ViewModel is a component of Android Jetpack. The aim of ViewModel class is to manage UI-related data and to store in a lifecycle conscious way. The ViewModel class keep data safe when configuration changes such as screen rotations in portrait and landscape mode.
Benefits
While we rotate the android device from portrait to landscape mode and vice versa then the activity is restarted again and all the data is reloaded again. To prevent such kind of problem we can use ViewModel class to load data into view .
Problem
Example How to implement ViewModel
Add dependancy to Build.gradle
def lifecycle_version = "2.3.1"
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var mainViewModel: MainViewModel
lateinit var txtCounter:TextView
lateinit var btnCounter:Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel=ViewModelProvider(this).get(MainViewModel::class.java)
txtCounter=findViewById(R.id.textView)
txtCounter.text=mainViewModel.count.toString()
btnCounter=findViewById(R.id.button)
btnCounter.setOnClickListener {
increamentCounter()
}
}
private fun increamentCounter() {
mainViewModel.increament()
txtCounter.text=mainViewModel.count.toString()
}
}
MainViewModel
class MainViewModel : ViewModel() {
var count:Int=0
fun increament()
{
count++
}
}
Note
The ViewModel class also helps in implementing MVVM(Model-View-ViewModel) architecture pattern which is recommended by Google for Android app .
there are some other advantages of using ViewModel class provided by Android framework like:
- Handle configuration changes: when activity is recreated due to configuration changes then ViewModel objects are automatically retained
- Lifecycle Awareness: ViewModel objects are lifecycle-aware. They are automatically cleared when the Lifecycle they are observing gets destroyed.
- Data Sharing: Sharing of data between fragments in an activity using ViewModels much easier
- Kotlin-Coroutines support: ViewModel includes support for Kotlin-Coroutines. So, they can be easy for using any asynchronous processing.
- View model classes are independent from view class. They are only use for hold data and don’t aware who to use data and how to represent data, make sure you should have only data not a reference or particular view if you do so have have to introduce memory leak.
- We can not create view model object on our own. We use view model provider to create object of view model
- But view model create object with no arg constructor.
Suppose we want to pass argument in view model constructer ,but we can’t not create view model object so how can we do this?
We can solve this problem using view model factory class, view model factory create objects of view model as per our requirement.
View Model Factory
View Model Factory is responsible to create your instance of ViewModel. In this you can create object of view model with a parameterised constructor. ViewModelProvider.Factory is an interface. It have create method which is responsible for creating our VeiwModel’s instance.
As you can not call ViewModel constructor in Activity or Fragment You pass your ViewModel arguments to the ViewModelProvider.Factory using constructor or any other pattern like (Singleton, FactoryPattern etc.).when you are initializing ViewModel.
When to use ViewModelProvider.Factory?
If your ViewModel class have dependencies then you should pass this dependencies through the constructor. so you can use that dependencies and test your ViewModel.
When not to use ViewModelProvider.Factory
If your ViewModel class don’t have any dependencies then you will not need ViewModelProvider.Factory.
How to create view model factory class
You have to create a class and extend it with ViewModelProvider.Factory and implement it’s methods
class MainViewFactory(val counter :Int) :ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(counter) as T
}
}
How to use With live Data
Example 1:-
Gradle file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.test.viewmodeldemo1"
minSdkVersion 22
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
dataBinding{
enabled = true
}
}
dependencies {
def lifecycle_version = "2.2.0"
def arch_version = "2.1.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
viewModel.count.observe(this, Observer {
binding.countText.text = it.toString()
})
binding.button.setOnClickListener {
viewModel.updateCount()
}
}
}
MainActivityViewModel
class MainActivityViewModel : ViewModel() {
var count = MutableLiveData<Int>()
init {
count.value = 0
}
fun updateCount(){
count.value = (count.value)?.plus(1)
}
}
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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/count_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="66sp"
android:textStyle="bold"
android:typeface="serif"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.262" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Here"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Example 2
Gradle file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.test.viewmodeldemo"
minSdkVersion 22
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
dataBinding{
enabled = true
}
}
dependencies {
def lifecycle_version = "2.1.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainActivityViewModel
private lateinit var viewModelFactory: MainActivityViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModelFactory = MainActivityViewModelFactory(125)
viewModel = ViewModelProvider(this,viewModelFactory).get(MainActivityViewModel::class.java)
viewModel.totalData.observe(this, Observer {
binding.resultTextView.text = it.toString()
})
binding.insertButton.setOnClickListener {
viewModel.setTotal(binding.inputEditText.text.toString().toInt())
}
}
}
MainActivityViewModel
class MainActivityViewModel(startingTotal : Int) : ViewModel() {
private var total = MutableLiveData<Int>()
val totalData : LiveData<Int>
get() = total
init {
total.value = startingTotal
}
fun setTotal(input:Int){
total.value =(total.value)?.plus(input)
}
}
MainActivityViewModelFactory
class MainActivityViewModelFactory(private val startingTotal : Int) : ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainActivityViewModel::class.java)){
return MainActivityViewModel(startingTotal) as T
}
throw IllegalArgumentException("Unknown View Model Class")
}
}
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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/input_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="number"
android:textSize="36sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.323"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.064" />
<Button
android:id="@+id/insert_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginTop="40dp"
android:text="Add"
android:textSize="36sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/input_edit_text" />
<TextView
android:id="@+id/result_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="45dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="41dp"
android:textSize="36sp"
app:layout_constraintEnd_toEndOf="@+id/insert_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/insert_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Conclusion
ViewModelProvider.Factory is responsible for create your instance of ViewModel. If your ViewModel have dependencies and you want to test your ViewModel then you should create your own ViewModelProvider. Factory and passed dependency using ViewModel constructor and give value to the ViewModelProvider.Factory instance.