Applying ViewModel
The ViewModel
sample module in the Sampler
and SamplerJ
projects start with the same code that we used in RecyclerViewBasics
. So, we have a RecyclerView
in an activity that is set up to display 25 random numbers as colors. The difference is that this time, we will use a ViewModel
to retain those random numbers across a configuration change. Also, we will log the lifecycle methods, so we can see them as they are executed.
The Dependencies
You get ViewModel
via a transitive dependency from androidx.appcompat:appcompat
— Java developers do not need anything else.
Kotlin developers will want to add androidx.lifecycle:lifecycle-viewmodel-ktx
as a dependency. This adds a few Kotlin-specific extension functions associated with ViewModel
and related classes. They make using ViewModel
a little bit easier in Kotlin than if you were to use the Java API alone. This is a common pattern in Jetpack: have a Java API with a separate library containing Kotlin extension functions. The “Android KTX” brand is used for these Kotlin extension function libraries.
Kotlin developers also will want to add androidx.activity:activity-ktx
as a dependency. This adds some Kotlin-specific extension functions to Activity
, including ones that we will use when working with ViewModel
.
So, in our Kotlin project, we have:
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation "androidx.activity:activity-ktx:1.3.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
The Java project just needs appcompat
:
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
}
The ViewModel
Our project now has a ColorViewModel
class that inherits from Jetpack’s ViewModel
, both in Java:
package com.commonsware.jetpack.samplerj.viewmodel;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import androidx.lifecycle.ViewModel;
public class ColorViewModel extends ViewModel {
private final Random random = new Random();
final List<Integer> numbers = buildItems();
private List<Integer> buildItems() {
ArrayList<Integer> result = new ArrayList<>(25);
for (int i = 0; i < 25; i++) {
result.add(random.nextInt());
}
return result;
}
}
…and Kotlin:
package com.commonsware.jetpack.sampler.viewmodel
import androidx.lifecycle.ViewModel
import java.util.*
class ColorViewModel : ViewModel() {
private val random = Random()
val numbers = List(25) { random.nextInt() }
}
Our random numbers are now created in the ColorViewModel
, via property initializers. Otherwise, our ColorViewModel
itself is fairly unremarkable.
Using the ViewModel
How you get a ViewModel
depends a bit on whether you are using Java or Kotlin.
Java
Our activity can get its instance of ColorViewModel
from a ViewModelProvider
, which we can create using an ordinary constructor. On that, we can call get()
to retrieve our ColorViewModel
. We need to pass the Java Class
object for our ColorViewModel
as a parameter to get()
:
ColorViewModel vm = new ViewModelProvider(this).get(ColorViewModel.class);
We can then use the numbers
property of our ColorViewModel
to populate our RecyclerView
:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityMainBinding binding =
ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
ColorAdapter adapter = new ColorAdapter(getLayoutInflater());
ColorViewModel vm = new ViewModelProvider(this).get(ColorViewModel.class);
adapter.submitList(vm.numbers);
binding.items.setLayoutManager(new LinearLayoutManager(this));
binding.items.addItemDecoration(
new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
binding.items.setAdapter(adapter);
Log.d(TAG, "onCreate() called!");
}
Kotlin
In Kotlin, the activity-ktx
library gives us a viewModels
property delegate that we can use:
val vm: ColorViewModel by viewModels()
Then, as with Java, we can use the numbers
property of our ColorViewModel
to populate our RecyclerView
:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val vm: ColorViewModel by viewModels()
binding.items.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
addItemDecoration(
DividerItemDecoration(
this@MainActivity,
DividerItemDecoration.VERTICAL
)
)
adapter = ColorAdapter(layoutInflater).apply {
submitList(vm.numbers)
}
}
Log.d(TAG, "onCreate() called!")
}
The Older Solution
Earlier versions of this book, and various blog posts and articles, will show ViewModelProviders.of()
as the way to get a ViewModel
. This was deprecated and should no longer be used, as it may be removed in future versions of the lifecycle-extensions
library.
The Results
If you run this sample app, and you rotate the screen, the random numbers are the same.
The reason this works is that we have the same ColorViewModel
instance in both the old activity (before the configuration change) and the new activity (after the configuration change). ViewModelProvider
and the rest of the ViewModel
portion of Jetpack will ensure that we have the same ColorViewModel
in the old and new activity instances. Since our ColorViewModel
holds our data, our data is retained across the configuration change, so we have the same numbers in both the old and the new activity.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.