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()
You can learn more about property delegates in the "Property Delegates" chapter of Elements of Kotlin!

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.